Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

StyleMind Backend is a NestJS application for managing a personal wardrobe with AI-powered outfit generation. It uses Fastify as the HTTP server, Prisma as the ORM, and integrates multiple AI providers (Google Gemini, OpenAI, Ollama, LMStudio) through the Vercel AI SDK.

## Development Commands

### Setup
```bash
yarn install
yarn prisma migrate deploy # Apply database migrations
```

### Development
```bash
yarn dev # Start with SWC compiler + watch mode (recommended)
yarn start:dev # Standard development mode with watch
yarn start:debug # Debug mode with watch
```

### Building
```bash
yarn build # Standard build
yarn build:swc # Build with SWC (faster)
```

### Testing
```bash
yarn test # Run unit tests
yarn test:watch # Run tests in watch mode
yarn test:cov # Run tests with coverage
yarn test:debug # Debug tests
yarn test:e2e # Run end-to-end tests
```

### Code Quality
```bash
yarn lint # Lint and auto-fix TypeScript files
yarn format # Format code with Prettier
```

### Production
```bash
yarn start:prod # Start production server
yarn start:migrate:prod # Run migrations and start production
```

### Database
```bash
yarn prisma migrate dev # Create and apply new migration
yarn prisma migrate deploy # Apply migrations in production
yarn prisma studio # Open Prisma Studio GUI
yarn prisma generate # Regenerate Prisma Client
```

## Architecture

### Module Structure

The codebase follows NestJS module architecture with clear separation of concerns:

- **`src/modules/`** - Feature modules (business logic)
- `wardrobe/` - Wardrobe items management and outfit combinations
- `ai/` - AI service abstraction layer supporting multiple providers
- `multimedia/` - Image upload/processing with Firebase/MinIO support
- `security/` - Authentication (JWT) and authorization
- `users/` - User management and profiles
- `categories/` - Clothing categories and gender associations
- `admin/` - Administrative functions

- **`src/shared/`** - Shared utilities and infrastructure
- `services/` - PrismaService and other shared services
- `config/` - Configuration files loaded via ConfigModule
- `interceptors/` - Global interceptors (e.g., DateFormatInterceptor)
- `decorators/` - Custom decorators
- `dtos/` - Shared DTOs (e.g., PaginationDto)
- `controllers/` - Shared controllers (e.g., HealthController)

### Path Aliases

TypeScript path aliases are configured for cleaner imports:
- `@/*` - Maps to `src/*`
- `@modules/*` - Maps to `src/modules/*`
- `@shared/*` - Maps to `src/shared/*`

Example: `import { PrismaService } from '@shared/services/prisma.service'`

### Technology Stack

- **Framework:** NestJS with Fastify adapter (not Express)
- **Database:** PostgreSQL with Prisma ORM
- **Caching:** Two-tier cache (in-memory + Redis) using Keyv
- **Queue:** BullMQ for background job processing (image processing)
- **AI Integration:** Vercel AI SDK with support for Google, OpenAI, Ollama, LMStudio
- **Storage:** Pluggable storage (Firebase Storage or MinIO)
- **Logging:** Winston with nest-winston integration
- **Validation:** class-validator + class-transformer with global ValidationPipe
- **Security:** Helmet, Throttler, JWT authentication
- **API Documentation:** Swagger/OpenAPI at `/api-docs`

### Key Architectural Patterns

#### AI Service Abstraction
The `AiService` (`src/modules/ai/ai.service.ts`) provides a unified interface for multiple AI providers:
- Configured via `AI_PROVIDER` environment variable
- Supports: `google`, `openai`, `ollama`, `lmstudio`
- Methods: `generateJSON()` for structured output, `generateText()` for text generation
- Used primarily in `CombinationsService` for outfit generation

#### Multimedia Storage
The `MultimediaService` supports two storage backends:
- Configured via `STORAGE_PROVIDER` environment variable (`MINIO` or `FIREBASE`)
- Handles image uploads with Sharp for processing
- Uses BullMQ queue for async image processing
- Consumer: `ImagesConsumer` in `src/modules/multimedia/consumer/`

#### Authentication Flow
- JWT-based authentication via Passport
- Strategy in `src/modules/security/jwt-strategy/`
- Guards protect endpoints that require authentication
- Sessions tracked in database for security audit

#### Database Schema
Core models (see `prisma/schema.prisma`):
- `User` - User accounts with profile and body description
- `WardrobeItem` - Clothing items with attributes (color, style, material, season)
- `Category` - Clothing categories (gender-specific)
- `Combination` - Outfit combinations (user-created or AI-generated)
- `Image` - Images linked to wardrobe items
- `UsageTracking` - Track user usage patterns

