Files
2025-11-29 18:20:21 +08:00

2294 lines
59 KiB
Markdown

# Feature Implementation Operation
Complete full-stack feature implementation across database, backend, and frontend layers with production-ready code, tests, and documentation.
## Parameters
**Received**: `$ARGUMENTS` (after removing 'implement' operation name)
Expected format: `description:"feature details" [scope:"specific-area"] [priority:"high|medium|low"] [tests:"coverage-level"] [framework:"react|vue|angular"]`
## Workflow
### Phase 1: Requirements Understanding
Parse the feature description and clarify:
**Functional Requirements:**
- What is the user-facing functionality?
- What business problem does it solve?
- What are the acceptance criteria?
- What are the expected inputs and outputs?
**Non-Functional Requirements:**
- Performance expectations (response time, throughput)
- Security considerations (authentication, authorization, data protection)
- Scalability requirements (concurrent users, data volume)
- UI/UX requirements (responsive, accessible, real-time updates)
**Ask clarifying questions if:**
- Requirements are ambiguous or incomplete
- Multiple implementation approaches are possible
- Technical constraints are unclear
- Acceptance criteria are not well-defined
### Phase 2: Codebase Analysis
Before implementation, examine the existing project:
**Project Structure:**
```bash
# Discover project layout
ls -la
find . -maxdepth 3 -type d | grep -E "(src|app|api|components|models)"
# Identify tech stack
find . -name "package.json" -o -name "requirements.txt" -o -name "go.mod" -o -name "pom.xml"
cat package.json | grep -E "(react|vue|angular|express|fastify|prisma|typeorm)"
```
**Existing Patterns:**
```bash
# Database patterns
find . -path "*/migrations/*" -o -path "*/models/*" -o -path "*/schemas/*"
# Backend patterns
find . -path "*/services/*" -o -path "*/controllers/*" -o -path "*/routes/*"
# Frontend patterns
find . -path "*/components/*" -o -path "*/hooks/*" -o -path "*/store/*"
# Testing patterns
find . -path "*/__tests__/*" -o -path "*/test/*" -name "*.test.*" -o -name "*.spec.*"
```
**Conventions to Follow:**
- Naming conventions (camelCase, PascalCase, snake_case)
- File organization patterns
- Import/export patterns
- Error handling approaches
- Testing frameworks and patterns
- Documentation style
### Phase 3: Implementation Design
Design the implementation across all layers:
#### Database Design
**Schema Design:**
```sql
-- Example: User authentication feature
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
email_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE user_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(255) NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_user_sessions_user_id (user_id),
INDEX idx_user_sessions_token_hash (token_hash),
INDEX idx_user_sessions_expires_at (expires_at)
);
```
**Index Strategy:**
- Primary keys for unique identification
- Foreign keys for relationships
- Indexes on frequently queried columns
- Composite indexes for multi-column queries
- Consider query patterns and performance
**Migration Planning:**
- Forward migration (up)
- Rollback migration (down)
- Data seeding if needed
- Migration testing strategy
#### Backend Design
**API Endpoint Design:**
```typescript
// Example: Authentication endpoints
POST /api/auth/register # Register new user
POST /api/auth/login # Login with credentials
POST /api/auth/logout # Logout current session
POST /api/auth/refresh # Refresh access token
GET /api/auth/me # Get current user profile
POST /api/auth/verify-email # Verify email address
POST /api/auth/forgot-password # Request password reset
POST /api/auth/reset-password # Reset password with token
```
**Request/Response Models:**
```typescript
// Register request
interface RegisterRequest {
email: string;
password: string;
name?: string;
}
// Register response
interface RegisterResponse {
user: {
id: string;
email: string;
name: string | null;
};
accessToken: string;
refreshToken: string;
}
// Error response
interface ErrorResponse {
error: {
code: string;
message: string;
details?: Record<string, any>;
};
}
```
**Service Layer Architecture:**
- Business logic separated from controllers
- Single responsibility per service
- Dependency injection for testability
- Error handling with custom exceptions
- Validation at service boundaries
**Data Access Layer:**
- Repository pattern for data operations
- Query builders or ORM usage
- Transaction management
- Connection pooling
- Caching strategy
#### Frontend Design
**Component Structure:**
```
src/features/auth/
├── components/
│ ├── LoginForm.tsx
│ ├── RegisterForm.tsx
│ ├── ForgotPasswordForm.tsx
│ └── EmailVerification.tsx
├── hooks/
│ ├── useAuth.ts
│ ├── useLogin.ts
│ └── useRegister.ts
├── store/
│ ├── authSlice.ts # Redux/Zustand
│ └── authSelectors.ts
├── api/
│ └── authApi.ts # API client
├── types/
│ └── auth.types.ts
└── __tests__/
├── LoginForm.test.tsx
└── useAuth.test.ts
```
**State Management:**
- Local state vs global state decisions
- Server state management (React Query, SWR)
- Form state (React Hook Form, Formik)
- Authentication state persistence
- Loading and error states
**API Integration:**
- HTTP client configuration (axios, fetch)
- Request/response interceptors
- Error handling and retry logic
- Token management and refresh
- API request cancellation
### Phase 4: Incremental Implementation
Implement in phases for robustness and testability:
#### Phase 4.1 - Data Layer Implementation
**Step 1: Create Migration Script**
```sql
-- migrations/20240101120000_add_user_authentication.up.sql
BEGIN;
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
email_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
CONSTRAINT email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$')
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE TABLE user_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(255) NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);
CREATE INDEX idx_user_sessions_token_hash ON user_sessions(token_hash);
CREATE INDEX idx_user_sessions_expires_at ON user_sessions(expires_at);
COMMIT;
```
```sql
-- migrations/20240101120000_add_user_authentication.down.sql
BEGIN;
DROP TABLE IF EXISTS user_sessions;
DROP TABLE IF EXISTS users;
COMMIT;
```
**Step 2: Create/Update Models**
```typescript
// models/User.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { UserSession } from './UserSession';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 255, unique: true })
email: string;
@Column({ type: 'varchar', length: 255, select: false })
passwordHash: string;
@Column({ type: 'boolean', default: false })
emailVerified: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@OneToMany(() => UserSession, session => session.user)
sessions: UserSession[];
}
```
```typescript
// models/UserSession.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn, Index } from 'typeorm';
import { User } from './User';
@Entity('user_sessions')
export class UserSession {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
@Index()
userId: string;
@Column({ type: 'varchar', length: 255 })
@Index()
tokenHash: string;
@Column({ type: 'timestamp' })
@Index()
expiresAt: Date;
@CreateDateColumn()
createdAt: Date;
@ManyToOne(() => User, user => user.sessions, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
}
```
**Step 3: Test Database Operations**
```typescript
// models/__tests__/User.test.ts
import { DataSource } from 'typeorm';
import { User } from '../User';
describe('User Model', () => {
let dataSource: DataSource;
beforeAll(async () => {
dataSource = await createTestDataSource();
});
afterAll(async () => {
await dataSource.destroy();
});
it('should create user with valid data', async () => {
const userRepo = dataSource.getRepository(User);
const user = userRepo.create({
email: 'test@example.com',
passwordHash: 'hashed_password',
});
await userRepo.save(user);
expect(user.id).toBeDefined();
expect(user.email).toBe('test@example.com');
expect(user.emailVerified).toBe(false);
});
it('should enforce unique email constraint', async () => {
const userRepo = dataSource.getRepository(User);
await userRepo.save({
email: 'duplicate@example.com',
passwordHash: 'hash1',
});
await expect(
userRepo.save({
email: 'duplicate@example.com',
passwordHash: 'hash2',
})
).rejects.toThrow();
});
});
```
#### Phase 4.2 - Backend Layer Implementation
**Step 1: Create Repository Layer**
```typescript
// repositories/UserRepository.ts
import { Repository } from 'typeorm';
import { User } from '../models/User';
import { AppDataSource } from '../config/database';
export class UserRepository {
private repository: Repository<User>;
constructor() {
this.repository = AppDataSource.getRepository(User);
}
async findByEmail(email: string): Promise<User | null> {
return this.repository.findOne({
where: { email: email.toLowerCase() },
select: ['id', 'email', 'passwordHash', 'emailVerified', 'createdAt', 'updatedAt'],
});
}
async findById(id: string): Promise<User | null> {
return this.repository.findOne({
where: { id },
});
}
async create(data: { email: string; passwordHash: string }): Promise<User> {
const user = this.repository.create({
email: data.email.toLowerCase(),
passwordHash: data.passwordHash,
});
return this.repository.save(user);
}
async updateEmailVerified(userId: string, verified: boolean): Promise<void> {
await this.repository.update(userId, { emailVerified: verified });
}
async updatePassword(userId: string, passwordHash: string): Promise<void> {
await this.repository.update(userId, { passwordHash });
}
async delete(userId: string): Promise<void> {
await this.repository.delete(userId);
}
}
```
**Step 2: Create Service Layer**
```typescript
// services/AuthService.ts
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { UserRepository } from '../repositories/UserRepository';
import { SessionRepository } from '../repositories/SessionRepository';
import {
UnauthorizedError,
ConflictError,
ValidationError
} from '../errors';
export interface RegisterInput {
email: string;
password: string;
name?: string;
}
export interface LoginInput {
email: string;
password: string;
}
export interface AuthTokens {
accessToken: string;
refreshToken: string;
}
export class AuthService {
constructor(
private userRepository: UserRepository,
private sessionRepository: SessionRepository
) {}
async register(input: RegisterInput): Promise<{ user: User; tokens: AuthTokens }> {
// Validate input
this.validateEmail(input.email);
this.validatePassword(input.password);
// Check if user exists
const existingUser = await this.userRepository.findByEmail(input.email);
if (existingUser) {
throw new ConflictError('User with this email already exists');
}
// Hash password
const passwordHash = await bcrypt.hash(input.password, 12);
// Create user
const user = await this.userRepository.create({
email: input.email,
passwordHash,
});
// Generate tokens
const tokens = await this.generateTokens(user.id);
// Create session
await this.sessionRepository.create({
userId: user.id,
tokenHash: await this.hashToken(tokens.refreshToken),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
});
return { user, tokens };
}
async login(input: LoginInput): Promise<{ user: User; tokens: AuthTokens }> {
// Find user
const user = await this.userRepository.findByEmail(input.email);
if (!user) {
throw new UnauthorizedError('Invalid credentials');
}
// Verify password
const isValid = await bcrypt.compare(input.password, user.passwordHash);
if (!isValid) {
throw new UnauthorizedError('Invalid credentials');
}
// Generate tokens
const tokens = await this.generateTokens(user.id);
// Create session
await this.sessionRepository.create({
userId: user.id,
tokenHash: await this.hashToken(tokens.refreshToken),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});
return { user, tokens };
}
async logout(refreshToken: string): Promise<void> {
const tokenHash = await this.hashToken(refreshToken);
await this.sessionRepository.deleteByTokenHash(tokenHash);
}
async refreshTokens(refreshToken: string): Promise<AuthTokens> {
// Verify refresh token
const payload = jwt.verify(refreshToken, process.env.JWT_SECRET!) as { userId: string };
// Check if session exists
const tokenHash = await this.hashToken(refreshToken);
const session = await this.sessionRepository.findByTokenHash(tokenHash);
if (!session || session.expiresAt < new Date()) {
throw new UnauthorizedError('Invalid or expired refresh token');
}
// Generate new tokens
const tokens = await this.generateTokens(payload.userId);
// Update session
await this.sessionRepository.update(session.id, {
tokenHash: await this.hashToken(tokens.refreshToken),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});
return tokens;
}
private async generateTokens(userId: string): Promise<AuthTokens> {
const accessToken = jwt.sign(
{ userId, type: 'access' },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId, type: 'refresh' },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
private async hashToken(token: string): Promise<string> {
return bcrypt.hash(token, 10);
}
private validateEmail(email: string): void {
const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$/;
if (!emailRegex.test(email)) {
throw new ValidationError('Invalid email format');
}
}
private validatePassword(password: string): void {
if (password.length < 8) {
throw new ValidationError('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
throw new ValidationError('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
throw new ValidationError('Password must contain at least one lowercase letter');
}
if (!/[0-9]/.test(password)) {
throw new ValidationError('Password must contain at least one number');
}
}
}
```
**Step 3: Create API Controllers**
```typescript
// controllers/AuthController.ts
import { Request, Response, NextFunction } from 'express';
import { AuthService } from '../services/AuthService';
export class AuthController {
constructor(private authService: AuthService) {}
register = async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password, name } = req.body;
const result = await this.authService.register({
email,
password,
name,
});
res.status(201).json({
user: {
id: result.user.id,
email: result.user.email,
emailVerified: result.user.emailVerified,
},
accessToken: result.tokens.accessToken,
refreshToken: result.tokens.refreshToken,
});
} catch (error) {
next(error);
}
};
login = async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
const result = await this.authService.login({ email, password });
res.json({
user: {
id: result.user.id,
email: result.user.email,
emailVerified: result.user.emailVerified,
},
accessToken: result.tokens.accessToken,
refreshToken: result.tokens.refreshToken,
});
} catch (error) {
next(error);
}
};
logout = async (req: Request, res: Response, next: NextFunction) => {
try {
const { refreshToken } = req.body;
await this.authService.logout(refreshToken);
res.status(204).send();
} catch (error) {
next(error);
}
};
refresh = async (req: Request, res: Response, next: NextFunction) => {
try {
const { refreshToken } = req.body;
const tokens = await this.authService.refreshTokens(refreshToken);
res.json(tokens);
} catch (error) {
next(error);
}
};
me = async (req: Request, res: Response, next: NextFunction) => {
try {
// User is attached by auth middleware
const user = req.user!;
res.json({
id: user.id,
email: user.email,
emailVerified: user.emailVerified,
createdAt: user.createdAt,
});
} catch (error) {
next(error);
}
};
}
```
**Step 4: Create Routes**
```typescript
// routes/auth.routes.ts
import { Router } from 'express';
import { AuthController } from '../controllers/AuthController';
import { AuthService } from '../services/AuthService';
import { UserRepository } from '../repositories/UserRepository';
import { SessionRepository } from '../repositories/SessionRepository';
import { authenticate } from '../middlewares/auth.middleware';
import { validateRequest } from '../middlewares/validation.middleware';
import { registerSchema, loginSchema, refreshSchema } from '../schemas/auth.schemas';
const router = Router();
// Initialize dependencies
const userRepository = new UserRepository();
const sessionRepository = new SessionRepository();
const authService = new AuthService(userRepository, sessionRepository);
const authController = new AuthController(authService);
// Public routes
router.post('/register', validateRequest(registerSchema), authController.register);
router.post('/login', validateRequest(loginSchema), authController.login);
router.post('/refresh', validateRequest(refreshSchema), authController.refresh);
// Protected routes
router.post('/logout', authenticate, authController.logout);
router.get('/me', authenticate, authController.me);
export default router;
```
**Step 5: Write Tests**
```typescript
// services/__tests__/AuthService.test.ts
import { AuthService } from '../AuthService';
import { UserRepository } from '../../repositories/UserRepository';
import { SessionRepository } from '../../repositories/SessionRepository';
import { ConflictError, UnauthorizedError, ValidationError } from '../../errors';
describe('AuthService', () => {
let authService: AuthService;
let userRepository: jest.Mocked<UserRepository>;
let sessionRepository: jest.Mocked<SessionRepository>;
beforeEach(() => {
userRepository = {
findByEmail: jest.fn(),
create: jest.fn(),
findById: jest.fn(),
} as any;
sessionRepository = {
create: jest.fn(),
findByTokenHash: jest.fn(),
update: jest.fn(),
deleteByTokenHash: jest.fn(),
} as any;
authService = new AuthService(userRepository, sessionRepository);
});
describe('register', () => {
it('should register new user successfully', async () => {
const input = {
email: 'test@example.com',
password: 'Password123',
};
userRepository.findByEmail.mockResolvedValue(null);
userRepository.create.mockResolvedValue({
id: 'user-id',
email: input.email,
emailVerified: false,
createdAt: new Date(),
updatedAt: new Date(),
} as any);
const result = await authService.register(input);
expect(result.user).toBeDefined();
expect(result.tokens.accessToken).toBeDefined();
expect(result.tokens.refreshToken).toBeDefined();
expect(userRepository.create).toHaveBeenCalled();
expect(sessionRepository.create).toHaveBeenCalled();
});
it('should throw ConflictError if user exists', async () => {
userRepository.findByEmail.mockResolvedValue({ id: 'existing-id' } as any);
await expect(
authService.register({
email: 'existing@example.com',
password: 'Password123',
})
).rejects.toThrow(ConflictError);
});
it('should throw ValidationError for invalid email', async () => {
await expect(
authService.register({
email: 'invalid-email',
password: 'Password123',
})
).rejects.toThrow(ValidationError);
});
it('should throw ValidationError for weak password', async () => {
await expect(
authService.register({
email: 'test@example.com',
password: 'weak',
})
).rejects.toThrow(ValidationError);
});
});
describe('login', () => {
it('should login user successfully', async () => {
const user = {
id: 'user-id',
email: 'test@example.com',
passwordHash: await bcrypt.hash('Password123', 12),
};
userRepository.findByEmail.mockResolvedValue(user as any);
const result = await authService.login({
email: 'test@example.com',
password: 'Password123',
});
expect(result.user).toBeDefined();
expect(result.tokens).toBeDefined();
expect(sessionRepository.create).toHaveBeenCalled();
});
it('should throw UnauthorizedError for invalid credentials', async () => {
userRepository.findByEmail.mockResolvedValue(null);
await expect(
authService.login({
email: 'test@example.com',
password: 'Password123',
})
).rejects.toThrow(UnauthorizedError);
});
});
});
```
```typescript
// controllers/__tests__/AuthController.test.ts
import request from 'supertest';
import { app } from '../../app';
import { UserRepository } from '../../repositories/UserRepository';
describe('AuthController', () => {
let userRepository: UserRepository;
beforeEach(async () => {
await clearDatabase();
userRepository = new UserRepository();
});
describe('POST /api/auth/register', () => {
it('should register new user', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'newuser@example.com',
password: 'Password123',
})
.expect(201);
expect(response.body).toHaveProperty('user');
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
expect(response.body.user.email).toBe('newuser@example.com');
});
it('should return 409 for duplicate email', async () => {
await userRepository.create({
email: 'existing@example.com',
passwordHash: 'hash',
});
await request(app)
.post('/api/auth/register')
.send({
email: 'existing@example.com',
password: 'Password123',
})
.expect(409);
});
it('should return 400 for invalid input', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'invalid-email',
password: 'weak',
})
.expect(400);
});
});
describe('POST /api/auth/login', () => {
it('should login existing user', async () => {
// Create user first
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'Password123',
});
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'Password123',
})
.expect(200);
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
});
it('should return 401 for invalid credentials', async () => {
await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@example.com',
password: 'Password123',
})
.expect(401);
});
});
describe('GET /api/auth/me', () => {
it('should return current user', async () => {
// Register and login
const registerResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'Password123',
});
const { accessToken } = registerResponse.body;
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);
expect(response.body.email).toBe('test@example.com');
});
it('should return 401 without token', async () => {
await request(app)
.get('/api/auth/me')
.expect(401);
});
});
});
```
#### Phase 4.3 - Frontend Layer Implementation
**Step 1: Create API Client**
```typescript
// src/features/auth/api/authApi.ts
import axios, { AxiosInstance } from 'axios';
export interface RegisterRequest {
email: string;
password: string;
name?: string;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface AuthResponse {
user: {
id: string;
email: string;
emailVerified: boolean;
};
accessToken: string;
refreshToken: string;
}
export interface RefreshTokenResponse {
accessToken: string;
refreshToken: string;
}
export interface UserProfile {
id: string;
email: string;
emailVerified: boolean;
createdAt: string;
}
export class AuthApi {
private client: AxiosInstance;
constructor(baseURL: string = '/api') {
this.client = axios.create({
baseURL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor to add auth token
this.client.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor to handle token refresh
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// If 401 and not already retried, try to refresh token
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
const response = await this.refreshTokens({ refreshToken });
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);
// Retry original request with new token
originalRequest.headers.Authorization = `Bearer ${response.accessToken}`;
return this.client(originalRequest);
}
} catch (refreshError) {
// Refresh failed, clear tokens and redirect to login
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
throw refreshError;
}
}
throw error;
}
);
}
async register(data: RegisterRequest): Promise<AuthResponse> {
const response = await this.client.post<AuthResponse>('/auth/register', data);
return response.data;
}
async login(data: LoginRequest): Promise<AuthResponse> {
const response = await this.client.post<AuthResponse>('/auth/login', data);
return response.data;
}
async logout(): Promise<void> {
const refreshToken = localStorage.getItem('refreshToken');
await this.client.post('/auth/logout', { refreshToken });
}
async refreshTokens(data: { refreshToken: string }): Promise<RefreshTokenResponse> {
const response = await this.client.post<RefreshTokenResponse>('/auth/refresh', data);
return response.data;
}
async getCurrentUser(): Promise<UserProfile> {
const response = await this.client.get<UserProfile>('/auth/me');
return response.data;
}
}
export const authApi = new AuthApi();
```
**Step 2: Create React Hooks**
```typescript
// src/features/auth/hooks/useAuth.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { authApi } from '../api/authApi';
interface User {
id: string;
email: string;
emailVerified: boolean;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
login: (email: string, password: string) => Promise<void>;
register: (email: string, password: string, name?: string) => Promise<void>;
logout: () => Promise<void>;
refreshUser: () => Promise<void>;
clearError: () => void;
}
export const useAuth = create<AuthState>()(
persist(
(set, get) => ({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
login: async (email: string, password: string) => {
set({ isLoading: true, error: null });
try {
const response = await authApi.login({ email, password });
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);
set({
user: response.user,
isAuthenticated: true,
isLoading: false,
});
} catch (error: any) {
const errorMessage = error.response?.data?.error?.message || 'Login failed';
set({ error: errorMessage, isLoading: false });
throw error;
}
},
register: async (email: string, password: string, name?: string) => {
set({ isLoading: true, error: null });
try {
const response = await authApi.register({ email, password, name });
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);
set({
user: response.user,
isAuthenticated: true,
isLoading: false,
});
} catch (error: any) {
const errorMessage = error.response?.data?.error?.message || 'Registration failed';
set({ error: errorMessage, isLoading: false });
throw error;
}
},
logout: async () => {
set({ isLoading: true });
try {
await authApi.logout();
} catch (error) {
console.error('Logout error:', error);
} finally {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
set({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
});
}
},
refreshUser: async () => {
if (!get().isAuthenticated) return;
try {
const user = await authApi.getCurrentUser();
set({ user });
} catch (error) {
console.error('Failed to refresh user:', error);
}
},
clearError: () => set({ error: null }),
}),
{
name: 'auth-storage',
partialize: (state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
}
)
);
```
**Step 3: Create Components**
```typescript
// src/features/auth/components/LoginForm.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuth } from '../hooks/useAuth';
import { useNavigate } from 'react-router-dom';
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type LoginFormData = z.infer<typeof loginSchema>;
export const LoginForm: React.FC = () => {
const { login, isLoading, error, clearError } = useAuth();
const navigate = useNavigate();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginFormData) => {
try {
clearError();
await login(data.email, data.password);
navigate('/dashboard');
} catch (error) {
// Error is handled by the store
}
};
return (
<div className="w-full max-w-md mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">Login</h2>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{error && (
<div className="p-3 bg-red-50 border border-red-200 text-red-700 rounded">
{error}
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
{...register('email')}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
)}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Password
</label>
<input
id="password"
type="password"
{...register('password')}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">{errors.password.message}</p>
)}
</div>
<button
type="submit"
disabled={isLoading}
className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{isLoading ? 'Logging in...' : 'Login'}
</button>
</form>
</div>
);
};
```
```typescript
// src/features/auth/components/RegisterForm.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuth } from '../hooks/useAuth';
import { useNavigate } from 'react-router-dom';
const registerSchema = z.object({
email: z.string().email('Invalid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number'),
confirmPassword: z.string(),
name: z.string().optional(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type RegisterFormData = z.infer<typeof registerSchema>;
export const RegisterForm: React.FC = () => {
const { register: registerUser, isLoading, error, clearError } = useAuth();
const navigate = useNavigate();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema),
});
const onSubmit = async (data: RegisterFormData) => {
try {
clearError();
await registerUser(data.email, data.password, data.name);
navigate('/dashboard');
} catch (error) {
// Error is handled by the store
}
};
return (
<div className="w-full max-w-md mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">Create Account</h2>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{error && (
<div className="p-3 bg-red-50 border border-red-200 text-red-700 rounded">
{error}
</div>
)}
<div>
<label htmlFor="name" className="block text-sm font-medium mb-1">
Name (Optional)
</label>
<input
id="name"
type="text"
{...register('name')}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
{...register('email')}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
)}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Password
</label>
<input
id="password"
type="password"
{...register('password')}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">{errors.password.message}</p>
)}
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium mb-1">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
{...register('confirmPassword')}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{errors.confirmPassword && (
<p className="mt-1 text-sm text-red-600">{errors.confirmPassword.message}</p>
)}
</div>
<button
type="submit"
disabled={isLoading}
className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{isLoading ? 'Creating account...' : 'Register'}
</button>
</form>
</div>
);
};
```
```typescript
// src/features/auth/components/ProtectedRoute.tsx
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
export const ProtectedRoute: React.FC = () => {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-lg">Loading...</div>
</div>
);
}
return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />;
};
```
**Step 4: Write Component Tests**
```typescript
// src/features/auth/components/__tests__/LoginForm.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { LoginForm } from '../LoginForm';
import { useAuth } from '../../hooks/useAuth';
import { BrowserRouter } from 'react-router-dom';
jest.mock('../../hooks/useAuth');
const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;
describe('LoginForm', () => {
const mockLogin = jest.fn();
const mockClearError = jest.fn();
beforeEach(() => {
mockUseAuth.mockReturnValue({
login: mockLogin,
isLoading: false,
error: null,
clearError: mockClearError,
} as any);
});
afterEach(() => {
jest.clearAllMocks();
});
const renderLoginForm = () => {
return render(
<BrowserRouter>
<LoginForm />
</BrowserRouter>
);
};
it('should render login form', () => {
renderLoginForm();
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
});
it('should show validation errors for invalid input', async () => {
renderLoginForm();
const submitButton = screen.getByRole('button', { name: /login/i });
fireEvent.click(submitButton);
await waitFor(() => {
expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
});
});
it('should call login with valid credentials', async () => {
mockLogin.mockResolvedValue(undefined);
renderLoginForm();
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' },
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'Password123' },
});
const submitButton = screen.getByRole('button', { name: /login/i });
fireEvent.click(submitButton);
await waitFor(() => {
expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'Password123');
});
});
it('should display error message on login failure', () => {
mockUseAuth.mockReturnValue({
login: mockLogin,
isLoading: false,
error: 'Invalid credentials',
clearError: mockClearError,
} as any);
renderLoginForm();
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
});
it('should disable form during loading', () => {
mockUseAuth.mockReturnValue({
login: mockLogin,
isLoading: true,
error: null,
clearError: mockClearError,
} as any);
renderLoginForm();
expect(screen.getByLabelText(/email/i)).toBeDisabled();
expect(screen.getByLabelText(/password/i)).toBeDisabled();
expect(screen.getByRole('button', { name: /logging in/i })).toBeDisabled();
});
});
```
#### Phase 4.4 - Integration & Polish
**Step 1: End-to-End Tests**
```typescript
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
});
test('should complete full registration flow', async ({ page }) => {
// Navigate to register page
await page.click('text=Register');
// Fill registration form
await page.fill('input[name="name"]', 'Test User');
await page.fill('input[name="email"]', `test-${Date.now()}@example.com`);
await page.fill('input[name="password"]', 'Password123');
await page.fill('input[name="confirmPassword"]', 'Password123');
// Submit form
await page.click('button:has-text("Register")');
// Should redirect to dashboard
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.locator('text=Welcome')).toBeVisible();
});
test('should complete full login flow', async ({ page }) => {
// Assume user already registered
await page.click('text=Login');
// Fill login form
await page.fill('input[name="email"]', 'existing@example.com');
await page.fill('input[name="password"]', 'Password123');
// Submit form
await page.click('button:has-text("Login")');
// Should redirect to dashboard
await expect(page).toHaveURL(/\/dashboard/);
});
test('should handle logout correctly', async ({ page }) => {
// Login first
await page.click('text=Login');
await page.fill('input[name="email"]', 'existing@example.com');
await page.fill('input[name="password"]', 'Password123');
await page.click('button:has-text("Login")');
await expect(page).toHaveURL(/\/dashboard/);
// Logout
await page.click('button:has-text("Logout")');
// Should redirect to home/login
await expect(page).toHaveURL(/\/(login)?$/);
});
test('should protect routes when not authenticated', async ({ page }) => {
// Try to access protected route
await page.goto('http://localhost:3000/dashboard');
// Should redirect to login
await expect(page).toHaveURL(/\/login/);
});
test('should refresh token automatically', async ({ page, context }) => {
// Login
await page.click('text=Login');
await page.fill('input[name="email"]', 'existing@example.com');
await page.fill('input[name="password"]', 'Password123');
await page.click('button:has-text("Login")');
// Get initial access token
const initialStorage = await context.storageState();
const initialToken = initialStorage.origins[0]?.localStorage.find(
(item) => item.name === 'accessToken'
)?.value;
// Wait for token to expire (in real scenario, this would be 15 minutes)
// For testing, you might mock the token expiration
await page.waitForTimeout(16 * 60 * 1000); // 16 minutes
// Make an API request that should trigger token refresh
await page.reload();
// Get new access token
const newStorage = await context.storageState();
const newToken = newStorage.origins[0]?.localStorage.find(
(item) => item.name === 'accessToken'
)?.value;
// Tokens should be different
expect(newToken).not.toBe(initialToken);
});
});
```
**Step 2: Performance Optimization**
```typescript
// Performance considerations for authentication feature
// 1. Database Indexes (already included in migration)
// - idx_users_email for login lookups
// - idx_user_sessions_user_id for session queries
// - idx_user_sessions_token_hash for token validation
// - idx_user_sessions_expires_at for cleanup queries
// 2. Caching Strategy
// Example: Redis caching for user sessions
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
// Cache user data after login
async function cacheUserSession(userId: string, userData: any) {
await redis.setex(
`user:${userId}`,
15 * 60, // 15 minutes (access token lifetime)
JSON.stringify(userData)
);
}
// Get cached user data
async function getCachedUser(userId: string) {
const cached = await redis.get(`user:${userId}`);
return cached ? JSON.parse(cached) : null;
}
// 3. Password Hashing Optimization
// Use bcrypt with work factor 12 (balance security and performance)
const BCRYPT_ROUNDS = 12;
// 4. Token Generation Optimization
// Use JWT with short expiration for access tokens (15 minutes)
// Use longer expiration for refresh tokens (7 days)
// 5. Connection Pooling
// Configure database connection pool
{
type: 'postgres',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
extra: {
max: 20, // Maximum pool size
min: 5, // Minimum pool size
idle: 10000, // Idle timeout
acquire: 30000, // Acquire timeout
}
}
// 6. Frontend Optimization
// - Use React.memo for authentication components
// - Lazy load protected routes
// - Implement request deduplication
// Example: Lazy loading
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));
// Example: Request deduplication
import { useQuery } from '@tanstack/react-query';
function useCurrentUser() {
return useQuery({
queryKey: ['currentUser'],
queryFn: () => authApi.getCurrentUser(),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
});
}
```
**Step 3: Security Hardening**
```typescript
// Security measures for authentication feature
// 1. Rate Limiting
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 requests per window
message: 'Too many login attempts, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/auth/login', loginLimiter);
// 2. Input Sanitization
import validator from 'validator';
import xss from 'xss';
function sanitizeInput(input: string): string {
return xss(validator.trim(input));
}
// 3. SQL Injection Prevention (using parameterized queries with TypeORM)
// TypeORM automatically uses parameterized queries
// 4. XSS Prevention
// - Set security headers
import helmet from 'helmet';
app.use(helmet());
// - Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
},
}));
// 5. CSRF Protection
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
// 6. Secure Cookie Configuration
app.use(session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
}));
// 7. Password Policy Enforcement
const PASSWORD_REQUIREMENTS = {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumber: true,
requireSpecialChar: false, // Optional
};
// 8. Account Lockout (after failed attempts)
async function checkAccountLockout(email: string): Promise<boolean> {
const attempts = await redis.get(`login_attempts:${email}`);
if (attempts && parseInt(attempts) >= 5) {
const ttl = await redis.ttl(`login_attempts:${email}`);
if (ttl > 0) {
throw new Error(`Account locked. Try again in ${Math.ceil(ttl / 60)} minutes`);
}
}
return false;
}
async function recordFailedLogin(email: string): Promise<void> {
const key = `login_attempts:${email}`;
const attempts = await redis.incr(key);
if (attempts === 1) {
await redis.expire(key, 15 * 60); // 15 minutes
}
}
// 9. Session Management
// - Implement session rotation on privilege escalation
// - Invalidate sessions on password change
// - Implement "logout all devices" functionality
async function invalidateAllUserSessions(userId: string): Promise<void> {
await sessionRepository.deleteAllByUserId(userId);
await redis.del(`user:${userId}`);
}
```
**Step 4: Documentation**
```markdown
# Authentication Feature Documentation
## Overview
Complete user authentication system with email/password login, JWT tokens, session management, and secure password handling.
## Features
- User registration with email verification
- Login with email and password
- JWT-based authentication (access + refresh tokens)
- Token refresh mechanism
- Secure session management
- Password hashing with bcrypt
- Rate limiting on authentication endpoints
- CSRF protection
- Account lockout after failed attempts
## API Endpoints
### POST /api/auth/register
Register a new user account.
**Request Body:**
```json
{
"email": "user@example.com",
"password": "SecurePassword123",
"name": "John Doe"
}
```
**Response (201 Created):**
```json
{
"user": {
"id": "uuid",
"email": "user@example.com",
"emailVerified": false
},
"accessToken": "jwt-access-token",
"refreshToken": "jwt-refresh-token"
}
```
**Error Responses:**
- 400 Bad Request - Invalid input
- 409 Conflict - Email already exists
### POST /api/auth/login
Login with existing credentials.
**Request Body:**
```json
{
"email": "user@example.com",
"password": "SecurePassword123"
}
```
**Response (200 OK):**
```json
{
"user": {
"id": "uuid",
"email": "user@example.com",
"emailVerified": true
},
"accessToken": "jwt-access-token",
"refreshToken": "jwt-refresh-token"
}
```
**Error Responses:**
- 401 Unauthorized - Invalid credentials
- 429 Too Many Requests - Rate limit exceeded
### POST /api/auth/refresh
Refresh access token using refresh token.
**Request Body:**
```json
{
"refreshToken": "jwt-refresh-token"
}
```
**Response (200 OK):**
```json
{
"accessToken": "new-jwt-access-token",
"refreshToken": "new-jwt-refresh-token"
}
```
### POST /api/auth/logout
Logout and invalidate current session.
**Headers:**
```
Authorization: Bearer {accessToken}
```
**Request Body:**
```json
{
"refreshToken": "jwt-refresh-token"
}
```
**Response (204 No Content)**
### GET /api/auth/me
Get current user profile.
**Headers:**
```
Authorization: Bearer {accessToken}
```
**Response (200 OK):**
```json
{
"id": "uuid",
"email": "user@example.com",
"emailVerified": true,
"createdAt": "2024-01-01T00:00:00Z"
}
```
## Frontend Usage
### Using the Auth Hook
```typescript
import { useAuth } from '@/features/auth/hooks/useAuth';
function MyComponent() {
const { user, isAuthenticated, login, logout } = useAuth();
const handleLogin = async () => {
try {
await login('user@example.com', 'password');
// User is now logged in
} catch (error) {
// Handle error
}
};
return (
<div>
{isAuthenticated ? (
<>
<p>Welcome, {user?.email}</p>
<button onClick={logout}>Logout</button>
</>
) : (
<button onClick={handleLogin}>Login</button>
)}
</div>
);
}
```
### Protecting Routes
```typescript
import { ProtectedRoute } from '@/features/auth/components/ProtectedRoute';
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
```
## Security Considerations
1. **Password Requirements:**
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
2. **Token Lifetimes:**
- Access token: 15 minutes
- Refresh token: 7 days
3. **Rate Limiting:**
- Login: 5 attempts per 15 minutes
- Register: 3 attempts per hour
4. **Session Management:**
- Sessions are invalidated on logout
- Sessions are automatically cleaned up after expiration
- Multiple sessions per user are allowed
## Testing
Run tests with:
```bash
# Unit tests
npm run test
# Integration tests
npm run test:integration
# E2E tests
npm run test:e2e
```
## Environment Variables
```env
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=myapp
# JWT
JWT_SECRET=your-secret-key-change-in-production
# Session
SESSION_SECRET=your-session-secret
# Redis (for rate limiting and caching)
REDIS_URL=redis://localhost:6379
# SMTP (for email verification)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASSWORD=your-smtp-password
```
## Deployment
1. Run migrations:
```bash
npm run migrate
```
2. Build the application:
```bash
npm run build
```
3. Start the server:
```bash
npm start
```
## Troubleshooting
### Token expired error
If you receive "Token expired" errors, ensure:
- The JWT_SECRET is correctly set
- System clocks are synchronized
- Token refresh is working correctly
### Cannot login
If login fails:
- Verify database connection
- Check password hashing (bcrypt rounds)
- Verify rate limiting isn't blocking requests
- Check error logs for details
### CORS issues
If frontend cannot connect to backend:
- Verify CORS configuration in backend
- Ensure credentials are included in requests
- Check allowed origins match frontend URL
```
## Output Format
Provide complete implementation organized by layer:
```markdown
# Feature Implementation: {Feature Name}
## Overview
{Brief description and purpose}
## Implementation Summary
{High-level approach and key decisions}
## Phase 1: Database Layer
### Migration Script
{SQL migration with up/down}
### Models/Schemas
{Data models with relationships}
### Testing
{Database operation tests}
## Phase 2: Backend Layer
### Data Access Layer
{Repositories and queries}
### Business Logic Layer
{Services with business rules}
### API Layer
{Controllers and routes}
### API Documentation
{Endpoint specs with examples}
### Testing
{Unit and integration tests}
## Phase 3: Frontend Layer
### API Client
{HTTP client with interceptors}
### State Management
{Hooks and stores}
### Components
{React/Vue/Angular components}
### Testing
{Component tests}
## Phase 4: Integration & Polish
### E2E Tests
{End-to-end test scenarios}
### Performance Optimizations
{Caching, indexing, lazy loading}
### Security Hardening
{Rate limiting, validation, CSRF}
### Documentation
{API docs, usage guide, troubleshooting}
## Configuration Changes
### Environment Variables
{New env vars needed}
### Dependencies
{New packages to install}
## Deployment Considerations
{Migration steps, monitoring, rollback}
## Follow-up Tasks
{Future improvements}
```
## Quality Checklist
Before considering the feature complete:
- [ ] All layers implemented (database, backend, frontend)
- [ ] Database migrations tested (up and down)
- [ ] Unit tests written and passing (>80% coverage)
- [ ] Integration tests written and passing
- [ ] E2E tests for critical flows
- [ ] Error handling comprehensive
- [ ] Validation on both frontend and backend
- [ ] Security measures implemented (auth, rate limiting, CSRF)
- [ ] Performance optimized (indexes, caching, lazy loading)
- [ ] Accessibility considered (WCAG 2.1 AA)
- [ ] Documentation complete (API docs, usage guide)
- [ ] No hardcoded secrets or sensitive data
- [ ] Code follows project conventions
- [ ] Ready for code review
## Error Handling
**Unclear Requirements:**
- Ask specific questions about acceptance criteria
- Request clarification on edge cases
- Provide examples to confirm understanding
- Suggest sensible defaults if context missing
**Missing Context:**
- List needed information (tech stack, patterns)
- Attempt to discover from codebase
- Document assumptions made
- Provide alternatives if context unclear
**Implementation Blockers:**
- Clearly identify the blocker
- Suggest alternative approaches
- Provide workarounds if available
- Document issue for resolution
- Continue with unblocked portions
## Examples by Feature Type
### Real-time Features (WebSocket/SSE)
- WebSocket connection management
- Event broadcasting and subscriptions
- Presence tracking
- Conflict resolution
- Reconnection handling
### Payment Features (Stripe/PayPal)
- Checkout flow integration
- Webhook handling
- Payment method management
- Subscription management
- Invoice generation
- Refund processing
### File Upload Features
- Multipart form handling
- File validation (type, size)
- Storage integration (S3, GCS)
- Progress tracking
- Image optimization
- Virus scanning
### Search Features
- Full-text search implementation
- Filter and sorting
- Pagination
- Search suggestions
- Relevance scoring
- Query optimization