# NestJS Implementation Plan for AutoStore ## Overview Implementation of AutoStore system using NestJS with TypeScript, following Clean Architecture principles. The system stores items with expiration dates and automatically orders new items when they expire. ## Architecture Approach - **Clean Architecture** with clear separation of concerns - **Domain-Driven Design** with rich domain models - **Hexagonal Architecture** with dependency inversion - **Repository Pattern** for data persistence - **CQRS-like** command/query separation - **Dependency Injection** leveraging NestJS IoC container ## Core Domain Logic ### ItemExpirationSpec - Single Source of Truth for Expiration **File**: `src/domain/specifications/item-expiration.spec.ts` **Purpose**: Centralized expiration checking logic - the single source of truth for determining if items are expired **Key Methods**: - `isExpired(item: ItemEntity, currentTime: Date): boolean` - Checks if item expired - `getSpec(currentTime: Date): Specification` - Returns specification for repository queries **Place in the flow**: - Called by `AddItemCommand.execute()` to check newly created items for immediate expiration - Called by `HandleExpiredItemsCommand.execute()` to find expired items for processing - Used by `ItemRepository.findWhere()` to query database for expired items ## Detailed Implementation Plan ### Domain Layer #### 1. Entities **File**: `src/domain/entities/item.entity.ts` **Purpose**: Core business entity representing an item **Key Methods**: - `constructor(id: ItemId, name: string, expirationDate: ExpirationDate, orderUrl: string, userId: UserId): void` - Creates item with validation - Getters for all properties **Place in the flow**: - Created by `AddItemCommand.execute()` - Retrieved by `ItemRepository` methods - Passed to `ItemExpirationSpec.isExpired()` for expiration checking **File**: `src/domain/entities/user.entity.ts` **Purpose**: User entity for item ownership and authentication purposes **Key Methods**: - `constructor(id: UserId, username: string, passwordHash: string): void` - Creates user with validation - Getters for all properties #### 2. Value Objects **File**: `src/domain/value-objects/item-id.vo.ts` **Purpose**: Strong typing for item identifiers **Key Methods**: - `constructor(value: string): void` - Validates UUID format - `getValue(): string` - Returns string value - `equals(other: ItemId): boolean` - Compares with another ItemId **File**: `src/domain/value-objects/expiration-date.vo.ts` **Purpose**: Immutable expiration date with validation **Key Methods**: - `constructor(value: Date): void` - Validates date is in future - `getValue(): Date` - Returns Date object - `format(): string` - Returns ISO string format **Place in the flow**: - Used by `ItemEntity` constructor for type-safe date handling - Validated by `ItemExpirationSpec.isExpired()` for expiration logic #### 3. Specifications **File**: `src/domain/specifications/specification.interface.ts` **Purpose**: Generic specification pattern interface **Key Methods**: - `isSatisfiedBy(candidate: T): boolean` - Evaluates specification - `getSpec(): object` - Returns specification object for repository implementation **Place in the flow**: - Implemented by `ItemExpirationSpec` for type-safe specifications - Used by `ItemRepository.findWhere()` for database queries ### Application Layer #### 4. Commands **File**: `src/application/commands/add-item.command.ts` **Purpose**: Use case for creating new items with expiration handling **Key Methods**: - `constructor(itemRepo: IItemRepository, orderService: IOrderService, timeProvider: ITimeProvider, expirationSpec: ItemExpirationSpec, logger: Logger): void` - Dependency injection - `execute(name: string, expirationDate: string, orderUrl: string, userId: string): Promise` - Creates item, handles expired items immediately **Flow**: 1. `ItemsController.createItem()` calls `AddItemCommand.execute()` 2. Creates `ItemEntity` with validated data 3. Calls `ItemExpirationSpec.isExpired()` to check if item is expired 4. If expired: - calls `OrderHttpService.orderItem()` - **DO NOT save to repository** - returns null 5. If not expired: calls `ItemRepository.save()` and returns item ID **File**: `src/application/commands/handle-expired-items.command.ts` **Purpose**: Background command to process expired items **Key Methods**: - `constructor(itemRepo: IItemRepository, orderService: IOrderService, timeProvider: ITimeProvider, expirationSpec: ItemExpirationSpec, logger: Logger): void` - Dependency injection - `execute(): Promise` - Finds and processes all expired items **Flow**: 1. `ExpiredItemsScheduler.handleCron()` calls `HandleExpiredItemsCommand.execute()` 2. Gets current time from `ITimeProvider` 3. Calls `ItemExpirationSpec.getSpec()` to get expiration specification 4. Calls `ItemRepository.findWhere()` to find expired items 5. For each expired item: calls `OrderHttpService.orderItem()` then `ItemRepository.delete()` **File**: `src/application/commands/delete-item.command.ts` **Purpose**: Use case for deleting user items **Key Methods**: - `constructor(itemRepo: IItemRepository, logger: Logger): void` - Dependency injection - `execute(itemId: string, userId: string): Promise` - Validates ownership and deletes item **Flow**: 1. `ItemsController.deleteItem()` calls `DeleteItemCommand.execute()` 2. Calls `ItemRepository.findById()` to retrieve item 3. Validates ownership by comparing user IDs 4. Calls `ItemRepository.delete()` to remove item **File**: `src/application/commands/login-user.command.ts` **Purpose**: User authentication use case **Key Methods**: - `constructor(authService: IAuthService, logger: Logger): void` - Dependency injection - `execute(username: string, password: string): Promise` - Authenticates and returns JWT token #### 5. Queries **File**: `src/application/queries/get-item.query.ts` **Purpose**: Retrieves single item by ID with authorization **Key Methods**: - `constructor(itemRepo: IItemRepository, logger: Logger): void` - Dependency injection - `execute(itemId: string, userId: string): Promise` - Validates ownership and returns item **Flow**: 1. `ItemsController.getItem()` calls `GetItemQuery.execute()` 2. Calls `ItemRepository.findById()` to retrieve item 3. Validates ownership by comparing user IDs 4. Returns item entity **File**: `src/application/queries/list-items.query.ts` **Purpose**: Retrieves all items for authenticated user **Key Methods**: - `constructor(itemRepo: IItemRepository, logger: Logger): void` - Dependency injection - `execute(userId: string): Promise` - Returns user's items **Flow**: 1. `ItemsController.listItems()` calls `ListItemsQuery.execute()` 2. Calls `ItemRepository.findByUserId()` to retrieve user's items 3. Returns array of item entities #### 6. DTOs **File**: `src/application/dto/create-item.dto.ts` **Purpose**: Request validation for item creation **Key Properties**: - `name: string` - Item name (min: 1, max: 255) - `expirationDate: string` - ISO date string (future date validation) - `orderUrl: string` - Valid URL format **Place in the flow**: - Used by `ItemsController.createItem()` for request body validation **File**: `src/application/dto/item-response.dto.ts` **Purpose**: Standardized item response format **Key Properties**: - `id: string` - Item ID - `name: string` - Item name - `expirationDate: string` - ISO date string - `orderUrl: string` - Order URL - `userId: string` - Owner user ID - `createdAt: string` - Creation timestamp **Place in the flow**: - Used by all item controller methods for response transformation ### Infrastructure Layer #### 7. Repositories **File**: `src/infrastructure/persistence/repositories/item-repository.impl.ts` **Purpose**: TypeORM implementation of item repository **Key Methods**: - `save(item: ItemEntity): Promise` - Persists item entity - `findById(id: ItemId): Promise` - Finds by ID - `findByUserId(userId: UserId): Promise` - Finds by user - `findWhere(spec: Specification): Promise` - Finds by specification using `ItemExpirationSpec` - `delete(id: ItemId): Promise` - Deletes item - `exists(id: ItemId): Promise` - Checks existence **Place in the flow**: - Called by all commands and queries for data persistence and retrieval - Uses `ItemExpirationSpec` for finding expired items #### 8. HTTP Services **File**: `src/infrastructure/http/order-http.service.ts` **Purpose**: HTTP implementation of order service **Key Methods**: - `constructor(httpService: HttpService, logger: Logger): void` - Dependency injection - `orderItem(item: ItemEntity): Promise` - Sends POST request to order URL **Place in the flow**: - Called by `AddItemCommand.execute()` for expired items - Called by `HandleExpiredItemsCommand.execute()` for batch processing #### 9. Authentication **File**: `src/infrastructure/auth/jwt-auth.service.ts` **Purpose**: JWT implementation of authentication service **Key Methods**: - `constructor(userRepo: IUserRepository, jwtService: JwtService, configService: ConfigService, logger: Logger): void` - Dependency injection - `authenticate(username: string, password: string): Promise` - Validates credentials and generates JWT - `validateToken(token: string): Promise` - Validates JWT token - `getUserIdFromToken(token: string): Promise` - Extracts user ID from token **Place in the flow**: - Called by `LoginUserCommand.execute()` for user authentication - Used by `JwtAuthGuard` for route protection ### Presentation Layer #### 10. Controllers **File**: `src/presentation/controllers/items.controller.ts` **Purpose**: REST API endpoints for item management **Key Methods**: - `constructor(addItemCmd: AddItemCommand, getItemQry: GetItemQuery, listItemsQry: ListItemsQuery, deleteItemCmd: DeleteItemCommand): void` - Dependency injection - `createItem(@Body() dto: CreateItemDto, @Req() req: Request): Promise` - POST /items - `getItem(@Param('id') id: string, @Req() req: Request): Promise` - GET /items/:id - `listItems(@Req() req: Request): Promise` - GET /items - `deleteItem(@Param('id') id: string, @Req() req: Request): Promise` - DELETE /items/:id **Flow**: - Receives HTTP requests and validates input - Calls appropriate commands/queries based on HTTP method - Returns standardized responses with DTOs **File**: `src/presentation/controllers/auth.controller.ts` **Purpose**: Authentication endpoints **Key Methods**: - `constructor(loginUserCmd: LoginUserCommand): void` - Dependency injection - `login(@Body() dto: LoginDto): Promise<{ token: string }>` - POST /login #### 11. Guards **File**: `src/presentation/guards/jwt-auth.guard.ts` **Purpose**: JWT authentication route protection **Key Methods**: - `constructor(jwtAuthService: IJwtAuthService, logger: Logger): void` - Dependency injection - `canActivate(context: ExecutionContext): Promise` - Validates JWT and attaches user to request **Place in the flow**: - Applied to all protected routes by NestJS Guard System - Uses `JwtAuthService` for token validation ## Background Processing **File**: `src/infrastructure/scheduler/expired-items.scheduler.ts` **Purpose**: Scheduled job for processing expired items **Key Methods**: - `constructor(handleExpiredItemsCmd: HandleExpiredItemsCommand, logger: Logger): void` - Dependency injection - `handleCron(): Promise` - Runs every minute to check for expired items **Flow**: 1. Upon application startup: Immediately invoke `HandleExpiredItemsCommand.execute()` 2. Start cron scheduler (every minute) 3. NestJS Scheduler triggers `handleCron()` every minute 4. Calls `HandleExpiredItemsCommand.execute()` to process expired items 5. Logs processing results and errors ## Complete Flow Summary ### Item Creation Flow ``` POST /items ├── JwtAuthGuard (authentication) ├── CreateItemDto validation ├── ItemsController.createItem() │ ├── AddItemCommand.execute() │ │ ├── ItemEntity constructor (validation) │ │ ├── ItemExpirationSpec.isExpired() ← SINGLE SOURCE OF TRUTH │ │ ├── If expired: OrderHttpService.orderItem() │ │ └── If not expired: ItemRepository.save() │ └── ItemResponseDto transformation └── HTTP response ``` ### Expired Items Processing Flow ``` Cron Job (every minute) └── ExpiredItemsScheduler.handleCron() └── HandleExpiredItemsCommand.execute() ├── ITimeProvider.now() ├── ItemExpirationSpec.getSpec() ← SINGLE SOURCE OF TRUTH ├── ItemRepository.findWhere() (using spec) ├── For each expired item: │ ├── OrderHttpService.orderItem() │ └── ItemRepository.delete() └── Logging ``` ### Item Retrieval Flow ``` GET /items/:id ├── JwtAuthGuard (authentication) ├── ItemsController.getItem() │ ├── GetItemQuery.execute() │ │ ├── ItemRepository.findById() │ │ ├── Ownership validation │ │ └── Return ItemEntity │ └── ItemResponseDto transformation └── HTTP response ``` ## Key Design Principles 1. **Single Source of Truth**: `ItemExpirationSpec` is the only component that determines expiration logic 2. **Clear Flow**: Each component has a well-defined place in the execution chain 3. **Dependency Inversion**: High-level modules don't depend on low-level modules 4. **Separation of Concerns**: Each layer has distinct responsibilities 5. **Testability**: All components can be tested in isolation This implementation plan ensures consistent development regardless of the implementer, providing clear flow definitions and emphasizing `ItemExpirationSpec` as the centralized source for expiration logic.