#### Configuration System
Configuration is modular using NestJS ConfigModule:
- Each module has its own `config/` directory with typed configs
- Global configs in `src/shared/config/`
- All configs registered in `AppModule` via `ConfigModule.forRoot()`
- Access via `ConfigService` with type safety

## Common Development Patterns

### Creating a New Module
1. Generate with NestJS CLI: `nest g module modules/feature-name`
2. Add controllers, services, DTOs in module directory
3. Import module in `AppModule`
4. Add config file in `config/` subdirectory if needed
5. Register config in module's imports: `ConfigModule.forFeature(featureConfig)`

### Adding Database Models
1. Update `prisma/schema.prisma`
2. Run `yarn prisma migrate dev --name descriptive_name`
3. Prisma Client is auto-generated and available via `PrismaService`

### Working with AI Generation
- Use `AiService.generateJSON()` with Zod schemas for structured responses
- Use `AiService.generateText()` for free-form text
- Create prompts in a `prompts/` subdirectory within your module
- Example: `src/modules/wardrobe/prompts/combinations.prompts.ts`

### File Uploads
- Use Fastify's multipart support (configured in `main.ts`)
- Inject `MultimediaService` for handling uploads
- Files are processed async via BullMQ queue
- Limits: 10MB max file size, 4 files max per request

### Testing Considerations
- Unit tests use Jest, colocated with source files (`*.spec.ts`)
- E2E tests in `test/` directory (`*.e2e-spec.ts`)
- Test environment uses configuration from `.env.test`
- Module path mapping configured in Jest config

## API Structure

- **Base path:** `/api` (global prefix)
- **Swagger docs:** `/api-docs`
- **Health check:** `/api/health` (via TerminusModule)
- Authentication required endpoints use Bearer token (JWT)

## Environment Configuration

Required environment variables (see README.md for complete list):
- `NODE_ENV` - Environment (development/production)
- `PORT` - Server port (default: 3000)
- `DATABASE_URL` - PostgreSQL connection string
- `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD` - Redis configuration
- `STORAGE_PROVIDER` - `MINIO` or `FIREBASE`
- `AI_PROVIDER` - `google`, `openai`, `ollama`, or `lmstudio`
- `TEXT_MODEL` - Model name for the selected AI provider
- Provider-specific API keys when using cloud services

## Important Notes

