speedrun-cli 2.6.12 → 2.7.0

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 (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +1 -1
  3. package/src/generator.js +1 -16
  4. package/src/moduleGenerator.js +597 -121
  5. package/src/postSetup.js +423 -402
  6. package/src/prompts.js +25 -3
  7. package/templates/orm/drizzle/package.json +2 -1
  8. package/templates/orm/mongoose/package.json +2 -1
  9. package/templates/orm/prisma/package.json +1 -0
  10. package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +0 -34
  11. package/templates/base-crud/src/modules/products/dto/product.dto.ts +0 -36
  12. package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +0 -4
  13. package/templates/base-crud/src/modules/products/products.controller.ts +0 -78
  14. package/templates/base-crud-drizzle/src/modules/products/dto/create-product.dto.ts +0 -34
  15. package/templates/base-crud-drizzle/src/modules/products/dto/product.dto.ts +0 -36
  16. package/templates/base-crud-drizzle/src/modules/products/dto/update-product.dto.ts +0 -4
  17. package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +0 -78
  18. package/templates/base-crud-drizzle/src/modules/products/products.module.ts +0 -24
  19. package/templates/base-crud-drizzle/src/modules/products/products.service.ts +0 -140
  20. package/templates/base-crud-drizzle/src/modules/products/schema/products.schema.ts +0 -39
  21. package/templates/base-crud-mongoose/src/modules/products/dto/create-product.dto.ts +0 -34
  22. package/templates/base-crud-mongoose/src/modules/products/dto/product.dto.ts +0 -36
  23. package/templates/base-crud-mongoose/src/modules/products/dto/update-product.dto.ts +0 -4
  24. package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +0 -78
  25. package/templates/base-crud-mongoose/src/modules/products/products.module.ts +0 -26
  26. package/templates/base-crud-mongoose/src/modules/products/products.service.ts +0 -133
  27. package/templates/base-crud-mongoose/src/modules/products/schemas/product.schema.ts +0 -67
  28. package/templates/base-crud-prisma/src/modules/products/dto/create-product.dto.ts +0 -34
  29. package/templates/base-crud-prisma/src/modules/products/dto/product.dto.ts +0 -36
  30. package/templates/base-crud-prisma/src/modules/products/dto/update-product.dto.ts +0 -4
  31. package/templates/base-crud-prisma/src/modules/products/products.controller.ts +0 -78
  32. package/templates/base-crud-prisma/src/modules/products/products.module.ts +0 -12
  33. package/templates/base-crud-prisma/src/modules/products/products.service.ts +0 -100
  34. package/templates/base-crud-typeorm/src/modules/products/dto/create-product.dto.ts +0 -34
  35. package/templates/base-crud-typeorm/src/modules/products/dto/product.dto.ts +0 -36
  36. package/templates/base-crud-typeorm/src/modules/products/dto/update-product.dto.ts +0 -4
  37. package/templates/base-crud-typeorm/src/modules/products/entities/product.entity.ts +0 -58
  38. package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +0 -78
  39. package/templates/base-crud-typeorm/src/modules/products/products.module.ts +0 -25
  40. package/templates/base-crud-typeorm/src/modules/products/products.service.ts +0 -102
package/src/prompts.js CHANGED
@@ -101,13 +101,33 @@ async function promptForProjectDetails(providedAppName, options) {
101
101
  }
102
102
 
103
103
  // Base CRUD Architecture prompt
104
- if (!options.baseCrud && !options.yes) {
104
+ if (options.baseCrud === undefined && !options.yes) {
105
105
  questions.push({
106
106
  type: 'confirm',
107
107
  name: 'baseCrud',
108
108
  message:
109
109
  'Enable Base CRUD Architecture? (generates abstract BaseService, BaseController & Swagger helpers in src/common/base)',
110
- default: false,
110
+ default: true,
111
+ });
112
+ }
113
+
114
+ // Generate First CRUD Module prompt
115
+ if (!options.yes) {
116
+ questions.push({
117
+ type: 'confirm',
118
+ name: 'generateFirstCrud',
119
+ message: 'Do you want to generate your first CRUD module now?',
120
+ default: true,
121
+ when: (answers) => (options.baseCrud !== false && (answers.baseCrud !== false)),
122
+ });
123
+
124
+ questions.push({
125
+ type: 'input',
126
+ name: 'firstModuleName',
127
+ message: 'What module do you want to generate? (e.g., orders, products)',
128
+ default: 'orders',
129
+ when: (answers) => answers.generateFirstCrud,
130
+ validate: (input) => (input && input.trim() ? true : 'Module name is required'),
111
131
  });
112
132
  }
113
133
 
@@ -127,7 +147,9 @@ async function promptForProjectDetails(providedAppName, options) {
127
147
  installDependencies: options.skipInstall ? false : answers.installDependencies !== false,
128
148
  initializeGit: options.skipGit ? false : answers.initializeGit !== false,
129
149
  swagger: options.swagger || answers.swagger || false,
130
- baseCrud: options.baseCrud || answers.baseCrud || false,
150
+ baseCrud: options.baseCrud !== undefined ? options.baseCrud : (answers.baseCrud !== false),
151
+ generateFirstCrud: answers.generateFirstCrud || false,
152
+ firstModuleName: answers.firstModuleName || undefined,
131
153
  };
132
154
  }
133
155
 
@@ -22,7 +22,8 @@
22
22
  "db:migrate": "drizzle-kit migrate",
23
23
  "db:push": "drizzle-kit push",
24
24
  "db:studio": "drizzle-kit studio",
25
- "db:seed": "ts-node src/database/seed.ts"
25
+ "db:seed": "ts-node src/database/seed.ts",
26
+ "seed": "ts-node src/database/seed.ts"
26
27
  },
