speedrun-cli 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/cli.js +224 -0
- package/index.js +12 -0
- package/package.json +74 -0
- package/src/constants.js +65 -0
- package/src/generator.js +271 -0
- package/src/index.js +13 -0
- package/src/moduleGenerator.js +586 -0
- package/src/postSetup.js +365 -0
- package/src/prompts.js +189 -0
- package/src/utils.js +112 -0
- package/templates/README.md +81 -0
- package/templates/base/eslint.config.mjs +34 -0
- package/templates/base/gitignore +78 -0
- package/templates/base/nest-cli.json +8 -0
- package/templates/base/src/common/constants/cookie.config.ts +20 -0
- package/templates/base/src/common/decorators/get-user.decorator.ts +13 -0
- package/templates/base/src/common/decorators/public.decorator.ts +4 -0
- package/templates/base/src/common/decorators/roles.decorator.ts +4 -0
- package/templates/base/src/common/dtos/pagination.dto.ts +29 -0
- package/templates/base/src/common/filters/http-exception.filter.ts +108 -0
- package/templates/base/src/common/guards/auth.guard.ts +51 -0
- package/templates/base/src/common/guards/refresh-token.guard.ts +39 -0
- package/templates/base/src/common/guards/roles.guard.ts +45 -0
- package/templates/base/src/common/interceptors/response.interceptor.ts +55 -0
- package/templates/base/src/common/interfaces/api-response.interface.ts +26 -0
- package/templates/base/src/common/middleware/correlation-id.middleware.ts +20 -0
- package/templates/base/src/common/validators/password.validator.ts +37 -0
- package/templates/base/src/config/config.module.ts +14 -0
- package/templates/base/src/config/logger.config.ts +109 -0
- package/templates/base/src/main.ts +84 -0
- package/templates/base/src/modules/auth/auth.controller.ts +133 -0
- package/templates/base/src/modules/auth/dtos/login.dto.ts +11 -0
- package/templates/base/src/modules/auth/dtos/signup.dto.ts +8 -0
- package/templates/base/src/modules/health/health.controller.ts +20 -0
- package/templates/base/src/modules/health/health.module.ts +7 -0
- package/templates/base/src/modules/users/dtos/update-profile.dto.ts +12 -0
- package/templates/base/src/modules/users/dtos/update-user.dto.ts +13 -0
- package/templates/base/src/modules/users/users.controller.ts +65 -0
- package/templates/base/test/app.e2e-spec.ts +24 -0
- package/templates/base/test/jest-e2e.json +9 -0
- package/templates/base/tsconfig.build.json +4 -0
- package/templates/base/tsconfig.json +24 -0
- package/templates/base-crud/CRUD_README.md +385 -0
- package/templates/base-crud/src/common/base/base.controller.ts +321 -0
- package/templates/base-crud/src/common/base/base.service.ts +192 -0
- package/templates/base-crud/src/common/base/index.ts +20 -0
- package/templates/base-crud/src/common/base/swagger/api-response.dto.ts +82 -0
- package/templates/base-crud/src/common/base/swagger/paginated.dto.ts +102 -0
- package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +44 -0
- package/templates/base-crud/src/modules/products/dto/product.dto.ts +38 -0
- package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +12 -0
- package/templates/base-crud/src/modules/products/products.controller.ts +94 -0
- package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +79 -0
- package/templates/base-crud-drizzle/src/modules/products/products.module.ts +24 -0
- package/templates/base-crud-drizzle/src/modules/products/products.service.ts +140 -0
- package/templates/base-crud-drizzle/src/modules/products/schema/products.schema.ts +39 -0
- package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +79 -0
- package/templates/base-crud-mongoose/src/modules/products/products.module.ts +26 -0
- package/templates/base-crud-mongoose/src/modules/products/products.service.ts +133 -0
- package/templates/base-crud-mongoose/src/modules/products/schemas/product.schema.ts +67 -0
- package/templates/base-crud-prisma/src/modules/products/products.module.ts +12 -0
- package/templates/base-crud-prisma/src/modules/products/products.service.ts +100 -0
- package/templates/base-crud-typeorm/src/modules/products/entities/product.entity.ts +58 -0
- package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +79 -0
- package/templates/base-crud-typeorm/src/modules/products/products.module.ts +25 -0
- package/templates/base-crud-typeorm/src/modules/products/products.service.ts +102 -0
- package/templates/database/mongodb/.env.example +22 -0
- package/templates/database/mysql/.env.example +24 -0
- package/templates/database/mysql/drizzle.config.ts +13 -0
- package/templates/database/mysql/package.json +5 -0
- package/templates/database/mysql/prisma/schema.prisma +55 -0
- package/templates/database/mysql/src/database/drizzle.ts +13 -0
- package/templates/database/mysql/src/database/schema.ts +58 -0
- package/templates/database/postgres/.env.example +24 -0
- package/templates/database/postgres/drizzle.config.ts +13 -0
- package/templates/database/postgres/package.json +8 -0
- package/templates/database/postgres/prisma/schema.prisma +55 -0
- package/templates/database/sqlite/.env.example +24 -0
- package/templates/database/sqlite/drizzle.config.ts +13 -0
- package/templates/database/sqlite/package.json +8 -0
- package/templates/database/sqlite/prisma/schema.prisma +48 -0
- package/templates/database/sqlite/src/database/drizzle.ts +11 -0
- package/templates/database/sqlite/src/database/schema.ts +52 -0
- package/templates/orm/drizzle/drizzle.config.ts +13 -0
- package/templates/orm/drizzle/package.json +90 -0
- package/templates/orm/drizzle/src/app.module.ts +73 -0
- package/templates/orm/drizzle/src/config/env.validation.ts +55 -0
- package/templates/orm/drizzle/src/database/database.module.ts +25 -0
- package/templates/orm/drizzle/src/database/drizzle.ts +13 -0
- package/templates/orm/drizzle/src/database/schema.ts +60 -0
- package/templates/orm/drizzle/src/database/seed.ts +87 -0
- package/templates/orm/drizzle/src/modules/auth/auth.module.ts +12 -0
- package/templates/orm/drizzle/src/modules/auth/auth.service.ts +298 -0
- package/templates/orm/drizzle/src/modules/health/health.controller.ts +34 -0
- package/templates/orm/drizzle/src/modules/health/health.module.ts +7 -0
- package/templates/orm/drizzle/src/modules/users/users.module.ts +10 -0
- package/templates/orm/drizzle/src/modules/users/users.service.ts +152 -0
- package/templates/orm/mongoose/.env.example +22 -0
- package/templates/orm/mongoose/package.json +86 -0
- package/templates/orm/mongoose/src/app.module.ts +73 -0
- package/templates/orm/mongoose/src/config/env.validation.ts +55 -0
- package/templates/orm/mongoose/src/database/database.module.ts +19 -0
- package/templates/orm/mongoose/src/database/seed.ts +107 -0
- package/templates/orm/mongoose/src/modules/auth/auth.module.ts +21 -0
- package/templates/orm/mongoose/src/modules/auth/auth.service.ts +272 -0
- package/templates/orm/mongoose/src/modules/auth/dtos/login.dto.ts +10 -0
- package/templates/orm/mongoose/src/modules/auth/dtos/signup.dto.ts +8 -0
- package/templates/orm/mongoose/src/modules/health/health.controller.ts +26 -0
- package/templates/orm/mongoose/src/modules/health/health.module.ts +7 -0
- package/templates/orm/mongoose/src/modules/users/dtos/update-profile.dto.ts +24 -0
- package/templates/orm/mongoose/src/modules/users/dtos/update-user.dto.ts +13 -0
- package/templates/orm/mongoose/src/modules/users/users.module.ts +19 -0
- package/templates/orm/mongoose/src/modules/users/users.service.ts +179 -0
- package/templates/orm/mongoose/src/schemas/index.ts +2 -0
- package/templates/orm/mongoose/src/schemas/refresh-token.schema.ts +55 -0
- package/templates/orm/mongoose/src/schemas/user.schema.ts +53 -0
- package/templates/orm/prisma/package.json +102 -0
- package/templates/orm/prisma/prisma/schema.prisma +60 -0
- package/templates/orm/prisma/prisma/seed.ts +69 -0
- package/templates/orm/prisma/src/app.module.ts +55 -0
- package/templates/orm/prisma/src/config/env.validation.ts +56 -0
- package/templates/orm/prisma/src/modules/auth/auth.module.ts +17 -0
- package/templates/orm/prisma/src/modules/auth/auth.service.ts +308 -0
- package/templates/orm/prisma/src/modules/health/health.controller.ts +26 -0
- package/templates/orm/prisma/src/modules/health/health.module.ts +10 -0
- package/templates/orm/prisma/src/modules/users/users.module.ts +11 -0
- package/templates/orm/prisma/src/modules/users/users.service.ts +105 -0
- package/templates/orm/prisma/src/prisma/prisma.module.ts +9 -0
- package/templates/orm/prisma/src/prisma/prisma.service.ts +16 -0
- package/templates/orm/typeorm/package.json +100 -0
- package/templates/orm/typeorm/src/app.module.ts +55 -0
- package/templates/orm/typeorm/src/config/env.validation.ts +58 -0
- package/templates/orm/typeorm/src/database/data-source.ts +19 -0
- package/templates/orm/typeorm/src/database/database.module.ts +27 -0
- package/templates/orm/typeorm/src/database/seed.ts +76 -0
- package/templates/orm/typeorm/src/entities/index.ts +2 -0
- package/templates/orm/typeorm/src/entities/refresh-token.entity.ts +45 -0
- package/templates/orm/typeorm/src/entities/user.entity.ts +51 -0
- package/templates/orm/typeorm/src/modules/auth/auth.module.ts +19 -0
- package/templates/orm/typeorm/src/modules/auth/auth.service.ts +286 -0
- package/templates/orm/typeorm/src/modules/health/health.controller.ts +24 -0
- package/templates/orm/typeorm/src/modules/health/health.module.ts +9 -0
- package/templates/orm/typeorm/src/modules/users/users.module.ts +13 -0
- package/templates/orm/typeorm/src/modules/users/users.service.ts +108 -0
- package/templates/swagger/src/common/dtos/pagination.dto.ts +43 -0
- package/templates/swagger/src/main.ts +116 -0
- package/templates/swagger/src/modules/auth/auth.controller.ts +235 -0
- package/templates/swagger/src/modules/auth/dtos/login.dto.ts +20 -0
- package/templates/swagger/src/modules/auth/dtos/signup.dto.ts +14 -0
- package/templates/swagger/src/modules/users/dtos/update-profile.dto.ts +19 -0
- package/templates/swagger/src/modules/users/dtos/update-user.dto.ts +23 -0
- package/templates/swagger/src/modules/users/users.controller.ts +214 -0
- package/templates/swagger-mongoose/src/modules/auth/dtos/login.dto.ts +20 -0
- package/templates/swagger-mongoose/src/modules/auth/dtos/signup.dto.ts +14 -0
- package/templates/swagger-mongoose/src/modules/users/dtos/update-profile.dto.ts +40 -0
- package/templates/swagger-mongoose/src/modules/users/dtos/update-user.dto.ts +23 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
Get,
|
|
4
|
+
Param,
|
|
5
|
+
Query,
|
|
6
|
+
ParseUUIDPipe,
|
|
7
|
+
HttpStatus,
|
|
8
|
+
Type,
|
|
9
|
+
} from '@nestjs/common';
|
|
10
|
+
import {
|
|
11
|
+
ApiTags,
|
|
12
|
+
ApiOperation,
|
|
13
|
+
ApiResponse,
|
|
14
|
+
ApiBearerAuth,
|
|
15
|
+
ApiExtraModels,
|
|
16
|
+
ApiParam,
|
|
17
|
+
} from '@nestjs/swagger';
|
|
18
|
+
import { BaseController } from '../../common/base/base.controller';
|
|
19
|
+
import {
|
|
20
|
+
ApiResponseDto,
|
|
21
|
+
ApiResponseSchema,
|
|
22
|
+
PaginatedResponseDto,
|
|
23
|
+
PaginatedResponseSchema,
|
|
24
|
+
PaginationQueryDto,
|
|
25
|
+
} from '../../common/base';
|
|
26
|
+
import { ProductsService, ProductEntity } from './products.service';
|
|
27
|
+
import { CreateProductDto } from './dto/create-product.dto';
|
|
28
|
+
import { UpdateProductDto } from './dto/update-product.dto';
|
|
29
|
+
import { ProductDto } from './dto/product.dto';
|
|
30
|
+
|
|
31
|
+
@ApiTags('Products')
|
|
32
|
+
@ApiBearerAuth('bearer')
|
|
33
|
+
@ApiExtraModels(ApiResponseDto, PaginatedResponseDto, ProductDto)
|
|
34
|
+
@Controller('products')
|
|
35
|
+
export class ProductsController extends BaseController<
|
|
36
|
+
ProductEntity,
|
|
37
|
+
CreateProductDto,
|
|
38
|
+
UpdateProductDto
|
|
39
|
+
> {
|
|
40
|
+
constructor(private readonly productsService: ProductsService) {
|
|
41
|
+
super(productsService);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected getDtoClass(): Type<ProductEntity> {
|
|
45
|
+
return ProductDto as unknown as Type<ProductEntity>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Get()
|
|
49
|
+
@ApiOperation({ summary: 'Get all products (paginated)' })
|
|
50
|
+
@ApiResponse({ status: HttpStatus.OK, schema: PaginatedResponseSchema(ProductDto) })
|
|
51
|
+
@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' })
|
|
52
|
+
override async findAll(@Query() pagination: PaginationQueryDto): Promise<PaginatedResponseDto<ProductEntity>> {
|
|
53
|
+
return super.findAll(pagination);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Get('by-sku/:sku')
|
|
57
|
+
@ApiOperation({ summary: 'Get a product by SKU' })
|
|
58
|
+
@ApiParam({ name: 'sku', example: 'KB-WL-MEC-001' })
|
|
59
|
+
@ApiResponse({ status: HttpStatus.OK, schema: ApiResponseSchema(ProductDto) })
|
|
60
|
+
@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Product not found' })
|
|
61
|
+
async findBySku(@Param('sku') sku: string): Promise<ApiResponseDto<ProductEntity>> {
|
|
62
|
+
const data = await this.productsService.findBySku(sku);
|
|
63
|
+
return { success: true, data, meta: { correlationId: '', timestamp: new Date().toISOString() } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Get(':id')
|
|
67
|
+
@ApiOperation({ summary: 'Get a product by ID' })
|
|
68
|
+
@ApiParam({ name: 'id', format: 'uuid', example: '123e4567-e89b-12d3-a456-426614174000' })
|
|
69
|
+
@ApiResponse({ status: HttpStatus.OK, schema: ApiResponseSchema(ProductDto) })
|
|
70
|
+
@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Product not found' })
|
|
71
|
+
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid UUID' })
|
|
72
|
+
override async findOne(
|
|
73
|
+
@Param('id', new ParseUUIDPipe({ version: '4', errorHttpStatusCode: HttpStatus.BAD_REQUEST }))
|
|
74
|
+
id: string,
|
|
75
|
+
): Promise<ApiResponseDto<ProductEntity>> {
|
|
76
|
+
return super.findOne(id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
import { ProductsController } from './products.controller';
|
|
4
|
+
import { ProductsService } from './products.service';
|
|
5
|
+
import { Product } from './entities/product.entity';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Products Module (TypeORM)
|
|
9
|
+
*
|
|
10
|
+
* Registers Product entity with TypeORM and wires the controller & service.
|
|
11
|
+
* The Product entity must also be added to the DatabaseModule's entities array.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // In app.module.ts:
|
|
15
|
+
* import { ProductsModule } from './modules/products/products.module';
|
|
16
|
+
* @Module({ imports: [ProductsModule] })
|
|
17
|
+
* export class AppModule {}
|
|
18
|
+
*/
|
|
19
|
+
@Module({
|
|
20
|
+
imports: [TypeOrmModule.forFeature([Product])],
|
|
21
|
+
controllers: [ProductsController],
|
|
22
|
+
providers: [ProductsService],
|
|
23
|
+
exports: [ProductsService],
|
|
24
|
+
})
|
|
25
|
+
export class ProductsModule {}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
2
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { Repository, IsNull } from 'typeorm';
|
|
4
|
+
import { BaseService, IBaseRepository, PaginationQueryDto } from '../../common/base';
|
|
5
|
+
import { CreateProductDto } from './dto/create-product.dto';
|
|
6
|
+
import { UpdateProductDto } from './dto/update-product.dto';
|
|
7
|
+
import { Product } from './entities/product.entity';
|
|
8
|
+
|
|
9
|
+
/** Uniform entity alias used by the shared ProductsController */
|
|
10
|
+
export type ProductEntity = Product;
|
|
11
|
+
|
|
12
|
+
// ─────────────────────────────────────────────────────────────
|
|
13
|
+
// TypeORM Product Repository
|
|
14
|
+
//
|
|
15
|
+
// Implements IBaseRepository using TypeORM's Repository<Product>.
|
|
16
|
+
// Soft-delete via deletedAt column (TypeORM also supports @DeleteDateColumn).
|
|
17
|
+
// ─────────────────────────────────────────────────────────────
|
|
18
|
+
class TypeOrmProductRepository
|
|
19
|
+
implements IBaseRepository<Product, CreateProductDto, UpdateProductDto>
|
|
20
|
+
{
|
|
21
|
+
constructor(private readonly repo: Repository<Product>) {}
|
|
22
|
+
|
|
23
|
+
async create(dto: CreateProductDto): Promise<Product> {
|
|
24
|
+
const product = this.repo.create(dto);
|
|
25
|
+
return this.repo.save(product);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async findAll(
|
|
29
|
+
pagination: PaginationQueryDto,
|
|
30
|
+
): Promise<{ data: Product[]; total: number }> {
|
|
31
|
+
const { page = 1, limit = 10 } = pagination;
|
|
32
|
+
const skip = (page - 1) * limit;
|
|
33
|
+
|
|
34
|
+
const [data, total] = await this.repo.findAndCount({
|
|
35
|
+
where: { deletedAt: IsNull() },
|
|
36
|
+
skip,
|
|
37
|
+
take: limit,
|
|
38
|
+
order: { createdAt: 'DESC' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { data, total };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async findOne(id: string): Promise<Product | null> {
|
|
45
|
+
return this.repo.findOne({
|
|
46
|
+
where: { id, deletedAt: IsNull() },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async update(id: string, dto: UpdateProductDto): Promise<Product> {
|
|
51
|
+
await this.repo.update(id, dto as Partial<Product>);
|
|
52
|
+
return this.repo.findOneOrFail({ where: { id } });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async remove(id: string): Promise<Product> {
|
|
56
|
+
const product = await this.repo.findOneOrFail({ where: { id } });
|
|
57
|
+
product.deletedAt = new Date();
|
|
58
|
+
return this.repo.save(product);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─────────────────────────────────────────────────────────────
|
|
63
|
+
// Products Service (TypeORM)
|
|
64
|
+
// ─────────────────────────────────────────────────────────────
|
|
65
|
+
@Injectable()
|
|
66
|
+
export class ProductsService extends BaseService<
|
|
67
|
+
Product,
|
|
68
|
+
CreateProductDto,
|
|
69
|
+
UpdateProductDto
|
|
70
|
+
> {
|
|
71
|
+
private readonly repository: TypeOrmProductRepository;
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
@InjectRepository(Product)
|
|
75
|
+
private readonly productRepo: Repository<Product>,
|
|
76
|
+
) {
|
|
77
|
+
super();
|
|
78
|
+
this.repository = new TypeOrmProductRepository(this.productRepo);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected getRepository(): IBaseRepository<Product, CreateProductDto, UpdateProductDto> {
|
|
82
|
+
return this.repository;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Find by SKU — domain-specific method */
|
|
86
|
+
async findBySku(sku: string): Promise<Product> {
|
|
87
|
+
const product = await this.productRepo.findOne({
|
|
88
|
+
where: { sku, deletedAt: IsNull() },
|
|
89
|
+
});
|
|
90
|
+
if (!product) {
|
|
91
|
+
throw new NotFoundException(`Product with SKU "${sku}" was not found`);
|
|
92
|
+
}
|
|
93
|
+
return product;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Adjust stock delta */
|
|
97
|
+
async adjustStock(id: string, delta: number): Promise<Product> {
|
|
98
|
+
await this.findOne(id);
|
|
99
|
+
await this.productRepo.increment({ id }, 'stock', delta);
|
|
100
|
+
return this.productRepo.findOneOrFail({ where: { id } });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Database Configuration
|
|
2
|
+
DATABASE_URL=mongodb://user:password@localhost:27017/database_name
|
|
3
|
+
|
|
4
|
+
# JWT Configuration
|
|
5
|
+
JWT_ACCESS_SECRET=your_jwt_access_secret_key_here_minimum_32_characters
|
|
6
|
+
JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_minimum_32_characters
|
|
7
|
+
# Token expiration (format: {number}{unit} where unit = s|m|h|d)
|
|
8
|
+
JWT_ACCESS_EXPIRY=60m
|
|
9
|
+
JWT_REFRESH_EXPIRY=30d
|
|
10
|
+
|
|
11
|
+
# Server Configuration
|
|
12
|
+
PORT=8080
|
|
13
|
+
NODE_ENV=development
|
|
14
|
+
|
|
15
|
+
# CORS Configuration
|
|
16
|
+
# Single origin: http://localhost:3000
|
|
17
|
+
# Multiple origins: http://localhost:3000,https://app.example.com
|
|
18
|
+
# Wildcard (use with caution): *
|
|
19
|
+
CORS_ORIGIN=http://localhost:3000
|
|
20
|
+
|
|
21
|
+
# Logging
|
|
22
|
+
LOG_LEVEL=info
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Database Configuration
|
|
2
|
+
DATABASE_URL=mysql://user:password@localhost:3306/database_name
|
|
3
|
+
# DB_TYPE is used by TypeORM (postgres, mysql, sqlite)
|
|
4
|
+
DB_TYPE=mysql
|
|
5
|
+
|
|
6
|
+
# JWT Configuration
|
|
7
|
+
JWT_ACCESS_SECRET=your_jwt_access_secret_key_here_minimum_32_characters
|
|
8
|
+
JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_minimum_32_characters
|
|
9
|
+
# Token expiration (format: {number}{unit} where unit = s|m|h|d)
|
|
10
|
+
JWT_ACCESS_EXPIRY=60m
|
|
11
|
+
JWT_REFRESH_EXPIRY=30d
|
|
12
|
+
|
|
13
|
+
# Server Configuration
|
|
14
|
+
PORT=8080
|
|
15
|
+
NODE_ENV=development
|
|
16
|
+
|
|
17
|
+
# CORS Configuration
|
|
18
|
+
# Single origin: http://localhost:3000
|
|
19
|
+
# Multiple origins: http://localhost:3000,https://app.example.com
|
|
20
|
+
# Wildcard (use with caution): *
|
|
21
|
+
CORS_ORIGIN=http://localhost:3000
|
|
22
|
+
|
|
23
|
+
# Logging
|
|
24
|
+
LOG_LEVEL=info
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config } from 'drizzle-kit';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
schema: './src/database/schema.ts',
|
|
8
|
+
out: './drizzle',
|
|
9
|
+
dialect: 'mysql',
|
|
10
|
+
dbCredentials: {
|
|
11
|
+
url: process.env.DATABASE_URL!,
|
|
12
|
+
},
|
|
13
|
+
} satisfies Config;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// This is your Prisma schema file for MySQL
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "mysql"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Enums
|
|
14
|
+
enum UserRole {
|
|
15
|
+
USER
|
|
16
|
+
ADMIN
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Tables
|
|
20
|
+
model User {
|
|
21
|
+
id String @id @default(uuid())
|
|
22
|
+
fullName String @db.VarChar(100)
|
|
23
|
+
email String @unique @db.VarChar(100)
|
|
24
|
+
passwordHash String @db.Text
|
|
25
|
+
role UserRole @default(USER)
|
|
26
|
+
|
|
27
|
+
refreshToken String? @db.Text // Legacy field - kept for backward compatibility
|
|
28
|
+
isActive Boolean @default(true) // for soft delete
|
|
29
|
+
|
|
30
|
+
// Relations
|
|
31
|
+
refreshTokens RefreshToken[]
|
|
32
|
+
|
|
33
|
+
createdAt DateTime @default(now())
|
|
34
|
+
updatedAt DateTime @updatedAt
|
|
35
|
+
|
|
36
|
+
@@map("users")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
model RefreshToken {
|
|
40
|
+
id String @id @default(uuid())
|
|
41
|
+
token String @db.Text
|
|
42
|
+
userId String
|
|
43
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
44
|
+
|
|
45
|
+
deviceInfo String? @db.VarChar(255)
|
|
46
|
+
ipAddress String? @db.VarChar(45)
|
|
47
|
+
|
|
48
|
+
expiresAt DateTime
|
|
49
|
+
createdAt DateTime @default(now())
|
|
50
|
+
updatedAt DateTime @updatedAt
|
|
51
|
+
|
|
52
|
+
@@index([userId])
|
|
53
|
+
@@index([token(length: 255)])
|
|
54
|
+
@@map("refresh_tokens")
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/mysql2';
|
|
2
|
+
import mysql from 'mysql2/promise';
|
|
3
|
+
import * as schema from './schema';
|
|
4
|
+
|
|
5
|
+
// Create MySQL connection pool
|
|
6
|
+
const pool = mysql.createPool({
|
|
7
|
+
uri: process.env.DATABASE_URL,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Create drizzle instance with schema
|
|
11
|
+
export const db = drizzle(pool, { schema, mode: 'default' });
|
|
12
|
+
|
|
13
|
+
export type DrizzleDB = typeof db;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mysqlTable,
|
|
3
|
+
varchar,
|
|
4
|
+
text,
|
|
5
|
+
timestamp,
|
|
6
|
+
boolean,
|
|
7
|
+
mysqlEnum,
|
|
8
|
+
} from 'drizzle-orm/mysql-core';
|
|
9
|
+
import { relations } from 'drizzle-orm';
|
|
10
|
+
|
|
11
|
+
// Role enum
|
|
12
|
+
export const roleEnum = mysqlEnum('role', ['USER', 'ADMIN']);
|
|
13
|
+
|
|
14
|
+
// Users table
|
|
15
|
+
export const users = mysqlTable('users', {
|
|
16
|
+
id: varchar('id', { length: 36 }).primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
17
|
+
name: varchar('name', { length: 255 }).notNull(),
|
|
18
|
+
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
19
|
+
password: text('password').notNull(),
|
|
20
|
+
role: roleEnum.default('USER').notNull(),
|
|
21
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
22
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull().$onUpdate(() => new Date()),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Refresh tokens table
|
|
26
|
+
export const refreshTokens = mysqlTable('refresh_tokens', {
|
|
27
|
+
id: varchar('id', { length: 36 }).primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
28
|
+
token: text('token').notNull(),
|
|
29
|
+
userId: varchar('user_id', { length: 36 })
|
|
30
|
+
.notNull()
|
|
31
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
32
|
+
userAgent: varchar('user_agent', { length: 500 }).notNull(),
|
|
33
|
+
ipAddress: varchar('ip_address', { length: 45 }).notNull(),
|
|
34
|
+
expiresAt: timestamp('expires_at').notNull(),
|
|
35
|
+
isRevoked: boolean('is_revoked').default(false).notNull(),
|
|
36
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
37
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull().$onUpdate(() => new Date()),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Relations
|
|
41
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
42
|
+
refreshTokens: many(refreshTokens),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
export const refreshTokensRelations = relations(refreshTokens, ({ one }) => ({
|
|
46
|
+
user: one(users, {
|
|
47
|
+
fields: [refreshTokens.userId],
|
|
48
|
+
references: [users.id],
|
|
49
|
+
}),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
// Types
|
|
53
|
+
export type User = typeof users.$inferSelect;
|
|
54
|
+
export type NewUser = typeof users.$inferInsert;
|
|
55
|
+
export type RefreshToken = typeof refreshTokens.$inferSelect;
|
|
56
|
+
export type NewRefreshToken = typeof refreshTokens.$inferInsert;
|
|
57
|
+
|
|
58
|
+
export type Role = 'USER' | 'ADMIN';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Database Configuration
|
|
2
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/database_name
|
|
3
|
+
# DB_TYPE is used by TypeORM (postgres, mysql, sqlite)
|
|
4
|
+
DB_TYPE=postgres
|
|
5
|
+
|
|
6
|
+
# JWT Configuration
|
|
7
|
+
JWT_ACCESS_SECRET=your_jwt_access_secret_key_here_minimum_32_characters
|
|
8
|
+
JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_minimum_32_characters
|
|
9
|
+
# Token expiration (format: {number}{unit} where unit = s|m|h|d)
|
|
10
|
+
JWT_ACCESS_EXPIRY=60m
|
|
11
|
+
JWT_REFRESH_EXPIRY=30d
|
|
12
|
+
|
|
13
|
+
# Server Configuration
|
|
14
|
+
PORT=8080
|
|
15
|
+
NODE_ENV=development
|
|
16
|
+
|
|
17
|
+
# CORS Configuration
|
|
18
|
+
# Single origin: http://localhost:3000
|
|
19
|
+
# Multiple origins: http://localhost:3000,https://app.example.com
|
|
20
|
+
# Wildcard (use with caution): *
|
|
21
|
+
CORS_ORIGIN=http://localhost:3000
|
|
22
|
+
|
|
23
|
+
# Logging
|
|
24
|
+
LOG_LEVEL=info
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config } from 'drizzle-kit';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
schema: './src/database/schema.ts',
|
|
8
|
+
out: './drizzle',
|
|
9
|
+
dialect: 'postgresql',
|
|
10
|
+
dbCredentials: {
|
|
11
|
+
url: process.env.DATABASE_URL!,
|
|
12
|
+
},
|
|
13
|
+
} satisfies Config;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// This is your Prisma schema file for PostgreSQL
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "postgresql"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Enums
|
|
14
|
+
enum UserRole {
|
|
15
|
+
USER
|
|
16
|
+
ADMIN
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Tables
|
|
20
|
+
model User {
|
|
21
|
+
id String @id @default(uuid())
|
|
22
|
+
fullName String @db.VarChar(100)
|
|
23
|
+
email String @unique @db.VarChar(100)
|
|
24
|
+
passwordHash String @db.Text
|
|
25
|
+
role UserRole @default(USER)
|
|
26
|
+
|
|
27
|
+
refreshToken String? @db.Text // Legacy field - kept for backward compatibility
|
|
28
|
+
isActive Boolean @default(true) // for soft delete
|
|
29
|
+
|
|
30
|
+
// Relations
|
|
31
|
+
refreshTokens RefreshToken[]
|
|
32
|
+
|
|
33
|
+
createdAt DateTime @default(now())
|
|
34
|
+
updatedAt DateTime @updatedAt
|
|
35
|
+
|
|
36
|
+
@@map("users")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
model RefreshToken {
|
|
40
|
+
id String @id @default(uuid())
|
|
41
|
+
token String @db.Text
|
|
42
|
+
userId String
|
|
43
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
44
|
+
|
|
45
|
+
deviceInfo String? @db.VarChar(255)
|
|
46
|
+
ipAddress String? @db.VarChar(45)
|
|
47
|
+
|
|
48
|
+
expiresAt DateTime
|
|
49
|
+
createdAt DateTime @default(now())
|
|
50
|
+
updatedAt DateTime @updatedAt
|
|
51
|
+
|
|
52
|
+
@@index([userId])
|
|
53
|
+
@@index([token])
|
|
54
|
+
@@map("refresh_tokens")
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Database Configuration
|
|
2
|
+
DATABASE_URL=file:./dev.db
|
|
3
|
+
# DB_TYPE is used by TypeORM (postgres, mysql, sqlite)
|
|
4
|
+
DB_TYPE=sqlite
|
|
5
|
+
|
|
6
|
+
# JWT Configuration
|
|
7
|
+
JWT_ACCESS_SECRET=your_jwt_access_secret_key_here_minimum_32_characters
|
|
8
|
+
JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_minimum_32_characters
|
|
9
|
+
# Token expiration (format: {number}{unit} where unit = s|m|h|d)
|
|
10
|
+
JWT_ACCESS_EXPIRY=60m
|
|
11
|
+
JWT_REFRESH_EXPIRY=30d
|
|
12
|
+
|
|
13
|
+
# Server Configuration
|
|
14
|
+
PORT=8080
|
|
15
|
+
NODE_ENV=development
|
|
16
|
+
|
|
17
|
+
# CORS Configuration
|
|
18
|
+
# Single origin: http://localhost:3000
|
|
19
|
+
# Multiple origins: http://localhost:3000,https://app.example.com
|
|
20
|
+
# Wildcard (use with caution): *
|
|
21
|
+
CORS_ORIGIN=http://localhost:3000
|
|
22
|
+
|
|
23
|
+
# Logging
|
|
24
|
+
LOG_LEVEL=info
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config } from 'drizzle-kit';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
schema: './src/database/schema.ts',
|
|
8
|
+
out: './drizzle',
|
|
9
|
+
dialect: 'sqlite',
|
|
10
|
+
dbCredentials: {
|
|
11
|
+
url: process.env.DATABASE_URL!,
|
|
12
|
+
},
|
|
13
|
+
} satisfies Config;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// This is your Prisma schema file for SQLite
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "sqlite"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Tables (SQLite doesn't support enums, using String instead)
|
|
14
|
+
model User {
|
|
15
|
+
id String @id @default(uuid())
|
|
16
|
+
fullName String
|
|
17
|
+
email String @unique
|
|
18
|
+
passwordHash String
|
|
19
|
+
role String @default("USER") // USER or ADMIN
|
|
20
|
+
|
|
21
|
+
refreshToken String?
|
|
22
|
+
isActive Boolean @default(true)
|
|
23
|
+
|
|
24
|
+
// Relations
|
|
25
|
+
refreshTokens RefreshToken[]
|
|
26
|
+
|
|
27
|
+
createdAt DateTime @default(now())
|
|
28
|
+
updatedAt DateTime @updatedAt
|
|
29
|
+
|
|
30
|
+
@@map("users")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
model RefreshToken {
|
|
34
|
+
id String @id @default(uuid())
|
|
35
|
+
token String
|
|
36
|
+
userId String
|
|
37
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
38
|
+
|
|
39
|
+
deviceInfo String?
|
|
40
|
+
ipAddress String?
|
|
41
|
+
|
|
42
|
+
expiresAt DateTime
|
|
43
|
+
createdAt DateTime @default(now())
|
|
44
|
+
updatedAt DateTime @updatedAt
|
|
45
|
+
|
|
46
|
+
@@index([userId])
|
|
47
|
+
@@map("refresh_tokens")
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import * as schema from './schema';
|
|
4
|
+
|
|
5
|
+
// Create SQLite database connection
|
|
6
|
+
const sqlite = new Database(process.env.DATABASE_URL?.replace('file:', '') || './dev.db');
|
|
7
|
+
|
|
8
|
+
// Create drizzle instance with schema
|
|
9
|
+
export const db = drizzle(sqlite, { schema });
|
|
10
|
+
|
|
11
|
+
export type DrizzleDB = typeof db;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sqliteTable,
|
|
3
|
+
text,
|
|
4
|
+
integer,
|
|
5
|
+
} from 'drizzle-orm/sqlite-core';
|
|
6
|
+
import { relations, sql } from 'drizzle-orm';
|
|
7
|
+
|
|
8
|
+
// Users table (SQLite doesn't support enums, using text)
|
|
9
|
+
export const users = sqliteTable('users', {
|
|
10
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
11
|
+
name: text('name').notNull(),
|
|
12
|
+
email: text('email').notNull().unique(),
|
|
13
|
+
password: text('password').notNull(),
|
|
14
|
+
role: text('role', { enum: ['USER', 'ADMIN'] }).default('USER').notNull(),
|
|
15
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`).notNull(),
|
|
16
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' }).default(sql`(unixepoch())`).notNull(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Refresh tokens table
|
|
20
|
+
export const refreshTokens = sqliteTable('refresh_tokens', {
|
|
21
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
22
|
+
token: text('token').notNull(),
|
|
23
|
+
userId: text('user_id')
|
|
24
|
+
.notNull()
|
|
25
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
26
|
+
userAgent: text('user_agent').notNull(),
|
|
27
|
+
ipAddress: text('ip_address').notNull(),
|
|
28
|
+
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
|
|
29
|
+
isRevoked: integer('is_revoked', { mode: 'boolean' }).default(false).notNull(),
|
|
30
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`).notNull(),
|
|
31
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' }).default(sql`(unixepoch())`).notNull(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Relations
|
|
35
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
36
|
+
refreshTokens: many(refreshTokens),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
export const refreshTokensRelations = relations(refreshTokens, ({ one }) => ({
|
|
40
|
+
user: one(users, {
|
|
41
|
+
fields: [refreshTokens.userId],
|
|
42
|
+
references: [users.id],
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Types
|
|
47
|
+
export type User = typeof users.$inferSelect;
|
|
48
|
+
export type NewUser = typeof users.$inferInsert;
|
|
49
|
+
export type RefreshToken = typeof refreshTokens.$inferSelect;
|
|
50
|
+
export type NewRefreshToken = typeof refreshTokens.$inferInsert;
|
|
51
|
+
|
|
52
|
+
export type Role = 'USER' | 'ADMIN';
|