266 lines
10 KiB
Markdown
266 lines
10 KiB
Markdown
# Coder Subagent Reference
|
|
|
|
## Quick Navigation
|
|
|
|
| Category | Key Files |
|
|
| ------------ | ------------------------------------------------------------------ |
|
|
| Routes | `src/routes/*.routes.ts` |
|
|
| Services | `src/services/*.server.ts` (backend), `src/services/*.ts` (shared) |
|
|
| Repositories | `src/services/db/*.db.ts` |
|
|
| Types | `src/types.ts`, `src/types/*.ts` |
|
|
| Schemas | `src/schemas/*.schemas.ts` |
|
|
| Config | `src/config/env.ts` |
|
|
| Utils | `src/utils/*.ts` |
|
|
|
|
---
|
|
|
|
## Architecture Patterns (ADR Summary)
|
|
|
|
### Layer Flow
|
|
|
|
```
|
|
Route → validateRequest(schema) → Service → Repository → Database
|
|
↓
|
|
External APIs
|
|
```
|
|
|
|
### Repository Naming Convention (ADR-034)
|
|
|
|
| Prefix | Behavior | Return |
|
|
| --------- | --------------------------------- | -------------- |
|
|
| `get*` | Throws `NotFoundError` if missing | Entity |
|
|
| `find*` | Returns `null` if missing | Entity \| null |
|
|
| `list*` | Returns empty array if none | Entity[] |
|
|
| `create*` | Creates new record | Entity |
|
|
| `update*` | Updates existing | Entity |
|
|
| `delete*` | Removes record | void |
|
|
|
|
### Error Handling (ADR-001)
|
|
|
|
```typescript
|
|
import { handleDbError, NotFoundError } from '../services/db/errors.db';
|
|
import { logger } from '../services/logger.server';
|
|
|
|
// Repository pattern
|
|
async function getById(id: string): Promise<Entity> {
|
|
try {
|
|
const result = await pool.query('SELECT * FROM table WHERE id = $1', [id]);
|
|
if (result.rows.length === 0) throw new NotFoundError('Entity not found');
|
|
return result.rows[0];
|
|
} catch (error) {
|
|
handleDbError(error, logger, 'Failed to get entity', { id });
|
|
}
|
|
}
|
|
```
|
|
|
|
### API Response Helpers (ADR-028)
|
|
|
|
```typescript
|
|
import {
|
|
sendSuccess,
|
|
sendPaginated,
|
|
sendError,
|
|
sendNoContent,
|
|
sendMessage,
|
|
ErrorCode,
|
|
} from '../utils/apiResponse';
|
|
|
|
// Success with data
|
|
sendSuccess(res, data); // 200
|
|
sendSuccess(res, data, 201); // 201 Created
|
|
|
|
// Paginated
|
|
sendPaginated(res, items, { page, limit, total });
|
|
|
|
// Error
|
|
sendError(res, ErrorCode.NOT_FOUND, 'User not found', 404);
|
|
sendError(res, ErrorCode.VALIDATION_ERROR, 'Invalid input', 400, validationErrors);
|
|
|
|
// No content / Message
|
|
sendNoContent(res); // 204
|
|
sendMessage(res, 'Password updated');
|
|
```
|
|
|
|
### Transaction Pattern (ADR-002)
|
|
|
|
```typescript
|
|
import { withTransaction } from '../services/db/connection.db';
|
|
|
|
const result = await withTransaction(async (client) => {
|
|
await client.query('INSERT INTO a ...');
|
|
await client.query('INSERT INTO b ...');
|
|
return { success: true };
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Adding New Features
|
|
|
|
### New API Endpoint Checklist
|
|
|
|
1. **Schema** (`src/schemas/{domain}.schemas.ts`)
|
|
|
|
```typescript
|
|
import { z } from 'zod';
|
|
export const createEntitySchema = z.object({
|
|
body: z.object({ name: z.string().min(1) }),
|
|
});
|
|
```
|
|
|
|
2. **Route** (`src/routes/{domain}.routes.ts`)
|
|
|
|
```typescript
|
|
import { validateRequest } from '../middleware/validation.middleware';
|
|
import { createEntitySchema } from '../schemas/{domain}.schemas';
|
|
|
|
router.post('/', validateRequest(createEntitySchema), async (req, res, next) => {
|
|
try {
|
|
const result = await entityService.create(req.body);
|
|
sendSuccess(res, result, 201);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
```
|
|
|
|
3. **Service** (`src/services/{domain}Service.server.ts`)
|
|
|
|
```typescript
|
|
export async function create(data: CreateInput): Promise<Entity> {
|
|
// Business logic here
|
|
return repository.create(data);
|
|
}
|
|
```
|
|
|
|
4. **Repository** (`src/services/db/{domain}.db.ts`)
|
|
```typescript
|
|
export async function create(data: CreateInput, client?: PoolClient): Promise<Entity> {
|
|
const pool = client || getPool();
|
|
try {
|
|
const result = await pool.query('INSERT INTO ...', [data.name]);
|
|
return result.rows[0];
|
|
} catch (error) {
|
|
handleDbError(error, logger, 'Failed to create entity', { data });
|
|
}
|
|
}
|
|
```
|
|
|
|
### New Background Job Checklist
|
|
|
|
1. **Queue** (`src/services/queues.server.ts`)
|
|
|
|
```typescript
|
|
export const myQueue = new Queue('my-queue', { connection: redisConnection });
|
|
```
|
|
|
|
2. **Worker** (`src/services/workers.server.ts`)
|
|
|
|
```typescript
|
|
new Worker(
|
|
'my-queue',
|
|
async (job) => {
|
|
// Process job
|
|
},
|
|
{ connection: redisConnection },
|
|
);
|
|
```
|
|
|
|
3. **Trigger** (in service)
|
|
```typescript
|
|
await myQueue.add('job-name', { data });
|
|
```
|
|
|
|
---
|
|
|
|
## Key Files Reference
|
|
|
|
### Database Repositories
|
|
|
|
| Repository | Purpose | Path |
|
|
| -------------------- | ----------------------------- | ------------------------------------ |
|
|
| `flyer.db.ts` | Flyer CRUD, processing status | `src/services/db/flyer.db.ts` |
|
|
| `store.db.ts` | Store management | `src/services/db/store.db.ts` |
|
|
| `user.db.ts` | User accounts | `src/services/db/user.db.ts` |
|
|
| `shopping.db.ts` | Shopping lists, watchlists | `src/services/db/shopping.db.ts` |
|
|
| `gamification.db.ts` | Achievements, points | `src/services/db/gamification.db.ts` |
|
|
| `category.db.ts` | Item categories | `src/services/db/category.db.ts` |
|
|
| `price.db.ts` | Price history, comparisons | `src/services/db/price.db.ts` |
|
|
| `recipe.db.ts` | Recipe management | `src/services/db/recipe.db.ts` |
|
|
|
|
### Services
|
|
|
|
| Service | Purpose | Path |
|
|
| ---------------------------------- | -------------------------------- | ----------------------------------------------- |
|
|
| `flyerProcessingService.server.ts` | Orchestrates flyer AI extraction | `src/services/flyerProcessingService.server.ts` |
|
|
| `flyerAiProcessor.server.ts` | Gemini AI integration | `src/services/flyerAiProcessor.server.ts` |
|
|
| `cacheService.server.ts` | Redis caching | `src/services/cacheService.server.ts` |
|
|
| `queues.server.ts` | BullMQ queue definitions | `src/services/queues.server.ts` |
|
|
| `workers.server.ts` | BullMQ workers | `src/services/workers.server.ts` |
|
|
| `emailService.server.ts` | Nodemailer integration | `src/services/emailService.server.ts` |
|
|
| `geocodingService.server.ts` | Address geocoding | `src/services/geocodingService.server.ts` |
|
|
|
|
### Routes
|
|
|
|
| Route | Base Path | Auth Required |
|
|
| ------------------ | ------------- | ------------- |
|
|
| `flyer.routes.ts` | `/api/flyers` | Mixed |
|
|
| `store.routes.ts` | `/api/stores` | Mixed |
|
|
| `user.routes.ts` | `/api/users` | Yes |
|
|
| `auth.routes.ts` | `/api/auth` | No |
|
|
| `admin.routes.ts` | `/api/admin` | Admin only |
|
|
| `deals.routes.ts` | `/api/deals` | No |
|
|
| `health.routes.ts` | `/api/health` | No |
|
|
|
|
---
|
|
|
|
## Error Types
|
|
|
|
| Error Class | HTTP Status | Use Case |
|
|
| --------------------------- | ----------- | ------------------------- |
|
|
| `NotFoundError` | 404 | Resource not found |
|
|
| `ForbiddenError` | 403 | Access denied |
|
|
| `ValidationError` | 400 | Input validation failed |
|
|
| `UniqueConstraintError` | 409 | Duplicate record |
|
|
| `ForeignKeyConstraintError` | 400 | Referenced record missing |
|
|
| `NotNullConstraintError` | 400 | Required field null |
|
|
|
|
Import: `import { NotFoundError, ... } from '../services/db/errors.db'`
|
|
|
|
---
|
|
|
|
## Middleware
|
|
|
|
| Middleware | Purpose | Usage |
|
|
| ------------------------- | -------------------- | ------------------------------------------------------------ |
|
|
| `validateRequest(schema)` | Zod validation | `router.post('/', validateRequest(schema), handler)` |
|
|
| `requireAuth` | JWT authentication | `router.get('/', requireAuth, handler)` |
|
|
| `requireAdmin` | Admin role check | `router.delete('/', requireAuth, requireAdmin, handler)` |
|
|
| `fileUpload` | Multer file handling | `router.post('/upload', fileUpload.single('file'), handler)` |
|
|
|
|
---
|
|
|
|
## Type Definitions
|
|
|
|
| File | Contains |
|
|
| --------------------------- | ----------------------------------------------- |
|
|
| `src/types.ts` | Main types: User, Flyer, FlyerItem, Store, etc. |
|
|
| `src/types/api.ts` | API response envelopes, pagination |
|
|
| `src/types/auth.ts` | Auth-related types |
|
|
| `src/types/gamification.ts` | Achievement types |
|
|
|
|
---
|
|
|
|
## Naming Conventions (ADR-027)
|
|
|
|
| Context | Convention | Example |
|
|
| ----------------- | ---------------- | ----------------------------------- |
|
|
| AI output types | `Ai*` prefix | `AiFlyerItem`, `AiExtractionResult` |
|
|
| Database types | `Db*` prefix | `DbFlyer`, `DbUser` |
|
|
| API types | No prefix | `Flyer`, `User` |
|
|
| Schema validation | `*Schema` suffix | `createFlyerSchema` |
|
|
| Routes | `*.routes.ts` | `flyer.routes.ts` |
|
|
| Repositories | `*.db.ts` | `flyer.db.ts` |
|
|
| Server services | `*.server.ts` | `aiService.server.ts` |
|
|
| Client services | `*.client.ts` | `logger.client.ts` |
|