siesa-agents 2.1.21 → 2.1.23
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/bin/install.js +2 -1
- package/bmad-core/data/architecture-patterns.md +147 -0
- package/bmad-core/data/backend-standards.md +127 -195
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -11,7 +11,8 @@ class SiesaBmadInstaller {
|
|
|
11
11
|
{ source: 'bmad-core', target: '.bmad-core' },
|
|
12
12
|
{ source: 'vscode', target: '.vscode' },
|
|
13
13
|
{ source: 'github', target: '.github' },
|
|
14
|
-
{ source: 'claude', target: '.claude' }
|
|
14
|
+
{ source: 'claude', target: '.claude' },
|
|
15
|
+
{ source: 'resources', target: '.resources' }
|
|
15
16
|
];
|
|
16
17
|
|
|
17
18
|
// Lista de archivos que se preservan automáticamente (no se crean backups)
|
|
@@ -112,3 +112,150 @@ Use MCP to install Shadcn components instead of creating manually.
|
|
|
112
112
|
**Exceptions**: Only use pure React + Vite when user specifically mentions offline-first functionality or requests non-Next.js setup.
|
|
113
113
|
|
|
114
114
|
**Reasoning**: Next.js provides better developer experience, built-in optimization, and easier deployment while maintaining PWA capabilities.
|
|
115
|
+
|
|
116
|
+
## Backend Architecture
|
|
117
|
+
|
|
118
|
+
### Architecture Style
|
|
119
|
+
- **Hexagonal Architecture** (Ports & Adapters) + **Domain-Driven Design (DDD)**
|
|
120
|
+
|
|
121
|
+
### Folder Structure
|
|
122
|
+
|
|
123
|
+
MonoRepo Structure with Hexagonal Architecture + DDD:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
├── apps/ # Microservices applications
|
|
127
|
+
│ ├── sales-service/ # Sales domain microservice
|
|
128
|
+
│ │ ├── src/
|
|
129
|
+
│ │ │ ├── modules/
|
|
130
|
+
│ │ │ │ ├── quotes/ # Quote bounded context
|
|
131
|
+
│ │ │ │ │ ├── application/
|
|
132
|
+
│ │ │ │ │ │ ├── ports/ # Interfaces (secondary ports)
|
|
133
|
+
│ │ │ │ │ │ │ ├── repositories/
|
|
134
|
+
│ │ │ │ │ │ │ └── services/
|
|
135
|
+
│ │ │ │ │ │ ├── use-cases/ # Primary ports
|
|
136
|
+
│ │ │ │ │ │ ├── commands/
|
|
137
|
+
│ │ │ │ │ │ ├── queries/
|
|
138
|
+
│ │ │ │ │ │ └── dto/
|
|
139
|
+
│ │ │ │ │ ├── domain/
|
|
140
|
+
│ │ │ │ │ │ ├── entities/
|
|
141
|
+
│ │ │ │ │ │ ├── value-objects/
|
|
142
|
+
│ │ │ │ │ │ ├── aggregates/
|
|
143
|
+
│ │ │ │ │ │ ├── events/
|
|
144
|
+
│ │ │ │ │ │ └── services/ # Domain services
|
|
145
|
+
│ │ │ │ │ └── infrastructure/ # Adapters (secondary adapters)
|
|
146
|
+
│ │ │ │ │ ├── repositories/ # Prisma implementations
|
|
147
|
+
│ │ │ │ │ ├── services/ # External service adapters
|
|
148
|
+
│ │ │ │ │ └── events/
|
|
149
|
+
│ │ │ │ └── products/ # Product bounded context
|
|
150
|
+
│ │ │ ├── api/ # Primary adapters
|
|
151
|
+
│ │ │ │ ├── controllers/
|
|
152
|
+
│ │ │ │ ├── guards/
|
|
153
|
+
│ │ │ │ ├── middlewares/
|
|
154
|
+
│ │ │ │ └── filters/
|
|
155
|
+
│ │ │ ├── config/
|
|
156
|
+
│ │ │ ├── main.ts
|
|
157
|
+
│ │ │ └── app.module.ts
|
|
158
|
+
│ │ ├── test/
|
|
159
|
+
│ │ ├── prisma/
|
|
160
|
+
│ │ │ ├── schema.prisma
|
|
161
|
+
│ │ │ └── migrations/
|
|
162
|
+
│ │ └── package.json
|
|
163
|
+
│ │
|
|
164
|
+
│ ├── inventory-service/ # Inventory domain microservice
|
|
165
|
+
│ └── user-service/ # User domain microservice
|
|
166
|
+
│
|
|
167
|
+
├── libs/ # Shared libraries
|
|
168
|
+
│ ├── common/ # Common utilities
|
|
169
|
+
│ │ ├── src/
|
|
170
|
+
│ │ │ ├── decorators/
|
|
171
|
+
│ │ │ ├── filters/
|
|
172
|
+
│ │ │ ├── guards/
|
|
173
|
+
│ │ │ ├── interceptors/
|
|
174
|
+
│ │ │ ├── pipes/
|
|
175
|
+
│ │ │ ├── types/
|
|
176
|
+
│ │ │ └── utils/
|
|
177
|
+
│ │ └── package.json
|
|
178
|
+
│ │
|
|
179
|
+
│ ├── domain-core/ # Shared domain concepts
|
|
180
|
+
│ │ ├── src/
|
|
181
|
+
│ │ │ ├── base/
|
|
182
|
+
│ │ │ │ ├── aggregate-root.ts
|
|
183
|
+
│ │ │ │ ├── entity.ts
|
|
184
|
+
│ │ │ │ ├── value-object.ts
|
|
185
|
+
│ │ │ │ └── domain-event.ts
|
|
186
|
+
│ │ │ ├── interfaces/
|
|
187
|
+
│ │ │ └── exceptions/
|
|
188
|
+
│ │ └── package.json
|
|
189
|
+
│ │
|
|
190
|
+
│ └── database/ # Shared database utilities
|
|
191
|
+
│ ├── src/
|
|
192
|
+
│ │ ├── base-repository.ts
|
|
193
|
+
│ │ ├── transaction.decorator.ts
|
|
194
|
+
│ │ └── prisma.service.ts
|
|
195
|
+
│ └── package.json
|
|
196
|
+
│
|
|
197
|
+
├── tools/ # Development tools
|
|
198
|
+
├── nx.json # Nx workspace configuration
|
|
199
|
+
├── package.json # Root package.json
|
|
200
|
+
└── tsconfig.base.json # Base TypeScript config
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Core Principles
|
|
204
|
+
|
|
205
|
+
#### Hexagonal Architecture First
|
|
206
|
+
Strict separation of concerns following ports & adapters pattern:
|
|
207
|
+
- **Domain Layer**: Pure business logic with entities, value objects, aggregates, and domain services
|
|
208
|
+
- **Application Layer**: Use cases orchestrating domain logic, defining ports (interfaces)
|
|
209
|
+
- **Infrastructure Layer**: Adapters implementing ports (Prisma repos, HTTP clients, message publishers)
|
|
210
|
+
- **API Layer**: Primary adapters exposing application via REST/GraphQL (controllers, resolvers)
|
|
211
|
+
|
|
212
|
+
#### Domain-Driven Design
|
|
213
|
+
Business logic drives all architectural decisions:
|
|
214
|
+
- **Bounded Contexts**: Each module represents a bounded context (quotes, products, billing)
|
|
215
|
+
- **Ubiquitous Language**: Code reflects business terminology
|
|
216
|
+
- **Aggregates**: Consistency boundaries for domain entities
|
|
217
|
+
- **Domain Events**: Communicate changes across bounded contexts
|
|
218
|
+
- **Repository Pattern**: Abstract data access behind interfaces
|
|
219
|
+
|
|
220
|
+
#### Dependency Rules
|
|
221
|
+
- Domain layer has zero dependencies on frameworks or external libraries
|
|
222
|
+
- All dependencies point inward toward the domain core
|
|
223
|
+
- Use dependency inversion for all external concerns (databases, APIs, messaging)
|
|
224
|
+
- Interfaces defined in application layer, implementations in infrastructure layer
|
|
225
|
+
|
|
226
|
+
#### Microservices Independence
|
|
227
|
+
- Each microservice has its own database (no shared databases)
|
|
228
|
+
- Shared code through libraries only (common, domain-core, database)
|
|
229
|
+
- Independent deployment pipelines per service
|
|
230
|
+
- Service-to-service communication via events (async messaging)
|
|
231
|
+
- No direct database access between services
|
|
232
|
+
|
|
233
|
+
#### Test-Driven Development
|
|
234
|
+
- Unit tests for domain entities, value objects, and use cases
|
|
235
|
+
- Integration tests for repository implementations
|
|
236
|
+
- E2E tests for complete API workflows
|
|
237
|
+
- TDD approach: write tests before implementation
|
|
238
|
+
|
|
239
|
+
#### Type Safety & Validation
|
|
240
|
+
- Leverage TypeScript strict mode for compile-time safety
|
|
241
|
+
- Domain validation in value objects and entities
|
|
242
|
+
- DTO validation at API boundaries with class-validator
|
|
243
|
+
- No `any` types allowed
|
|
244
|
+
|
|
245
|
+
#### Security by Design
|
|
246
|
+
- Authentication and authorization at every layer
|
|
247
|
+
- Input validation on all endpoints
|
|
248
|
+
- OWASP Top 10 compliance
|
|
249
|
+
- Audit logging for critical operations
|
|
250
|
+
|
|
251
|
+
### Framework Selection Rules
|
|
252
|
+
|
|
253
|
+
**Default**: Always use NestJS 10+ with TypeScript for backend services.
|
|
254
|
+
|
|
255
|
+
**Database**: Prisma ORM only - no raw SQL queries allowed.
|
|
256
|
+
|
|
257
|
+
**Testing**: Jest + Supertest with TDD approach.
|
|
258
|
+
|
|
259
|
+
**Documentation**: Swagger/OpenAPI auto-generated from decorators.
|
|
260
|
+
|
|
261
|
+
**Reasoning**: NestJS provides excellent DI container, decorator-based development, and native support for microservices patterns while enforcing SOLID principles.
|
|
@@ -1,43 +1,32 @@
|
|
|
1
1
|
# Backend Development Standards
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
### Hexagonal Architecture Implementation
|
|
6
|
-
- **Application Core**: Domain entities, value objects, aggregates, and domain services
|
|
7
|
-
- **Primary Ports**: Use cases, commands, queries, and application services
|
|
8
|
-
- **Primary Adapters**: REST controllers, GraphQL resolvers, message handlers
|
|
9
|
-
- **Secondary Ports**: Repository interfaces, external service interfaces
|
|
10
|
-
- **Secondary Adapters**: Prisma repositories, HTTP clients, message publishers
|
|
11
|
-
|
|
12
|
-
### Dependency Rules
|
|
13
|
-
- Application core must not depend on external frameworks
|
|
14
|
-
- All dependencies point inward toward the domain
|
|
15
|
-
- Use dependency inversion for all external concerns
|
|
16
|
-
- Interfaces defined in application layer, implementations in infrastructure
|
|
3
|
+
> **Note**: For architecture patterns and principles (Hexagonal Architecture, DDD, folder structure), see [architecture-patterns.md](./architecture-patterns.md)
|
|
17
4
|
|
|
18
5
|
## Technology Stack Standards
|
|
19
6
|
|
|
20
7
|
### Core Technologies
|
|
21
8
|
- **NestJS**: 10+ with TypeScript and decorators
|
|
22
9
|
- **TypeScript**: Strict mode enabled, no `any` types
|
|
23
|
-
- **Prisma**: ORM for database operations (no raw queries)
|
|
24
|
-
- **Jest**: Unit and
|
|
25
|
-
- **Class-validator**:
|
|
10
|
+
- **Prisma**: ORM for database operations (no raw queries allowed)
|
|
11
|
+
- **Jest + Supertest**: Unit, integration, and E2E testing
|
|
12
|
+
- **Class-validator + Class-transformer**: DTO validation
|
|
26
13
|
|
|
27
|
-
### Framework
|
|
28
|
-
- **Default**:
|
|
29
|
-
- **Database**: Prisma ORM only - no raw SQL queries
|
|
30
|
-
- **Testing**: TDD approach with
|
|
31
|
-
- **Documentation**: Swagger/OpenAPI
|
|
14
|
+
### Framework Standards
|
|
15
|
+
- **Default Framework**: NestJS 10+ with TypeScript
|
|
16
|
+
- **Database**: Prisma ORM only - no raw SQL queries
|
|
17
|
+
- **Testing**: TDD approach with comprehensive test coverage
|
|
18
|
+
- **Documentation**: Swagger/OpenAPI auto-generated from decorators
|
|
19
|
+
- **Messaging**: NestJS Microservices (Redis, RabbitMQ, or gRPC)
|
|
32
20
|
|
|
33
21
|
### Development Tools
|
|
34
|
-
- **Nx**: MonoRepo management and build
|
|
22
|
+
- **Nx**: MonoRepo management and build orchestration
|
|
35
23
|
- **ESLint + Prettier**: Code quality and formatting
|
|
36
|
-
- **Husky**:
|
|
37
|
-
- **Winston**: Structured logging
|
|
38
|
-
- **Redis**: Caching and message transport
|
|
24
|
+
- **Husky**: Pre-commit hooks for quality gates
|
|
25
|
+
- **Winston**: Structured logging with log levels
|
|
26
|
+
- **Redis**: Caching, session storage, and message transport
|
|
27
|
+
- **Passport + JWT**: Authentication and authorization
|
|
39
28
|
|
|
40
|
-
## Domain-Driven Design
|
|
29
|
+
## Domain-Driven Design Implementation
|
|
41
30
|
|
|
42
31
|
### Entity Structure
|
|
43
32
|
```typescript
|
|
@@ -46,7 +35,6 @@ export class UserEntity extends AggregateRoot {
|
|
|
46
35
|
public readonly id: UserId,
|
|
47
36
|
private _email: EmailValueObject,
|
|
48
37
|
private _name: NameValueObject,
|
|
49
|
-
private _createdAt: Date,
|
|
50
38
|
) {
|
|
51
39
|
super();
|
|
52
40
|
}
|
|
@@ -56,7 +44,6 @@ export class UserEntity extends AggregateRoot {
|
|
|
56
44
|
UserId.generate(),
|
|
57
45
|
EmailValueObject.create(props.email),
|
|
58
46
|
NameValueObject.create(props.name),
|
|
59
|
-
new Date(),
|
|
60
47
|
);
|
|
61
48
|
user.addDomainEvent(new UserCreatedEvent(user.id));
|
|
62
49
|
return user;
|
|
@@ -91,11 +78,6 @@ export class EmailValueObject {
|
|
|
91
78
|
}
|
|
92
79
|
}
|
|
93
80
|
|
|
94
|
-
private isValidEmail(email: string): boolean {
|
|
95
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
96
|
-
return emailRegex.test(email);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
81
|
equals(other: EmailValueObject): boolean {
|
|
100
82
|
return this.value === other.value;
|
|
101
83
|
}
|
|
@@ -106,15 +88,45 @@ export class EmailValueObject {
|
|
|
106
88
|
}
|
|
107
89
|
```
|
|
108
90
|
|
|
109
|
-
### Repository
|
|
91
|
+
### Repository Pattern
|
|
110
92
|
```typescript
|
|
93
|
+
// Interface (in application/ports/repositories)
|
|
111
94
|
export interface UserRepositoryInterface {
|
|
112
95
|
save(user: UserEntity): Promise<UserEntity>;
|
|
113
96
|
findById(id: UserId): Promise<UserEntity | null>;
|
|
114
97
|
findByEmail(email: EmailValueObject): Promise<UserEntity | null>;
|
|
115
|
-
findAll(criteria: FindUsersCriteria): Promise<UserEntity[]>;
|
|
116
98
|
delete(id: UserId): Promise<void>;
|
|
117
99
|
}
|
|
100
|
+
|
|
101
|
+
// Implementation (in infrastructure/repositories)
|
|
102
|
+
@Injectable()
|
|
103
|
+
export class PrismaUserRepository implements UserRepositoryInterface {
|
|
104
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
105
|
+
|
|
106
|
+
async save(user: UserEntity): Promise<UserEntity> {
|
|
107
|
+
const data = {
|
|
108
|
+
id: user.id.toString(),
|
|
109
|
+
email: user.email.toString(),
|
|
110
|
+
name: user.name.toString(),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const savedUser = await this.prisma.user.upsert({
|
|
114
|
+
where: { id: data.id },
|
|
115
|
+
update: data,
|
|
116
|
+
create: data,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return this.toDomain(savedUser);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private toDomain(prismaUser: User): UserEntity {
|
|
123
|
+
return UserEntity.reconstitute({
|
|
124
|
+
id: UserId.create(prismaUser.id),
|
|
125
|
+
email: EmailValueObject.create(prismaUser.email),
|
|
126
|
+
name: NameValueObject.create(prismaUser.name),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
118
130
|
```
|
|
119
131
|
|
|
120
132
|
## Use Case Standards
|
|
@@ -133,20 +145,19 @@ export class CreateUserUseCase {
|
|
|
133
145
|
async execute(command: CreateUserCommand): Promise<UserResponseDto> {
|
|
134
146
|
// 1. Validate business rules
|
|
135
147
|
await this.validateUserDoesNotExist(command.email);
|
|
136
|
-
|
|
148
|
+
|
|
137
149
|
// 2. Create domain entity
|
|
138
150
|
const user = UserEntity.create({
|
|
139
151
|
email: command.email,
|
|
140
152
|
name: command.name,
|
|
141
153
|
});
|
|
142
|
-
|
|
154
|
+
|
|
143
155
|
// 3. Persist entity
|
|
144
156
|
const savedUser = await this.userRepository.save(user);
|
|
145
|
-
|
|
157
|
+
|
|
146
158
|
// 4. Publish domain events
|
|
147
159
|
await this.eventBus.publishAll(savedUser.getUncommittedEvents());
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
|
|
150
161
|
// 5. Return response DTO
|
|
151
162
|
return UserResponseDto.fromEntity(savedUser);
|
|
152
163
|
}
|
|
@@ -172,20 +183,16 @@ export class CreateUserCommand {
|
|
|
172
183
|
@IsNotEmpty()
|
|
173
184
|
@Length(2, 50)
|
|
174
185
|
readonly name: string;
|
|
175
|
-
|
|
176
|
-
@IsOptional()
|
|
177
|
-
@IsString()
|
|
178
|
-
readonly organizationId?: string;
|
|
179
186
|
}
|
|
180
187
|
```
|
|
181
188
|
|
|
182
189
|
## Testing Standards
|
|
183
190
|
|
|
184
191
|
### Testing Strategy
|
|
185
|
-
- **Unit Tests**: Domain entities, value objects, use cases
|
|
186
|
-
- **Integration Tests**: Repository implementations,
|
|
187
|
-
- **E2E Tests**: Complete API workflows
|
|
188
|
-
- **
|
|
192
|
+
- **Unit Tests**: Domain entities, value objects, use cases (isolated logic)
|
|
193
|
+
- **Integration Tests**: Repository implementations, database operations
|
|
194
|
+
- **E2E Tests**: Complete API workflows with Supertest
|
|
195
|
+
- **TDD Approach**: Write tests before implementation
|
|
189
196
|
|
|
190
197
|
### Test Structure
|
|
191
198
|
```typescript
|
|
@@ -198,19 +205,8 @@ describe('CreateUserUseCase', () => {
|
|
|
198
205
|
const module = await Test.createTestingModule({
|
|
199
206
|
providers: [
|
|
200
207
|
CreateUserUseCase,
|
|
201
|
-
{
|
|
202
|
-
|
|
203
|
-
useValue: {
|
|
204
|
-
save: jest.fn(),
|
|
205
|
-
findByEmail: jest.fn(),
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
provide: EVENT_BUS,
|
|
210
|
-
useValue: {
|
|
211
|
-
publishAll: jest.fn(),
|
|
212
|
-
},
|
|
213
|
-
},
|
|
208
|
+
{ provide: USER_REPOSITORY, useValue: { save: jest.fn(), findByEmail: jest.fn() } },
|
|
209
|
+
{ provide: EVENT_BUS, useValue: { publishAll: jest.fn() } },
|
|
214
210
|
],
|
|
215
211
|
}).compile();
|
|
216
212
|
|
|
@@ -219,46 +215,33 @@ describe('CreateUserUseCase', () => {
|
|
|
219
215
|
eventBus = module.get(EVENT_BUS);
|
|
220
216
|
});
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
command.name = 'Test User';
|
|
228
|
-
|
|
229
|
-
const expectedUser = UserEntity.create({
|
|
230
|
-
email: command.email,
|
|
231
|
-
name: command.name,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
userRepository.findByEmail.mockResolvedValue(null);
|
|
235
|
-
userRepository.save.mockResolvedValue(expectedUser);
|
|
218
|
+
it('should create user successfully', async () => {
|
|
219
|
+
// Arrange
|
|
220
|
+
const command = new CreateUserCommand();
|
|
221
|
+
command.email = 'test@example.com';
|
|
222
|
+
command.name = 'Test User';
|
|
236
223
|
|
|
237
|
-
|
|
238
|
-
|
|
224
|
+
userRepository.findByEmail.mockResolvedValue(null);
|
|
225
|
+
userRepository.save.mockResolvedValue(UserEntity.create(command));
|
|
239
226
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
expect(userRepository.save).toHaveBeenCalledWith(expect.any(UserEntity));
|
|
243
|
-
expect(eventBus.publishAll).toHaveBeenCalled();
|
|
244
|
-
});
|
|
227
|
+
// Act
|
|
228
|
+
const result = await useCase.execute(command);
|
|
245
229
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
230
|
+
// Assert
|
|
231
|
+
expect(result.email).toBe(command.email);
|
|
232
|
+
expect(userRepository.save).toHaveBeenCalledWith(expect.any(UserEntity));
|
|
233
|
+
expect(eventBus.publishAll).toHaveBeenCalled();
|
|
234
|
+
});
|
|
251
235
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
236
|
+
it('should throw error when user already exists', async () => {
|
|
237
|
+
// Arrange
|
|
238
|
+
const command = new CreateUserCommand();
|
|
239
|
+
command.email = 'existing@example.com';
|
|
256
240
|
|
|
257
|
-
|
|
241
|
+
userRepository.findByEmail.mockResolvedValue(UserEntity.create(command));
|
|
258
242
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
243
|
+
// Act & Assert
|
|
244
|
+
await expect(useCase.execute(command)).rejects.toThrow(UserAlreadyExistsException);
|
|
262
245
|
});
|
|
263
246
|
});
|
|
264
247
|
```
|
|
@@ -277,23 +260,20 @@ export class UserController {
|
|
|
277
260
|
|
|
278
261
|
@Post()
|
|
279
262
|
@ApiOperation({ summary: 'Create a new user' })
|
|
280
|
-
@ApiResponse({ status: 201,
|
|
263
|
+
@ApiResponse({ status: 201, type: UserResponseDto })
|
|
281
264
|
@ApiResponse({ status: 400, description: 'Bad request' })
|
|
282
|
-
@
|
|
283
|
-
async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
|
|
265
|
+
async createUser(@Body() dto: CreateUserDto): Promise<UserResponseDto> {
|
|
284
266
|
const command = new CreateUserCommand();
|
|
285
|
-
Object.assign(command,
|
|
267
|
+
Object.assign(command, dto);
|
|
286
268
|
return this.createUserUseCase.execute(command);
|
|
287
269
|
}
|
|
288
270
|
|
|
289
271
|
@Get(':id')
|
|
290
272
|
@ApiOperation({ summary: 'Get user by ID' })
|
|
291
|
-
@
|
|
292
|
-
@ApiResponse({ status: 200, description: 'User found', type: UserResponseDto })
|
|
273
|
+
@ApiResponse({ status: 200, type: UserResponseDto })
|
|
293
274
|
@ApiResponse({ status: 404, description: 'User not found' })
|
|
294
275
|
async getUser(@Param('id', ParseUUIDPipe) id: string): Promise<UserResponseDto> {
|
|
295
|
-
|
|
296
|
-
return this.getUserUseCase.execute(query);
|
|
276
|
+
return this.getUserUseCase.execute(new GetUserQuery(id));
|
|
297
277
|
}
|
|
298
278
|
}
|
|
299
279
|
```
|
|
@@ -308,9 +288,7 @@ model User {
|
|
|
308
288
|
name String
|
|
309
289
|
createdAt DateTime @default(now())
|
|
310
290
|
updatedAt DateTime @updatedAt
|
|
311
|
-
|
|
312
|
-
// Relationships
|
|
313
|
-
orders Order[]
|
|
291
|
+
orders Order[]
|
|
314
292
|
|
|
315
293
|
@@map("users")
|
|
316
294
|
}
|
|
@@ -320,8 +298,6 @@ model Order {
|
|
|
320
298
|
total Decimal @db.Decimal(10, 2)
|
|
321
299
|
status OrderStatus
|
|
322
300
|
userId String
|
|
323
|
-
|
|
324
|
-
// Relationships
|
|
325
301
|
user User @relation(fields: [userId], references: [id])
|
|
326
302
|
items OrderItem[]
|
|
327
303
|
|
|
@@ -337,104 +313,60 @@ enum OrderStatus {
|
|
|
337
313
|
}
|
|
338
314
|
```
|
|
339
315
|
|
|
340
|
-
###
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const data = {
|
|
348
|
-
id: user.id.toString(),
|
|
349
|
-
email: user.email.toString(),
|
|
350
|
-
name: user.name.toString(),
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
const savedUser = await this.prisma.user.upsert({
|
|
354
|
-
where: { id: data.id },
|
|
355
|
-
update: data,
|
|
356
|
-
create: data,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
return this.toDomain(savedUser);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async findById(id: UserId): Promise<UserEntity | null> {
|
|
363
|
-
const user = await this.prisma.user.findUnique({
|
|
364
|
-
where: { id: id.toString() },
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
return user ? this.toDomain(user) : null;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
private toDomain(prismaUser: User): UserEntity {
|
|
371
|
-
return UserEntity.reconstitute({
|
|
372
|
-
id: UserId.create(prismaUser.id),
|
|
373
|
-
email: EmailValueObject.create(prismaUser.email),
|
|
374
|
-
name: NameValueObject.create(prismaUser.name),
|
|
375
|
-
createdAt: prismaUser.createdAt,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
```
|
|
316
|
+
### Prisma Best Practices
|
|
317
|
+
- Use enums for fixed value sets
|
|
318
|
+
- Always add indexes on foreign keys
|
|
319
|
+
- Use `@@map` for table naming (plural snake_case)
|
|
320
|
+
- Include `createdAt` and `updatedAt` timestamps
|
|
321
|
+
- Use `cuid()` for primary keys
|
|
322
|
+
- No raw SQL queries - use Prisma Client only
|
|
380
323
|
|
|
381
324
|
## Security Standards
|
|
382
325
|
|
|
383
326
|
### Authentication & Authorization
|
|
384
|
-
- JWT
|
|
385
|
-
- Role-based access control
|
|
386
|
-
- Input validation on all endpoints
|
|
387
|
-
- Rate
|
|
388
|
-
- HTTPS only in production
|
|
327
|
+
- **JWT Tokens**: Proper expiration and refresh token handling
|
|
328
|
+
- **RBAC**: Role-based access control with Guards
|
|
329
|
+
- **Validation**: Input validation on all endpoints (class-validator)
|
|
330
|
+
- **Rate Limiting**: Throttle public endpoints to prevent abuse
|
|
331
|
+
- **Environment**: HTTPS only in production, secrets in env variables
|
|
389
332
|
|
|
390
333
|
### Data Protection
|
|
391
|
-
- Encrypt sensitive data at rest
|
|
392
|
-
-
|
|
393
|
-
- Implement audit logging
|
|
394
|
-
-
|
|
395
|
-
-
|
|
334
|
+
- Encrypt sensitive data at rest and in transit
|
|
335
|
+
- Never commit secrets to repository
|
|
336
|
+
- Implement audit logging for critical operations
|
|
337
|
+
- OWASP Top 10 compliance
|
|
338
|
+
- Regular dependency security audits
|
|
396
339
|
|
|
397
340
|
## Performance Standards
|
|
398
341
|
|
|
399
342
|
### Database Optimization
|
|
400
|
-
- Proper indexing
|
|
401
|
-
- Connection pooling
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
343
|
+
- Proper indexing on frequently queried fields
|
|
344
|
+
- Connection pooling via Prisma
|
|
345
|
+
- Pagination for large datasets (cursor-based preferred)
|
|
346
|
+
- Avoid N+1 queries with Prisma `include`
|
|
347
|
+
- Query monitoring and slow query logging
|
|
405
348
|
|
|
406
349
|
### Caching Strategy
|
|
407
|
-
- Redis
|
|
408
|
-
- Application-
|
|
409
|
-
-
|
|
410
|
-
-
|
|
411
|
-
- Cache invalidation patterns
|
|
350
|
+
- **Redis**: Session data, rate limiting, and frequently accessed data
|
|
351
|
+
- **Application Cache**: In-memory caching for configuration
|
|
352
|
+
- **TTL Strategy**: Appropriate time-to-live for different data types
|
|
353
|
+
- **Invalidation**: Event-driven cache invalidation
|
|
412
354
|
|
|
413
|
-
##
|
|
355
|
+
## Error Handling
|
|
414
356
|
|
|
415
|
-
###
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
│ ├── filters/
|
|
421
|
-
│ ├── guards/
|
|
422
|
-
│ ├── interceptors/
|
|
423
|
-
│ ├── pipes/
|
|
424
|
-
│ └── utils/
|
|
425
|
-
├── domain-core/
|
|
426
|
-
│ ├── base/
|
|
427
|
-
│ ├── interfaces/
|
|
428
|
-
│ └── exceptions/
|
|
429
|
-
└── database/
|
|
430
|
-
├── base-repository.ts
|
|
431
|
-
├── transaction.decorator.ts
|
|
432
|
-
└── prisma.service.ts
|
|
433
|
-
```
|
|
357
|
+
### Exception Hierarchy
|
|
358
|
+
- Domain exceptions for business rule violations
|
|
359
|
+
- Application exceptions for use case errors
|
|
360
|
+
- Infrastructure exceptions for external service failures
|
|
361
|
+
- HTTP exception filters for API responses
|
|
434
362
|
|
|
435
|
-
###
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
363
|
+
### Error Response Format
|
|
364
|
+
```typescript
|
|
365
|
+
{
|
|
366
|
+
"statusCode": 400,
|
|
367
|
+
"message": "User with email already exists",
|
|
368
|
+
"error": "UserAlreadyExistsException",
|
|
369
|
+
"timestamp": "2024-01-15T10:30:00Z",
|
|
370
|
+
"path": "/api/users"
|
|
371
|
+
}
|
|
372
|
+
```
|