speedrun-cli 2.6.8 → 2.6.10
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 +67 -0
- package/package.json +1 -1
- package/src/generator.js +4 -0
- package/src/moduleGenerator.js +2 -1
- package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +34 -44
- package/templates/base-crud/src/modules/products/dto/product.dto.ts +36 -38
- package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +4 -12
- package/templates/base-crud/src/modules/products/products.controller.ts +78 -94
- package/templates/base-crud-drizzle/src/modules/products/dto/create-product.dto.ts +34 -0
- package/templates/base-crud-drizzle/src/modules/products/dto/product.dto.ts +36 -0
- package/templates/base-crud-drizzle/src/modules/products/dto/update-product.dto.ts +4 -0
- package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +0 -1
- package/templates/base-crud-mongoose/src/modules/products/dto/create-product.dto.ts +34 -0
- package/templates/base-crud-mongoose/src/modules/products/dto/product.dto.ts +36 -0
- package/templates/base-crud-mongoose/src/modules/products/dto/update-product.dto.ts +4 -0
- package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +0 -1
- package/templates/base-crud-prisma/src/modules/products/dto/create-product.dto.ts +34 -0
- package/templates/base-crud-prisma/src/modules/products/dto/product.dto.ts +36 -0
- package/templates/base-crud-prisma/src/modules/products/dto/update-product.dto.ts +4 -0
- package/templates/base-crud-prisma/src/modules/products/products.controller.ts +78 -0
- package/templates/base-crud-typeorm/src/modules/products/dto/create-product.dto.ts +34 -0
- package/templates/base-crud-typeorm/src/modules/products/dto/product.dto.ts +36 -0
- package/templates/base-crud-typeorm/src/modules/products/dto/update-product.dto.ts +4 -0
- package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +0 -1
- package/templates/orm/prisma/package.json +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,72 @@ All notable changes to create-nestjs-auth will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.6.10] - 2026-08-21
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Restored Products controller and DTO files** — Restored `products.controller.ts`, `create-product.dto.ts`, `update-product.dto.ts`, and `product.dto.ts` into the `base-crud` and `base-crud-*` templates to eliminate TypeScript compilation errors (`TS2307: Cannot find module './products.controller'` / `Cannot find module './dto/create-product.dto'`).
|
|
12
|
+
- **Automated `app.module.ts` registration** — `ProductsModule` (and any module generated via `speedrun-cli generate`) is now automatically imported and registered inside `src/app.module.ts`'s `imports` array upon generation.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [2.6.9] - 2026-08-21
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Products module no longer shipped by default** — `src/modules/products/` was incorrectly included in the `base-crud` shared template and therefore copied into every project where `--base-crud` was enabled, even before the user ran `speedrun-cli generate`. The products example files have been moved exclusively into the ORM-specific overlay templates (`base-crud-{orm}`), which are only written during the guided CRUD setup step. The `base-crud` shared layer now only contains the abstract classes (`src/common/base/`) and `CRUD_README.md`.
|
|
20
|
+
- **Seed runs twice on Prisma** — removed the `"prisma": { "seed": "ts-node prisma/seed.ts" }` field from `templates/orm/prisma/package.json`. This field caused Prisma to automatically invoke the seed at the end of every `prisma migrate dev`, while `postSetup.js` also called `npm run prisma:seed` explicitly — resulting in the seed being executed twice and credentials being printed double.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [2.6.8] - 2026-08-21
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Guided CRUD setup step** — `? Do you want to generate your first CRUD module now?` is now part of the main setup flow, positioned after Database (Schema/Migration → Seed) and before Dev server start
|
|
28
|
+
- **`promptCrudGeneration()`** in `postSetup.js` — reusable function that calls `generateModule` with the selected ORM token, and auto-registers the generated module in `src/app.module.ts`
|
|
29
|
+
- **Manual instructions** now include a CRUD generation step (`speedrun-cli generate <name>`) when interactive setup is skipped
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Setup prompt message updated: `"Would you like to complete the setup now? (JWT secrets, database, CRUD)"` to accurately reflect full scope
|
|
33
|
+
- CRUD generation is now **always offered** during guided setup (previously only shown when `--base-crud` flag was set, and appeared _after_ the dev server prompt)
|
|
34
|
+
- Guided setup flow order: **JWT → Database → CRUD → Dev server**
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- **Duplicate CRUD prompt** — removed orphaned CRUD prompt block from `bin/cli.js`; single source of truth is now `postSetup.js`
|
|
38
|
+
- **`registerInAppModule` false positive** — overly broad `includes(${Name}Module)` check replaced with regex word-boundary `\b${Name}Module\b` to prevent partial-name collisions
|
|
39
|
+
- **Drizzle inject token mismatch** — `@Inject('DB_CONNECTION')` corrected to `@Inject('DRIZZLE')` to match the exported token in `database.module.ts`
|
|
40
|
+
- **TypeORM `@InjectRepository(Object)`** — added inline comment guiding users to replace `Object` with their actual entity class
|
|
41
|
+
- Ctrl+C during dev server no longer silently drops the CRUD prompt (prompt now runs _before_ the dev server)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## [2.6.0] - 2026-08-18
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- **Base CRUD Architecture** (`--base-crud` flag) — generates abstract `BaseService<T>` and `BaseController<T>` in `src/common/base/` with full Swagger integration
|
|
49
|
+
- **`BaseService<T>`** — generic CRUD abstraction with `create`, `findAll` (paginated), `findOne`, `update`, `remove` (soft-delete) and automatic `NotFoundException` throwing
|
|
50
|
+
- **`BaseController<T>`** — generic REST controller wiring `BaseService` methods to NestJS route decorators with full `@nestjs/swagger` decorators
|
|
51
|
+
- **Swagger DTO helpers** — `ApiResponseDto<T>`, `PaginatedResponseDto<T>`, `ApiResponseSchema()`, `PaginatedResponseSchema()` utility functions
|
|
52
|
+
- **ORM-specific ProductModule examples** — concrete `ProductsService` + `ProductsModule` templates for all four ORMs (`base-crud-{prisma,typeorm,drizzle,mongoose}`)
|
|
53
|
+
- **`ProductEntity` alias pattern** — each ORM service exports `export type ProductEntity = <OrmType>` so the shared controller imports from a single uniform name
|
|
54
|
+
- **`CRUD_README.md`** — full guide and cheatsheet copied into generated projects when `--base-crud` is enabled
|
|
55
|
+
- **`speedrun-cli generate [module-name]`** (`g` alias) — interactive CRUD module generator:
|
|
56
|
+
- Full CRUD or Custom Selection (checkbox) for individual operations
|
|
57
|
+
- ORM auto-detection from `package.json` dependencies
|
|
58
|
+
- Generates `service`, `controller`, `module`, and `dto/` files
|
|
59
|
+
- Auto-registers generated module in `src/app.module.ts`
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- Generator step 6 split into **6a (shared base-crud)** + **6b (ORM-specific overlay)** for correct template composition
|
|
63
|
+
- `printSuccessHeader` now displays Base CRUD status in success output
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- Prisma template import paths corrected (`../../database/` → `../../prisma/`) to resolve `TS2307` errors
|
|
67
|
+
- Removed direct `@prisma/client` model imports in templates; replaced with local interface stubs to decouple from user schema (`TS2305` fix)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
8
71
|
## [2.0.8] - 2025-12-05
|
|
9
72
|
|
|
73
|
+
|
|
10
74
|
### Fixed
|
|
11
75
|
- Fixed template copy failure when CLI is installed globally or via npx (node_modules path check issue)
|
|
12
76
|
- Fixed .gitignore not being included in generated projects (renamed to gitignore for npm compatibility)
|
|
@@ -91,6 +155,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
91
155
|
|
|
92
156
|
| Version | Date | Description |
|
|
93
157
|
|---------|------|-------------|
|
|
158
|
+
| 2.6.8 | 2026-08-21 | CRUD generation integrated into guided setup flow + bug fixes |
|
|
159
|
+
| 2.6.0 | 2026-08-18 | Base CRUD Architecture, ORM-specific templates, `generate` command |
|
|
160
|
+
| 2.0.8 | 2025-12-05 | Template copy & Prisma migration prompt fixes |
|
|
94
161
|
| 2.0.0 | 2025-12-04 | Multi-ORM and multi-database support |
|
|
95
162
|
| 1.1.0 | 2025-11-17 | Interactive mode and post-setup automation |
|
|
96
163
|
| 1.0.0 | 2025-11-16 | Initial release with Prisma + PostgreSQL |
|
package/package.json
CHANGED
package/src/generator.js
CHANGED
|
@@ -121,6 +121,10 @@ async function generateFromModularTemplates(targetDir, { baseDir, ormDir, dbDir,
|
|
|
121
121
|
filter: createCopyFilter(baseCrudOrmDir),
|
|
122
122
|
});
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
// 6c. Automatically register ProductsModule in src/app.module.ts
|
|
126
|
+
const { registerInAppModule } = require('./moduleGenerator');
|
|
127
|
+
await registerInAppModule(targetDir, 'Products', 'products');
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
package/src/moduleGenerator.js
CHANGED
|
@@ -1,44 +1,34 @@
|
|
|
1
|
-
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@
|
|
17
|
-
@
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@
|
|
26
|
-
@
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
stock: number;
|
|
36
|
-
|
|
37
|
-
@ApiPropertyOptional({ description: 'Product category', example: 'Peripherals', maxLength: 100 })
|
|
38
|
-
@IsOptional() @IsString() @MaxLength(100)
|
|
39
|
-
category?: string;
|
|
40
|
-
|
|
41
|
-
@ApiPropertyOptional({ enum: ProductStatus, default: ProductStatus.ACTIVE, example: ProductStatus.ACTIVE })
|
|
42
|
-
@IsOptional() @IsEnum(ProductStatus)
|
|
43
|
-
status?: ProductStatus = ProductStatus.ACTIVE;
|
|
44
|
-
}
|
|
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,38 +1,36 @@
|
|
|
1
|
-
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
deletedAt?: Date | null;
|
|
38
|
-
}
|
|
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,12 +1,4 @@
|
|
|
1
|
-
import { PartialType } from '@nestjs/swagger';
|
|
2
|
-
import { CreateProductDto } from './create-product.dto';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Update Product DTO
|
|
6
|
-
*
|
|
7
|
-
* Uses PartialType from @nestjs/swagger (NOT @nestjs/mapped-types) to:
|
|
8
|
-
* 1. Make all fields optional (PATCH semantics)
|
|
9
|
-
* 2. Preserve @ApiProperty decorators for Swagger schema rendering
|
|
10
|
-
* 3. Keep all class-validator rules active on provided fields
|
|
11
|
-
*/
|
|
12
|
-
export class UpdateProductDto extends PartialType(CreateProductDto) {}
|
|
1
|
+
import { PartialType } from '@nestjs/swagger';
|
|
2
|
+
import { CreateProductDto } from './create-product.dto';
|
|
3
|
+
|
|
4
|
+
export class UpdateProductDto extends PartialType(CreateProductDto) {}
|
|
@@ -1,94 +1,78 @@
|
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return { success: true, data, meta: { correlationId: '', timestamp: new Date().toISOString() } };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@Get(':id')
|
|
83
|
-
@ApiOperation({ summary: 'Get a product by UUID' })
|
|
84
|
-
@ApiParam({ name: 'id', format: 'uuid', example: '123e4567-e89b-12d3-a456-426614174000' })
|
|
85
|
-
@ApiResponse({ status: HttpStatus.OK, description: 'Product found', schema: ApiResponseSchema(ProductDto) })
|
|
86
|
-
@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Product not found' })
|
|
87
|
-
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid UUID format' })
|
|
88
|
-
@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' })
|
|
89
|
-
override async findOne(
|
|
90
|
-
@Param('id', new ParseUUIDPipe({ version: '4', errorHttpStatusCode: HttpStatus.BAD_REQUEST })) id: string,
|
|
91
|
-
): Promise<ApiResponseDto<ProductEntity>> {
|
|
92
|
-
return super.findOne(id);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|