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.
Files changed (159) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +21 -0
  3. package/README.md +620 -0
  4. package/bin/cli.js +224 -0
  5. package/index.js +12 -0
  6. package/package.json +74 -0
  7. package/src/constants.js +65 -0
  8. package/src/generator.js +271 -0
  9. package/src/index.js +13 -0
  10. package/src/moduleGenerator.js +586 -0
  11. package/src/postSetup.js +365 -0
  12. package/src/prompts.js +189 -0
  13. package/src/utils.js +112 -0
  14. package/templates/README.md +81 -0
  15. package/templates/base/eslint.config.mjs +34 -0
  16. package/templates/base/gitignore +78 -0
  17. package/templates/base/nest-cli.json +8 -0
  18. package/templates/base/src/common/constants/cookie.config.ts +20 -0
  19. package/templates/base/src/common/decorators/get-user.decorator.ts +13 -0
  20. package/templates/base/src/common/decorators/public.decorator.ts +4 -0
  21. package/templates/base/src/common/decorators/roles.decorator.ts +4 -0
  22. package/templates/base/src/common/dtos/pagination.dto.ts +29 -0
  23. package/templates/base/src/common/filters/http-exception.filter.ts +108 -0
  24. package/templates/base/src/common/guards/auth.guard.ts +51 -0
  25. package/templates/base/src/common/guards/refresh-token.guard.ts +39 -0
  26. package/templates/base/src/common/guards/roles.guard.ts +45 -0
  27. package/templates/base/src/common/interceptors/response.interceptor.ts +55 -0
  28. package/templates/base/src/common/interfaces/api-response.interface.ts +26 -0
  29. package/templates/base/src/common/middleware/correlation-id.middleware.ts +20 -0
  30. package/templates/base/src/common/validators/password.validator.ts +37 -0
  31. package/templates/base/src/config/config.module.ts +14 -0
  32. package/templates/base/src/config/logger.config.ts +109 -0
  33. package/templates/base/src/main.ts +84 -0
  34. package/templates/base/src/modules/auth/auth.controller.ts +133 -0
  35. package/templates/base/src/modules/auth/dtos/login.dto.ts +11 -0
  36. package/templates/base/src/modules/auth/dtos/signup.dto.ts +8 -0
  37. package/templates/base/src/modules/health/health.controller.ts +20 -0
  38. package/templates/base/src/modules/health/health.module.ts +7 -0
  39. package/templates/base/src/modules/users/dtos/update-profile.dto.ts +12 -0
  40. package/templates/base/src/modules/users/dtos/update-user.dto.ts +13 -0
  41. package/templates/base/src/modules/users/users.controller.ts +65 -0
  42. package/templates/base/test/app.e2e-spec.ts +24 -0
  43. package/templates/base/test/jest-e2e.json +9 -0
  44. package/templates/base/tsconfig.build.json +4 -0
  45. package/templates/base/tsconfig.json +24 -0
  46. package/templates/base-crud/CRUD_README.md +385 -0
  47. package/templates/base-crud/src/common/base/base.controller.ts +321 -0
  48. package/templates/base-crud/src/common/base/base.service.ts +192 -0
  49. package/templates/base-crud/src/common/base/index.ts +20 -0
  50. package/templates/base-crud/src/common/base/swagger/api-response.dto.ts +82 -0
  51. package/templates/base-crud/src/common/base/swagger/paginated.dto.ts +102 -0
  52. package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +44 -0
  53. package/templates/base-crud/src/modules/products/dto/product.dto.ts +38 -0
  54. package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +12 -0
  55. package/templates/base-crud/src/modules/products/products.controller.ts +94 -0
  56. package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +79 -0
  57. package/templates/base-crud-drizzle/src/modules/products/products.module.ts +24 -0
  58. package/templates/base-crud-drizzle/src/modules/products/products.service.ts +140 -0
  59. package/templates/base-crud-drizzle/src/modules/products/schema/products.schema.ts +39 -0
  60. package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +79 -0
  61. package/templates/base-crud-mongoose/src/modules/products/products.module.ts +26 -0
  62. package/templates/base-crud-mongoose/src/modules/products/products.service.ts +133 -0
  63. package/templates/base-crud-mongoose/src/modules/products/schemas/product.schema.ts +67 -0
  64. package/templates/base-crud-prisma/src/modules/products/products.module.ts +12 -0
  65. package/templates/base-crud-prisma/src/modules/products/products.service.ts +100 -0
  66. package/templates/base-crud-typeorm/src/modules/products/entities/product.entity.ts +58 -0
  67. package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +79 -0
  68. package/templates/base-crud-typeorm/src/modules/products/products.module.ts +25 -0
  69. package/templates/base-crud-typeorm/src/modules/products/products.service.ts +102 -0
  70. package/templates/database/mongodb/.env.example +22 -0
  71. package/templates/database/mysql/.env.example +24 -0
  72. package/templates/database/mysql/drizzle.config.ts +13 -0
  73. package/templates/database/mysql/package.json +5 -0
  74. package/templates/database/mysql/prisma/schema.prisma +55 -0
  75. package/templates/database/mysql/src/database/drizzle.ts +13 -0
  76. package/templates/database/mysql/src/database/schema.ts +58 -0
  77. package/templates/database/postgres/.env.example +24 -0
  78. package/templates/database/postgres/drizzle.config.ts +13 -0
  79. package/templates/database/postgres/package.json +8 -0
  80. package/templates/database/postgres/prisma/schema.prisma +55 -0
  81. package/templates/database/sqlite/.env.example +24 -0
  82. package/templates/database/sqlite/drizzle.config.ts +13 -0
  83. package/templates/database/sqlite/package.json +8 -0
  84. package/templates/database/sqlite/prisma/schema.prisma +48 -0
  85. package/templates/database/sqlite/src/database/drizzle.ts +11 -0
  86. package/templates/database/sqlite/src/database/schema.ts +52 -0
  87. package/templates/orm/drizzle/drizzle.config.ts +13 -0
  88. package/templates/orm/drizzle/package.json +90 -0
  89. package/templates/orm/drizzle/src/app.module.ts +73 -0
  90. package/templates/orm/drizzle/src/config/env.validation.ts +55 -0
  91. package/templates/orm/drizzle/src/database/database.module.ts +25 -0
  92. package/templates/orm/drizzle/src/database/drizzle.ts +13 -0
  93. package/templates/orm/drizzle/src/database/schema.ts +60 -0
  94. package/templates/orm/drizzle/src/database/seed.ts +87 -0
  95. package/templates/orm/drizzle/src/modules/auth/auth.module.ts +12 -0
  96. package/templates/orm/drizzle/src/modules/auth/auth.service.ts +298 -0
  97. package/templates/orm/drizzle/src/modules/health/health.controller.ts +34 -0
  98. package/templates/orm/drizzle/src/modules/health/health.module.ts +7 -0
  99. package/templates/orm/drizzle/src/modules/users/users.module.ts +10 -0
  100. package/templates/orm/drizzle/src/modules/users/users.service.ts +152 -0
  101. package/templates/orm/mongoose/.env.example +22 -0
  102. package/templates/orm/mongoose/package.json +86 -0
  103. package/templates/orm/mongoose/src/app.module.ts +73 -0
  104. package/templates/orm/mongoose/src/config/env.validation.ts +55 -0
  105. package/templates/orm/mongoose/src/database/database.module.ts +19 -0
  106. package/templates/orm/mongoose/src/database/seed.ts +107 -0
  107. package/templates/orm/mongoose/src/modules/auth/auth.module.ts +21 -0
  108. package/templates/orm/mongoose/src/modules/auth/auth.service.ts +272 -0
  109. package/templates/orm/mongoose/src/modules/auth/dtos/login.dto.ts +10 -0
  110. package/templates/orm/mongoose/src/modules/auth/dtos/signup.dto.ts +8 -0
  111. package/templates/orm/mongoose/src/modules/health/health.controller.ts +26 -0
  112. package/templates/orm/mongoose/src/modules/health/health.module.ts +7 -0
  113. package/templates/orm/mongoose/src/modules/users/dtos/update-profile.dto.ts +24 -0
  114. package/templates/orm/mongoose/src/modules/users/dtos/update-user.dto.ts +13 -0
  115. package/templates/orm/mongoose/src/modules/users/users.module.ts +19 -0
  116. package/templates/orm/mongoose/src/modules/users/users.service.ts +179 -0
  117. package/templates/orm/mongoose/src/schemas/index.ts +2 -0
  118. package/templates/orm/mongoose/src/schemas/refresh-token.schema.ts +55 -0
  119. package/templates/orm/mongoose/src/schemas/user.schema.ts +53 -0
  120. package/templates/orm/prisma/package.json +102 -0
  121. package/templates/orm/prisma/prisma/schema.prisma +60 -0
  122. package/templates/orm/prisma/prisma/seed.ts +69 -0
  123. package/templates/orm/prisma/src/app.module.ts +55 -0
  124. package/templates/orm/prisma/src/config/env.validation.ts +56 -0
  125. package/templates/orm/prisma/src/modules/auth/auth.module.ts +17 -0
  126. package/templates/orm/prisma/src/modules/auth/auth.service.ts +308 -0
  127. package/templates/orm/prisma/src/modules/health/health.controller.ts +26 -0
  128. package/templates/orm/prisma/src/modules/health/health.module.ts +10 -0
  129. package/templates/orm/prisma/src/modules/users/users.module.ts +11 -0
  130. package/templates/orm/prisma/src/modules/users/users.service.ts +105 -0
  131. package/templates/orm/prisma/src/prisma/prisma.module.ts +9 -0
  132. package/templates/orm/prisma/src/prisma/prisma.service.ts +16 -0
  133. package/templates/orm/typeorm/package.json +100 -0
  134. package/templates/orm/typeorm/src/app.module.ts +55 -0
  135. package/templates/orm/typeorm/src/config/env.validation.ts +58 -0
  136. package/templates/orm/typeorm/src/database/data-source.ts +19 -0
  137. package/templates/orm/typeorm/src/database/database.module.ts +27 -0
  138. package/templates/orm/typeorm/src/database/seed.ts +76 -0
  139. package/templates/orm/typeorm/src/entities/index.ts +2 -0
  140. package/templates/orm/typeorm/src/entities/refresh-token.entity.ts +45 -0
  141. package/templates/orm/typeorm/src/entities/user.entity.ts +51 -0
  142. package/templates/orm/typeorm/src/modules/auth/auth.module.ts +19 -0
  143. package/templates/orm/typeorm/src/modules/auth/auth.service.ts +286 -0
  144. package/templates/orm/typeorm/src/modules/health/health.controller.ts +24 -0
  145. package/templates/orm/typeorm/src/modules/health/health.module.ts +9 -0
  146. package/templates/orm/typeorm/src/modules/users/users.module.ts +13 -0
  147. package/templates/orm/typeorm/src/modules/users/users.service.ts +108 -0
  148. package/templates/swagger/src/common/dtos/pagination.dto.ts +43 -0
  149. package/templates/swagger/src/main.ts +116 -0
  150. package/templates/swagger/src/modules/auth/auth.controller.ts +235 -0
  151. package/templates/swagger/src/modules/auth/dtos/login.dto.ts +20 -0
  152. package/templates/swagger/src/modules/auth/dtos/signup.dto.ts +14 -0
  153. package/templates/swagger/src/modules/users/dtos/update-profile.dto.ts +19 -0
  154. package/templates/swagger/src/modules/users/dtos/update-user.dto.ts +23 -0
  155. package/templates/swagger/src/modules/users/users.controller.ts +214 -0
  156. package/templates/swagger-mongoose/src/modules/auth/dtos/login.dto.ts +20 -0
  157. package/templates/swagger-mongoose/src/modules/auth/dtos/signup.dto.ts +14 -0
  158. package/templates/swagger-mongoose/src/modules/users/dtos/update-profile.dto.ts +40 -0
  159. 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,5 @@
1
+ {
2
+ "dependencies": {
3
+ "mysql2": "^3.9.1"
4
+ }
5
+ }
@@ -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,8 @@
1
+ {
2
+ "dependencies": {
3
+ "pg": "^8.13.0"
4
+ },
5
+ "devDependencies": {
6
+ "@types/pg": "^8.11.10"
7
+ }
8
+ }
@@ -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,8 @@
1
+ {
2
+ "dependencies": {
3
+ "better-sqlite3": "^9.4.3"
4
+ },
5
+ "devDependencies": {
6
+ "@types/better-sqlite3": "^7.6.8"
7
+ }
8
+ }
@@ -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';