diff --git a/CLAUDE.md b/CLAUDE.md index aa779b7..baee65e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -198,9 +198,44 @@ The repository pattern allows swapping databases: - Multiple calendars - CalDAV synchronization with external services +## Development Environment + +### MongoDB (Docker) +```bash +cd apps/server/docker/mongo +docker compose up -d # Start MongoDB + Mongo Express +docker compose down # Stop services +``` +- MongoDB: `localhost:27017` (root/mongoose) +- Mongo Express UI: `localhost:8081` (admin/admin) + +### Environment Variables +Server requires `.env` file in `apps/server/`: +``` +JWT_SECRET=your-secret-key +JWT_EXPIRES_IN=1h +MONGODB_URI=mongodb://root:mongoose@localhost:27017/calchat?authSource=admin +``` + ## Current Implementation Status -**Backend:** Skeleton complete - all files exist with `throw new Error('Not implemented')` placeholders. Ready for step-by-step implementation. +**Backend:** +- **Implemented:** + - `AuthController`: login(), register() with error handling + - `AuthService`: login(), register() with password validation + - `MongoUserRepository`: findByEmail(), create() + - `utils/password`: hash(), compare() using bcrypt + - `utils/jwt`: signToken() (verifyToken() pending) + - `dotenv` integration for environment variables +- **Stubbed (TODO):** + - `AuthMiddleware.authenticate()`: Currently uses fake user for testing + - `AuthController`: refresh(), logout() + - `AuthService`: refreshToken() + - All Chat and Event functionality +- **Not started:** + - `ChatController`, `ChatService`, `MongoChatRepository` + - `EventController`, `EventService`, `MongoEventRepository` + - `ClaudeAdapter` (AI integration) **Shared:** Types and DTOs defined and exported. @@ -216,6 +251,7 @@ The repository pattern allows swapping databases: ## Documentation Detailed architecture diagrams are in `docs/`: +- `api-routes.md` - API endpoint overview (German) - `technisches_brainstorm.tex` - Technical concept document (German) - `architecture-class-diagram.puml` - Backend class diagram - `frontend-class-diagram.puml` - Frontend class diagram diff --git a/apps/server/.gitignore b/apps/server/.gitignore index 1521c8b..637e765 100644 --- a/apps/server/.gitignore +++ b/apps/server/.gitignore @@ -1 +1,2 @@ dist +.env diff --git a/apps/server/docker/mongo/compose.yml b/apps/server/docker/mongo/compose.yml new file mode 100644 index 0000000..4afdf4a --- /dev/null +++ b/apps/server/docker/mongo/compose.yml @@ -0,0 +1,33 @@ +services: + mongo: + image: mongo:8 + restart: always + ports: + - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: mongoose + volumes: + - mongo-data:/data/db + healthcheck: + test: mongosh --eval "db.adminCommand('ping')" + interval: 10s + timeout: 5s + retries: 5 + + mongo-express: + image: mongo-express:latest + restart: always + ports: + - "8081:8081" + environment: + ME_CONFIG_MONGODB_URL: mongodb://root:mongoose@mongo:27017/ + ME_CONFIG_BASICAUTH_ENABLED: true + ME_CONFIG_BASICAUTH_USERNAME: admin + ME_CONFIG_BASICAUTH_PASSWORD: admin + depends_on: + mongo: + condition: service_healthy + +volumes: + mongo-data: diff --git a/apps/server/package.json b/apps/server/package.json index 0cc6599..264c285 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -11,6 +11,7 @@ "@anthropic-ai/sdk": "^0.71.2", "@caldav/shared": "*", "bcrypt": "^6.0.0", + "dotenv": "^16.4.7", "express": "^5.2.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.1.1" diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index f3d2823..5b6c368 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -1,5 +1,6 @@ import express from 'express'; import mongoose from 'mongoose'; +import 'dotenv/config' import { createRoutes } from './routes'; import { AuthController, ChatController, EventController } from './controllers'; diff --git a/apps/server/src/controllers/AuthController.ts b/apps/server/src/controllers/AuthController.ts index b1f6a1d..8393230 100644 --- a/apps/server/src/controllers/AuthController.ts +++ b/apps/server/src/controllers/AuthController.ts @@ -5,11 +5,21 @@ export class AuthController { constructor(private authService: AuthService) {} async login(req: Request, res: Response): Promise { - throw new Error('Not implemented'); + try { + const result = await this.authService.login(req.body); + res.json(result); + } catch (error) { + res.status(401).json({ error: (error as Error).message }); + } } async register(req: Request, res: Response): Promise { - throw new Error('Not implemented'); + try { + const result = await this.authService.register(req.body); + res.status(201).json(result); + } catch (error) { + res.status(400).json({ error: (error as Error).message }); + } } async refresh(req: Request, res: Response): Promise { diff --git a/apps/server/src/middleware/AuthMiddleware.ts b/apps/server/src/middleware/AuthMiddleware.ts index 7fda4dd..c52bc21 100644 --- a/apps/server/src/middleware/AuthMiddleware.ts +++ b/apps/server/src/middleware/AuthMiddleware.ts @@ -6,5 +6,11 @@ export interface AuthenticatedRequest extends Request { } export function authenticate(req: AuthenticatedRequest, res: Response, next: NextFunction): void { - throw new Error('Not implemented'); + // TODO: Implement real JWT verification + // Fake user for testing purposes + req.user = { + userId: 'fake-user-id', + email: 'test@example.com', + }; + next(); } diff --git a/apps/server/src/repositories/mongo/MongoUserRepository.ts b/apps/server/src/repositories/mongo/MongoUserRepository.ts index 72e64ca..11b19bd 100644 --- a/apps/server/src/repositories/mongo/MongoUserRepository.ts +++ b/apps/server/src/repositories/mongo/MongoUserRepository.ts @@ -8,10 +8,12 @@ export class MongoUserRepository implements UserRepository { } async findByEmail(email: string): Promise { - throw new Error('Not implemented'); + const user = await UserModel.findOne({ email: email.toLowerCase() }); + return user ? user.toJSON() as User : null; } async create(data: CreateUserData): Promise { - throw new Error('Not implemented'); + const user = await UserModel.create(data); + return user.toJSON() as User; } } diff --git a/apps/server/src/services/AuthService.ts b/apps/server/src/services/AuthService.ts index 1e3da85..2a20496 100644 --- a/apps/server/src/services/AuthService.ts +++ b/apps/server/src/services/AuthService.ts @@ -7,11 +7,33 @@ export class AuthService { constructor(private userRepo: UserRepository) {} async login(data: LoginDTO): Promise { - throw new Error('Not implemented'); + const user = await this.userRepo.findByEmail(data.email); + if (!user || !user.passwordHash) { + throw new Error('Invalid credentials'); + } + + const isValid = await password.compare(data.password, user.passwordHash); + if (!isValid) { + throw new Error('Invalid credentials'); + } + + return { user, accessToken: '' }; } async register(data: CreateUserDTO): Promise { - throw new Error('Not implemented'); + const existingUser = await this.userRepo.findByEmail(data.email); + if (existingUser) { + throw new Error('Email already exists'); + } + + const passwordHash = await password.hash(data.password); + const user = await this.userRepo.create({ + email: data.email, + displayName: data.displayName, + passwordHash, + }); + + return { user, accessToken: '' }; } async refreshToken(refreshToken: string): Promise { diff --git a/apps/server/src/utils/password.ts b/apps/server/src/utils/password.ts index 825c3d3..508a567 100644 --- a/apps/server/src/utils/password.ts +++ b/apps/server/src/utils/password.ts @@ -3,9 +3,9 @@ import bcrypt from 'bcrypt'; const SALT_ROUNDS = 10; export async function hash(password: string): Promise { - throw new Error('Not implemented'); + return bcrypt.hash(password, SALT_ROUNDS); } export async function compare(password: string, hash: string): Promise { - throw new Error('Not implemented'); + return bcrypt.compare(password, hash); } diff --git a/docs/api-routes.md b/docs/api-routes.md new file mode 100644 index 0000000..ec5b39f --- /dev/null +++ b/docs/api-routes.md @@ -0,0 +1,54 @@ +# CalChat API Routes + +Base URL: `/api` + +## Authentication + +### Auth Endpoints (`/api/auth`) +Öffentliche Endpoints - keine Authentifizierung erforderlich. + +| Method | Endpoint | Beschreibung | +|--------|----------|--------------| +| POST | `/auth/login` | User Login | +| POST | `/auth/register` | User Registrierung | +| POST | `/auth/refresh` | JWT Token erneuern | +| POST | `/auth/logout` | User Logout | + +--- + +## Events + +### Event Endpoints (`/api/events`) +Alle Endpoints erfordern JWT-Authentifizierung. + +| Method | Endpoint | Beschreibung | +|--------|----------|--------------| +| GET | `/events` | Alle Events des Users abrufen | +| GET | `/events/range` | Events nach Zeitraum filtern | +| GET | `/events/:id` | Einzelnes Event abrufen | +| POST | `/events` | Neues Event erstellen | +| PUT | `/events/:id` | Event aktualisieren | +| DELETE | `/events/:id` | Event löschen | + +--- + +## Chat + +### Chat Endpoints (`/api/chat`) +Alle Endpoints erfordern JWT-Authentifizierung. + +| Method | Endpoint | Beschreibung | +|--------|----------|--------------| +| POST | `/chat/message` | Nachricht an AI senden | +| POST | `/chat/confirm/:conversationId/:messageId` | Vorgeschlagenes Event bestätigen | +| POST | `/chat/reject/:conversationId/:messageId` | Vorgeschlagenes Event ablehnen | +| GET | `/chat/conversations` | Alle Konversationen abrufen | +| GET | `/chat/conversations/:id` | Nachrichten einer Konversation (mit Pagination) | + +--- + +## Health + +| Method | Endpoint | Beschreibung | +|--------|----------|--------------| +| GET | `/health` | Health Check | diff --git a/package-lock.json b/package-lock.json index 41f93fe..68f5aa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "@anthropic-ai/sdk": "^0.71.2", "@caldav/shared": "*", "bcrypt": "^6.0.0", + "dotenv": "^16.4.7", "express": "^5.2.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.1.1"