27
28
  "dependencies": {
28
29
  "@nestjs/common": "^11.0.0",
@@ -18,7 +18,8 @@
18
18
  "test:cov": "jest --coverage",
19
19
  "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20
20
  "test:e2e": "jest --config ./test/jest-e2e.json",
21
- "db:seed": "ts-node src/database/seed.ts"
21
+ "db:seed": "ts-node src/database/seed.ts",
22
+ "seed": "ts-node src/database/seed.ts"
22
23
  },
23
24
  "dependencies": {
24
25
  "@nestjs/common": "^11.0.0",
@@ -24,6 +24,7 @@
24
24
  "prisma:migrate:deploy": "prisma migrate deploy",
25
25
  "prisma:migrate:status": "prisma migrate status",
26
26
  "prisma:seed": "ts-node prisma/seed.ts",
27
+ "seed": "ts-node prisma/seed.ts",
27
28
  "prisma:studio": "prisma studio",
28
29
  "prisma:reset": "prisma migrate reset --force"
29
30
  },
@@ -1,34 +0,0 @@
1
- import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
- import { IsString, IsNotEmpty, IsNumber, IsOptional, Min } from 'class-validator';
3
-
4
- export class CreateProductDto {
5
- @ApiProperty({ example: 'Wireless Mouse' })
6
- @IsString()
7
- @IsNotEmpty()
8
- name: string;
9
-
10
- @ApiPropertyOptional({ example: 'Ergonomic wireless mouse' })
11
- @IsString()
12
- @IsOptional()
13
- description?: string;
14
-
15
- @ApiProperty({ example: 'PROD-001' })
16
- @IsString()
17
- @IsNotEmpty()
18
- sku: string;
19
-
20
- @ApiProperty({ example: 29.99 })
21
- @IsNumber()
22
- @Min(0)
23
- price: number;
24
-
25
- @ApiProperty({ example: 100 })
26
- @IsNumber()
27
- @Min(0)
28
- stock: number;
29
-
30
- @ApiPropertyOptional({ example: 'Electronics' })
31
- @IsString()
32
- @IsOptional()
33
- category?: string;
34
- }
@@ -1,36 +0,0 @@
1
- import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
-
3
- export class ProductDto {
4
- @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', format: 'uuid' })
5
- id: string;
6
-
7
- @ApiProperty({ example: 'Wireless Mouse' })
8
- name: string;
9
-
10
- @ApiPropertyOptional({ example: 'Ergonomic wireless mouse' })
11
- description?: string;
12
-
13
- @ApiProperty({ example: 'PROD-001' })
14
- sku: string;
15
-
16
- @ApiProperty({ example: 29.99 })
17
- price: number;
18
-
19
- @ApiProperty({ example: 100 })
20
- stock: number;
21
-
22
- @ApiPropertyOptional({ example: 'Electronics' })
23
- category?: string;
24
-
25
- @ApiProperty({ example: 'ACTIVE' })
26
- status: string;
27
-
28
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
29
- createdAt: Date;
30
-
31
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
32
- updatedAt: Date;
33
-
34
- @ApiPropertyOptional({ nullable: true, example: null })
35
- deletedAt?: Date | null;
36
- }
@@ -1,4 +0,0 @@
1
- import { PartialType } from '@nestjs/swagger';
2
- import { CreateProductDto } from './create-product.dto';
3
-
4
- export class UpdateProductDto extends PartialType(CreateProductDto) {}
@@ -1,78 +0,0 @@
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
- }
@@ -1,34 +0,0 @@
1
- import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
- import { IsString, IsNotEmpty, IsNumber, IsOptional, Min } from 'class-validator';
3
-
4
- export class CreateProductDto {
5
- @ApiProperty({ example: 'Wireless Mouse' })
6
- @IsString()
7
- @IsNotEmpty()
8
- name: string;
9
-
10
- @ApiPropertyOptional({ example: 'Ergonomic wireless mouse' })
11
- @IsString()
12
- @IsOptional()
13
- description?: string;
14
-
15
- @ApiProperty({ example: 'PROD-001' })
16
- @IsString()
17
- @IsNotEmpty()
18
- sku: string;
19
-
20
- @ApiProperty({ example: 29.99 })
21
- @IsNumber()
22
- @Min(0)
23
- price: number;
24
-
25
- @ApiProperty({ example: 100 })
26
- @IsNumber()
27
- @Min(0)
28
- stock: number;
29
-
30
- @ApiPropertyOptional({ example: 'Electronics' })
31
- @IsString()
32
- @IsOptional()
33
- category?: string;
34
- }
@@ -1,36 +0,0 @@
1
- import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
-
3
- export class ProductDto {
4
- @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', format: 'uuid' })
5
- id: string;
6
-
7
- @ApiProperty({ example: 'Wireless Mouse' })
8
- name: string;
9
-
10
- @ApiPropertyOptional({ example: 'Ergonomic wireless mouse' })
11
- description?: string;
12
-
13
- @ApiProperty({ example: 'PROD-001' })
14
- sku: string;
15
-
16
- @ApiProperty({ example: 29.99 })
17
- price: number;
18
-
19
- @ApiProperty({ example: 100 })
20
- stock: number;
21
-
22
- @ApiPropertyOptional({ example: 'Electronics' })
23
- category?: string;
24
-
25
- @ApiProperty({ example: 'ACTIVE' })
26
- status: string;
27
-
28
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
29
- createdAt: Date;
30
-
31
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
32
- updatedAt: Date;
33
-
34
- @ApiPropertyOptional({ nullable: true, example: null })
35
- deletedAt?: Date | null;
36
- }
@@ -1,4 +0,0 @@
1
- import { PartialType } from '@nestjs/swagger';
2
- import { CreateProductDto } from './create-product.dto';
3
-
4
- export class UpdateProductDto extends PartialType(CreateProductDto) {}
@@ -1,78 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { ProductsController } from './products.controller';
3
- import { ProductsService } from './products.service';
4
- import { DatabaseModule } from '../../database/database.module';
5
-
6
- /**
7
- * Products Module (Drizzle)
8
- *
9
- * DatabaseModule is @Global() so DRIZZLE token is available app-wide,
10
- * but we import it here explicitly for documentation clarity.
11
- *
12
- * @example
13
- * // In app.module.ts:
14
- * import { ProductsModule } from './modules/products/products.module';
15
- * @Module({ imports: [ProductsModule] })
16
- * export class AppModule {}
17
- */
18
- @Module({
19
- imports: [DatabaseModule],
20
- controllers: [ProductsController],
21
- providers: [ProductsService],
22
- exports: [ProductsService],
23
- })
24
- export class ProductsModule {}
@@ -1,140 +0,0 @@
1
- import { Injectable, Inject, NotFoundException } from '@nestjs/common';
2
- import { eq, isNull, and, sql } from 'drizzle-orm';
3
- import { NodePgDatabase } from 'drizzle-orm/node-postgres';
4
- import { DRIZZLE } from '../../database/database.module';
5
- import { BaseService, IBaseRepository, PaginationQueryDto } from '../../common/base';
6
- import { CreateProductDto } from './dto/create-product.dto';
7
- import { UpdateProductDto } from './dto/update-product.dto';
8
- import { products, Product } from './schema/products.schema';
9
-
10
- /** Uniform entity alias used by the shared ProductsController */
11
- export type ProductEntity = Product;
12
-
13
- // ─────────────────────────────────────────────────────────────
14
- // Drizzle Product Repository
15
- // ─────────────────────────────────────────────────────────────
16
- class DrizzleProductRepository
17
- implements IBaseRepository<Product, CreateProductDto, UpdateProductDto>
18
- {
19
- constructor(private readonly db: NodePgDatabase) {}
20
-
21
- async create(dto: CreateProductDto): Promise<Product> {
22
- const [product] = await this.db
23
- .insert(products)
24
- .values({
25
- name: dto.name,
26
- description: dto.description,
27
- sku: dto.sku,
28
- price: String(dto.price), // Drizzle numeric → string
29
- stock: dto.stock,
30
- category: dto.category,
31
- status: dto.status as Product['status'],
32
- })
33
- .returning();
34
- return product;
35
- }
36
-
37
- async findAll(
38
- pagination: PaginationQueryDto,
39
- ): Promise<{ data: Product[]; total: number }> {
40
- const { page = 1, limit = 10 } = pagination;
41
- const offset = (page - 1) * limit;
42
-
43
- const [data, [{ count }]] = await Promise.all([
44
- this.db
45
- .select()
46
- .from(products)
47
- .where(isNull(products.deletedAt))
48
- .orderBy(sql`${products.createdAt} DESC`)
49
- .limit(limit)
50
- .offset(offset),
51
- this.db
52
- .select({ count: sql<number>`count(*)::int` })
53
- .from(products)
54
- .where(isNull(products.deletedAt)),
55
- ]);
56
-
57
- return { data, total: count };
58
- }
59
-
60
- async findOne(id: string): Promise<Product | null> {
61
- const [product] = await this.db
62
- .select()
63
- .from(products)
64
- .where(and(eq(products.id, id), isNull(products.deletedAt)));
65
- return product ?? null;
66
- }
67
-
68
- async update(id: string, dto: UpdateProductDto): Promise<Product> {
69
- const updateData: Partial<typeof products.$inferInsert> = {};
70
- if (dto.name !== undefined) updateData.name = dto.name;
71
- if (dto.description !== undefined) updateData.description = dto.description;
72
- if (dto.sku !== undefined) updateData.sku = dto.sku;
73
- if (dto.price !== undefined) updateData.price = String(dto.price);
74
- if (dto.stock !== undefined) updateData.stock = dto.stock;
75
- if (dto.category !== undefined) updateData.category = dto.category;
76
- if (dto.status !== undefined) updateData.status = dto.status as Product['status'];
77
- updateData.updatedAt = new Date();
78
-
79
- const [updated] = await this.db
80
- .update(products)
81
- .set(updateData)
82
- .where(eq(products.id, id))
83
- .returning();
84
- return updated;
85
- }
86
-
87
- async remove(id: string): Promise<Product> {
88
- const [deleted] = await this.db
89
- .update(products)
90
- .set({ deletedAt: new Date() })
91
- .where(eq(products.id, id))
92
- .returning();
93
- return deleted;
94
- }
95
- }
96
-
97
- // ─────────────────────────────────────────────────────────────
98
- // Products Service (Drizzle)
99
- // ─────────────────────────────────────────────────────────────
100
- @Injectable()
101
- export class ProductsService extends BaseService<
102
- Product,
103
- CreateProductDto,
104
- UpdateProductDto
105
- > {
106
- private readonly repository: DrizzleProductRepository;
107
-
108
- constructor(@Inject(DRIZZLE) private readonly db: NodePgDatabase) {
109
- super();
110
- this.repository = new DrizzleProductRepository(this.db);
111
- }
112
-
113
- protected getRepository(): IBaseRepository<Product, CreateProductDto, UpdateProductDto> {
114
- return this.repository;
115
- }
116
-
117
- /** Find by SKU */
118
- async findBySku(sku: string): Promise<Product> {
119
- const [product] = await this.db
120
- .select()
121
- .from(products)
122
- .where(and(eq(products.sku, sku), isNull(products.deletedAt)));
123
-
124
- if (!product) {
125
- throw new NotFoundException(`Product with SKU "${sku}" was not found`);
126
- }
127
- return product;
128
- }
129
-
130
- /** Adjust stock */
131
- async adjustStock(id: string, delta: number): Promise<Product> {
132
- await this.findOne(id);
133
- const [updated] = await this.db
134
- .update(products)
135
- .set({ stock: sql`${products.stock} + ${delta}` })
136
- .where(eq(products.id, id))
137
- .returning();
138
- return updated;
139
- }
140
- }
@@ -1,39 +0,0 @@
1
- import {
2
- pgTable,
3
- varchar,
4
- text,
5
- timestamp,
6
- integer,
7
- numeric,
8
- pgEnum,
9
- uuid,
10
- } from 'drizzle-orm/pg-core';
11
-
12
- // ProductStatus enum
13
- export const productStatusEnum = pgEnum('product_status', [
14
- 'ACTIVE',
15
- 'INACTIVE',
16
- 'OUT_OF_STOCK',
17
- 'DISCONTINUED',
18
- ]);
19
-
20
- // Products table
21
- export const products = pgTable('products', {
22
- id: uuid('id').defaultRandom().primaryKey(),
23
- name: varchar('name', { length: 150 }).notNull(),
24
- description: text('description'),
25
- sku: varchar('sku', { length: 50 }).notNull().unique(),
26
- price: numeric('price', { precision: 10, scale: 2 }).notNull(),
27
- stock: integer('stock').default(0).notNull(),
28
- category: varchar('category', { length: 100 }),
29
- status: productStatusEnum('status').default('ACTIVE').notNull(),
30
- createdAt: timestamp('created_at').defaultNow().notNull(),
31
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
32
- deletedAt: timestamp('deleted_at'),
33
- });
34
-
35
- // Inferred types
36
- export type Product = typeof products.$inferSelect;
37
- export type NewProduct = typeof products.$inferInsert;
38
-
39
- export type ProductStatus = 'ACTIVE' | 'INACTIVE' | 'OUT_OF_STOCK' | 'DISCONTINUED';
@@ -1,34 +0,0 @@
1
- import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
- import { IsString, IsNotEmpty, IsNumber, IsOptional, Min } from 'class-validator';
3
-
4
- export class CreateProductDto {
5
- @ApiProperty({ example: 'Wireless Mouse' })
6
- @IsString()
7
- @IsNotEmpty()
8
- name: string;
9
-
10
- @ApiPropertyOptional({ example: 'Ergonomic wireless mouse' })
11
- @IsString()
12
- @IsOptional()
13
- description?: string;
14
-
15
- @ApiProperty({ example: 'PROD-001' })
16
- @IsString()
17
- @IsNotEmpty()
18
- sku: string;
19
-
20
- @ApiProperty({ example: 29.99 })
21
- @IsNumber()
22
- @Min(0)
23
- price: number;
24
-
25
- @ApiProperty({ example: 100 })
26
- @IsNumber()
27
- @Min(0)
28
- stock: number;
29
-
30
- @ApiPropertyOptional({ example: 'Electronics' })
31
- @IsString()
32
- @IsOptional()
33
- category?: string;
34
- }
@@ -1,36 +0,0 @@
1
- import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
-
3
- export class ProductDto {
4
- @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', format: 'uuid' })
5
- id: string;
6
-
7
- @ApiProperty({ example: 'Wireless Mouse' })
8
- name: string;
9
-
10
- @ApiPropertyOptional({ example: 'Ergonomic wireless mouse' })
11
- description?: string;
12
-
13
- @ApiProperty({ example: 'PROD-001' })
14
- sku: string;
15
-
16
- @ApiProperty({ example: 29.99 })
17
- price: number;
18
-
19
- @ApiProperty({ example: 100 })
20
- stock: number;
21
-
22
- @ApiPropertyOptional({ example: 'Electronics' })
23
- category?: string;
24
-
25
- @ApiProperty({ example: 'ACTIVE' })
26
- status: string;
27
-
28
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
29
- createdAt: Date;
30
-
31
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
32
- updatedAt: Date;
33
-
34
- @ApiPropertyOptional({ nullable: true, example: null })
35
- deletedAt?: Date | null;
36
- }
@@ -1,4 +0,0 @@
1
- import { PartialType } from '@nestjs/swagger';
2
- import { CreateProductDto } from './create-product.dto';
3
-
4
- export class UpdateProductDto extends PartialType(CreateProductDto) {}