- This project uses **Fastify**, not Express. Use Fastify-specific decorators and plugins.
- File uploads use `@fastify/multipart`, not Express multer.
- The app uses **Yarn** as package manager (see `packageManager` field in package.json).
- Date formatting is handled globally by `DateFormatInterceptor`.
- CORS is configured in `main.ts` with origin from `server.origin` config.
- Rate limiting via ThrottlerGuard: 15 requests per 60 seconds.
- Winston logger is available throughout the app via `WINSTON_MODULE_NEST_PROVIDER`.
- Prisma queries should use `PrismaService` injected from `@shared/services/prisma.service`.
71 changes: 36 additions & 35 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,75 +23,76 @@
"build:swc": "nest build -b swc"
},
"dependencies": {
"@ai-sdk/google": "^2.0.24",
"@ai-sdk/openai": "^2.0.56",
"@ai-sdk/openai-compatible": "^1.0.23",
"@ai-sdk/google": "^2.0.47",
"@ai-sdk/openai": "^2.0.88",
"@ai-sdk/openai-compatible": "^1.0.29",
"@fastify/helmet": "^13.0.2",
"@fastify/multipart": "^9.3.0",
"@fastify/static": "^8.3.0",
"@keyv/redis": "^5.1.3",
"@keyv/redis": "^5.1.5",
"@nestjs/bullmq": "^11.0.4",
"@nestjs/cache-manager": "^3.0.1",
"@nestjs/common": "^11.1.8",
"@nestjs/common": "^11.1.9",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.8",
"@nestjs/jwt": "^11.0.1",
"@nestjs/core": "^11.1.9",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.8",
"@nestjs/platform-fastify": "^11.1.8",
"@nestjs/swagger": "^11.2.1",
"@nestjs/platform-express": "^11.1.9",
"@nestjs/platform-fastify": "^11.1.9",
"@nestjs/swagger": "^11.2.3",
"@nestjs/terminus": "^11.0.0",
"@nestjs/throttler": "^6.4.0",
"@prisma/client": "^6.18.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "^6.19.1",
"@toon-format/toon": "^2.1.0",
"@types/passport-jwt": "^4.0.1",
"ai": "^5.0.81",
"ai": "^5.0.114",
"bcrypt": "^6.0.0",
"bullmq": "^5.61.2",
"cache-manager": "^7.2.4",
"cacheable": "^2.1.1",
"bullmq": "^5.66.0",
"cache-manager": "^7.2.7",
"cacheable": "^2.3.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"firebase": "^12.4.0",
"class-validator": "^0.14.3",
"firebase": "^12.7.0",
"helmet": "^8.1.0",
"keyv": "^5.5.3",
"keyv": "^5.5.5",
"minio": "^8.0.6",
"moment-timezone": "^0.6.0",
"nest-winston": "^1.10.2",
"ollama-ai-provider-v2": "^1.5.1",
"ollama-ai-provider-v2": "^1.5.5",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pg": "^8.16.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
"sharp": "^0.34.4",
"sharp": "^0.34.5",
"uuid": "^13.0.0",
"winston": "^3.18.3",
"zod": "^4.1.12"
"winston": "^3.19.0",
"zod": "^4.2.1"
},
"devDependencies": {
"@nestjs/cli": "^11.0.10",
"@nestjs/cli": "^11.0.14",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.8",
"@swc/cli": "^0.7.8",
"@swc/core": "^1.13.5",
"@nestjs/testing": "^11.1.9",
"@swc/cli": "^0.7.9",
"@swc/core": "^1.15.5",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.4",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/multer": "^2.0.0",
"@types/node": "^24.9.1",
"@types/node": "^25.0.3",
"@types/supertest": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0",
"@typescript-eslint/eslint-plugin": "^8.50.0",
"@typescript-eslint/parser": "^8.50.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-security": "^3.0.1",
"jest": "^30.2.0",
"prettier": "^3.6.2",
"prisma": "^6.18.0",
"prettier": "^3.7.4",
"prisma": "^6.19.1",
"source-map-support": "^0.5.21",
"supertest": "^7.1.4",
"ts-jest": "^29.4.5",
"ts-jest": "^29.4.6",
"ts-loader": "^9.5.4",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
Expand Down
24 changes: 12 additions & 12 deletions src/modules/wardrobe/prompts/combinations.prompts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Category, ClothingItem } from '../interfaces/combinations.interface';
import { encode } from '@toon-format/toon'

export function generateCombinationsPrompt(
clothingItemsBase: ClothingItem[],
Expand Down Expand Up @@ -48,38 +49,37 @@ The user will provide a JSON-like structure or clearly formatted text containing

**Output Format:**

Return your recommendation as a JSON object with the key '"outfitRecommendation"'. The value should be an array of clothing item objects, each including the parameters (id, categoryId) of the recommended items for the outfit. Also include a brief explanation for each recommended item, justifying its inclusion in the outfit. In Spanish, explain how the item complements the base item(s) and fits the user's description or occasion.
Return your recommendation as a JSON object with two keys:
1. '"outfitRecommendation"': An array of clothing item objects, each including only the "id" parameter of the recommended items.
2. '"overallExplanation"': A single comprehensive explanation in Spanish that describes the entire outfit. Explain how all the items work together, the color harmony, style cohesion, and how the complete outfit fits the user's description or occasion.

**Example Output:**

{
"outfitRecommendation": [
{
"id": "item1",
"categoryId": "category1",
"explanation": "This item complements the base item with its color and style, making it a perfect match for a casual day out."
"id": "item1"
},
{
"id": "item2",
"categoryId": "category 2",
"explanation": "This item adds a touch of elegance to the outfit, suitable for a semi-formal occasion."
"id": "item2"
}
]
],
"overallExplanation": "Este conjunto combina perfectamente para una salida casual. La camisa azul se complementa con los jeans negros, creando un equilibrio entre los tonos fríos. Los zapatos deportivos añaden comodidad manteniendo el estilo relajado del outfit, ideal para un viernes casual en la oficina."
}


**User Input:**
# Clothing Items Base:
${clothingItemsBase.map((item) => `- ${JSON.stringify(item)}`).join('\n')}
${encode(clothingItemsBase)}

# Clothing Items:
${clothingItems.map((item) => `- ${JSON.stringify(item)}`).join('\n')}
${encode(clothingItems)}

# Categories:
${categories.map((category) => `- ${category.name} (${category.id})`).join('\n')}
${encode(categories)}$

# Occasions:
${occasions.map((occasion) => `- ${occasion}`).join('\n')}
${encode(occasions)}

${description ? `# Description: ${description}` : ''}
`;
Expand Down
Loading