chore: remove all NestJS backend references, replace with Hono/Bun

- Delete nestjs-backend.md guideline (replaced by hono-server.md)
- Delete Dockerfile.nestjs-base and Dockerfile.nestjs templates
- Delete stale BACKEND_ARCHITECTURE.md doc (NestJS-era, obsolete)
- Update CLAUDE.md, GUIDELINES.md, authentication.md to Hono/Bun first
- Update all app CLAUDE.md files: backend/ → server/, NestJS → Hono+Bun
- Update all app package.json files: @*/backend → @*/server
- Update docs: LOCAL_DEVELOPMENT, PORT_SCHEMA, ENVIRONMENT_VARIABLES,
  DATABASE_MIGRATIONS, MAC_MINI_SERVER, PROJECT_OVERVIEW
- Update scripts: generate-env.mjs, setup-databases.sh, build-app.sh
- Update CI/CD: cd-macmini.yml backend → server paths
- Update Astro docs site: @chat/backend → @chat/server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-31 16:52:25 +02:00
parent 708299b35e
commit ab387b9b3d
43 changed files with 598 additions and 2398 deletions

View file

@ -9,7 +9,7 @@ This directory contains comprehensive guidelines for working in the Mana Univers
| [Code Style](./guidelines/code-style.md) | Formatting, naming conventions, linting rules | | [Code Style](./guidelines/code-style.md) | Formatting, naming conventions, linting rules |
| [Database](./guidelines/database.md) | Drizzle ORM patterns, schema design, migrations | | [Database](./guidelines/database.md) | Drizzle ORM patterns, schema design, migrations |
| [Testing](./guidelines/testing.md) | Jest/Vitest patterns, mock factories, coverage | | [Testing](./guidelines/testing.md) | Jest/Vitest patterns, mock factories, coverage |
| [NestJS Backend](./guidelines/nestjs-backend.md) | Controllers, services, DTOs, modules | | [Hono Server](./guidelines/hono-server.md) | Compute servers (Hono + Bun) |
| [Error Handling](./guidelines/error-handling.md) | Go-style errors, error codes, Result types | | [Error Handling](./guidelines/error-handling.md) | Go-style errors, error codes, Result types |
| [SvelteKit Web](./guidelines/sveltekit-web.md) | Svelte 5 runes, stores, routing | | [SvelteKit Web](./guidelines/sveltekit-web.md) | Svelte 5 runes, stores, routing |
| [Expo Mobile](./guidelines/expo-mobile.md) | React Native, NativeWind, navigation | | [Expo Mobile](./guidelines/expo-mobile.md) | React Native, NativeWind, navigation |
@ -44,7 +44,7 @@ This directory contains comprehensive guidelines for working in the Mana Univers
|-------|------------|-------| |-------|------------|-------|
| **Package Manager** | pnpm 9.15+ | Workspace monorepo | | **Package Manager** | pnpm 9.15+ | Workspace monorepo |
| **Build System** | Turborepo | Parallel task execution | | **Build System** | Turborepo | Parallel task execution |
| **Backend** | NestJS 10-11 | TypeScript, Drizzle ORM | | **Server** | Hono + Bun | TypeScript, Drizzle ORM |
| **Web** | SvelteKit 2 + Svelte 5 | Runes mode only | | **Web** | SvelteKit 2 + Svelte 5 | Runes mode only |
| **Mobile** | Expo SDK 52-54 | React Native, NativeWind | | **Mobile** | Expo SDK 52-54 | React Native, NativeWind |
| **Database** | PostgreSQL | Via Drizzle ORM | | **Database** | PostgreSQL | Via Drizzle ORM |
@ -62,15 +62,15 @@ manacore-monorepo/
├── apps/ # Product applications ├── apps/ # Product applications
│ └── {project}/ │ └── {project}/
│ ├── apps/ │ ├── apps/
│ │ ├── backend/ # NestJS API │ │ ├── server/ # Hono/Bun compute server
│ │ ├── web/ # SvelteKit web │ │ ├── web/ # SvelteKit web
│ │ ├── mobile/ # Expo app │ │ ├── mobile/ # Expo app
│ │ └── landing/ # Astro landing │ │ └── landing/ # Astro landing
│ └── packages/ # Project-specific shared │ └── packages/ # Project-specific shared
├── packages/ # Monorepo-wide shared ├── packages/ # Monorepo-wide shared
│ ├── shared-errors/ # Error codes & Result types │ ├── shared-errors/ # Error codes & Result types
│ ├── shared-nestjs-auth/ # NestJS auth guards
│ ├── shared-auth/ # Client auth service │ ├── shared-auth/ # Client auth service
│ ├── local-store/ # Local-first data layer (Dexie.js + sync)
│ └── ... │ └── ...
├── services/ # Standalone microservices ├── services/ # Standalone microservices
│ └── mana-core-auth/ # Central auth service │ └── mana-core-auth/ # Central auth service
@ -114,7 +114,8 @@ See [Error Handling](./guidelines/error-handling.md) for complete details.
# Development # Development
pnpm install # Install dependencies pnpm install # Install dependencies
pnpm {project}:dev # Start project (all apps) pnpm {project}:dev # Start project (all apps)
pnpm dev:{project}:backend # Start just backend pnpm dev:{project}:server # Start just Hono server
pnpm dev:{project}:local # Start sync + server + web (no auth)
pnpm dev:{project}:web # Start just web pnpm dev:{project}:web # Start just web
# Quality # Quality
@ -122,7 +123,7 @@ pnpm type-check # TypeScript validation
pnpm format # Format code pnpm format # Format code
pnpm test # Run tests pnpm test # Run tests
# Database # Database (for apps with Drizzle in server)
pnpm {project}:db:push # Push schema changes pnpm --filter @{project}/server db:push # Push schema changes
pnpm {project}:db:studio # Open Drizzle Studio pnpm --filter @{project}/server db:studio # Open Drizzle Studio
``` ```

View file

@ -8,8 +8,8 @@ All authentication is handled by **Mana Core Auth**, a centralized authenticatio
``` ```
┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Web/Mobile │────>│ Backend API │────>│ mana-core-auth │ Web/Mobile │────>│ Compute Server │────>│ mana-auth
│ Client │ │ (NestJS) │ │ (port 3001) │ │ Client │ │ (Hono/Bun) │ │ (port 3001) │
└─────────────────┘ └─────────────────┘ └──────────────────┘ └─────────────────┘ └─────────────────┘ └──────────────────┘
│ │ │ │ │ │
│ 1. Login │ │ │ 1. Login │ │
@ -85,101 +85,48 @@ Always use `text` type for `user_id` columns in all database schemas.
| Package | Purpose | Use Case | | Package | Purpose | Use Case |
|---------|---------|----------| |---------|---------|----------|
| `@manacore/shared-nestjs-auth` | NestJS guards/decorators | Backend APIs | | `@manacore/shared-hono` | Hono auth middleware + helpers | All compute servers (Hono/Bun) |
| `@mana-core/nestjs-integration` | Auth + Credits integration | Backends with credits |
| `@manacore/shared-auth` | Client auth service | Web/Mobile apps | | `@manacore/shared-auth` | Client auth service | Web/Mobile apps |
| `@mana-core/nestjs-integration` | Auth + Credits for NestJS | `@arcade/backend` only |
## Backend Integration ## Server Integration (Hono/Bun)
### Option 1: Simple Auth Only All compute servers use `@manacore/shared-hono`:
Use `@manacore/shared-nestjs-auth` for JWT validation:
```typescript ```typescript
// app.module.ts import { Hono } from 'hono';
import { Module } from '@nestjs/common'; import { authMiddleware, healthRoute, errorHandler, notFoundHandler } from '@manacore/shared-hono';
import { ConfigModule } from '@nestjs/config';
@Module({ const app = new Hono();
imports: [ app.onError(errorHandler);
ConfigModule.forRoot({ isGlobal: true }), app.notFound(notFoundHandler);
// No auth module needed - guards handle it app.route('/health', healthRoute('my-server'));
],
}) // Protect all /api/* routes
export class AppModule {} app.use('/api/*', authMiddleware());
// Access user in route handlers
app.get('/api/v1/data', (c) => {
const userId = c.get('userId'); // Better Auth user ID (not UUID)
const email = c.get('userEmail');
return c.json({ userId });
});
``` ```
```typescript ### NestJS (arcade only)
// file.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('files') `@arcade/backend` still uses NestJS with `@mana-core/nestjs-integration`:
@UseGuards(JwtAuthGuard) // Apply to all routes
export class FileController {
@Get()
async listFiles(@CurrentUser() user: CurrentUserData) {
// user.userId, user.email, user.role available
return this.fileService.findAll(user.userId);
}
}
```
### Option 2: Auth + Credits
Use `@mana-core/nestjs-integration` for full integration:
```typescript ```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ManaCoreModule } from '@mana-core/nestjs-integration';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ManaCoreModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
appId: config.get('APP_ID'),
serviceKey: config.get('MANA_CORE_SERVICE_KEY'),
debug: config.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
```
```typescript
// generation.controller.ts
import { Controller, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@mana-core/nestjs-integration/guards'; import { AuthGuard } from '@mana-core/nestjs-integration/guards';
import { CurrentUser } from '@mana-core/nestjs-integration/decorators'; import { CurrentUser } from '@mana-core/nestjs-integration/decorators';
import { CreditClientService } from '@mana-core/nestjs-integration';
@Controller('generations') @Controller('api')
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
export class GenerationController { export class ApiController {
constructor(private creditClient: CreditClientService) {} @Get('data')
getData(@CurrentUser() user: any) {
@Post() return this.service.findAll(user.sub);
async generate(@CurrentUser() user: any, @Body() dto: GenerateDto) {
// Check and consume credits
const result = await this.creditClient.consumeCredits(
user.sub,
'ai_generation',
10,
'AI image generation'
);
if (!result.ok) {
throw new AppException(result.error);
}
// Proceed with generation
return this.generationService.generate(user.sub, dto);
} }
} }
``` ```
@ -187,7 +134,7 @@ export class GenerationController {
## Environment Variables ## Environment Variables
```env ```env
# Required for all backends # Required for all servers
MANA_CORE_AUTH_URL=http://localhost:3001 MANA_CORE_AUTH_URL=http://localhost:3001
# Development bypass (optional) # Development bypass (optional)

View file

@ -236,13 +236,12 @@ All SvelteKit web apps use **Phosphor icons** via `@manacore/shared-icons` (re-e
- **Ideal**: 10-25 lines - **Ideal**: 10-25 lines
- Extract complex logic into helper functions - Extract complex logic into helper functions
### Module Structure (NestJS) ### Module Structure (Hono/Bun Server)
``` ```
feature/ feature/
├── feature.controller.ts # HTTP layer ├── feature.routes.ts # HTTP routes
├── feature.service.ts # Business logic ├── feature.service.ts # Business logic
├── feature.module.ts # DI configuration
├── feature.spec.ts # Tests ├── feature.spec.ts # Tests
└── dto/ └── dto/
├── create-feature.dto.ts ├── create-feature.dto.ts
@ -340,5 +339,5 @@ pnpm format
pnpm format:check pnpm format:check
# Format specific project # Format specific project
pnpm --filter @chat/backend format pnpm --filter @chat/server format
``` ```

View file

@ -0,0 +1,137 @@
# Hono Server Guidelines
## Overview
All app compute servers use Hono + Bun with a lightweight architecture. Servers handle only what can't run client-side: file uploads (S3), AI calls, RRULE expansion, external API integration, etc. All CRUD is handled client-side via local-first (Dexie.js + mana-sync).
## Project Structure
```
apps/{project}/apps/server/
├── src/
│ ├── index.ts # Entry point + route mounting
│ ├── routes/ # Route handlers
│ │ ├── {feature}.ts
│ │ └── admin.ts
│ ├── lib/ # Shared utilities
│ └── db/ # Drizzle schema (if needed)
│ ├── schema/
│ ├── connection.ts
│ └── drizzle.config.ts
├── package.json
└── tsconfig.json
```
## Entry Point (index.ts)
```typescript
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { authMiddleware, healthRoute, errorHandler, notFoundHandler } from '@manacore/shared-hono';
const PORT = parseInt(process.env.PORT || '3031', 10);
const CORS_ORIGINS = (process.env.CORS_ORIGINS || 'http://localhost:5173').split(',');
const app = new Hono();
// Standard middleware stack
app.onError(errorHandler);
app.notFound(notFoundHandler);
app.use('*', cors({ origin: CORS_ORIGINS, credentials: true }));
app.route('/health', healthRoute('my-server'));
app.use('/api/*', authMiddleware());
// Routes
app.route('/api/v1/compute', computeRoutes);
export default { port: PORT, fetch: app.fetch };
```
## Shared Hono Package
Use `@manacore/shared-hono` for consistent middleware across all servers:
```typescript
import {
authMiddleware, // JWT validation via mana-auth JWKS
healthRoute, // Standard /health endpoint
errorHandler, // Global error handler with logging
notFoundHandler, // 404 handler
} from '@manacore/shared-hono';
```
## Route Handlers
```typescript
// src/routes/upload.ts
import { Hono } from 'hono';
export const uploadRoutes = new Hono();
uploadRoutes.post('/avatar', async (c) => {
const userId = c.get('userId'); // Set by authMiddleware
const formData = await c.req.formData();
const file = formData.get('file') as File | null;
if (!file) return c.json({ error: 'No file' }, 400);
if (file.size > 5 * 1024 * 1024) return c.json({ error: 'Max 5MB' }, 400);
// ... handle upload
return c.json({ url: result.url }, 201);
});
```
## Database (Optional)
Only servers that need their own database use Drizzle. Most apps rely on mana-sync for data persistence.
**Servers with Drizzle:** chat, todo, moodlit, context, planta, presi, traces, uload, wisekeep, news
**Servers without Drizzle (mana-sync only):** calendar, contacts, manadeck, mukke, nutriphi, picture, questions, storage
## Running Servers
```bash
# Development (with watch)
cd apps/{project}/apps/server && bun run --watch src/index.ts
# Via root scripts
pnpm dev:{project}:server # Just the server
pnpm dev:{project}:local # sync + server + web (no auth needed)
pnpm dev:{project}:full # auth + sync + server + web
```
## When to Add a Server
Add a Hono server when the app needs:
- **File operations**: S3 uploads, image processing
- **AI/LLM calls**: Gemini, OpenAI, etc. (API keys can't be client-side)
- **External APIs**: Google Calendar sync, vCard parsing, etc.
- **Heavy compute**: RRULE expansion, PDF generation, etc.
- **Admin endpoints**: GDPR compliance, data export
Do NOT add a server for pure CRUD — use local-first + mana-sync instead.
## Environment Variables
Servers read `.env` from their directory (Bun auto-loads it):
```env
PORT=3031
CORS_ORIGINS=http://localhost:5173,http://localhost:5188
MANA_CORE_AUTH_URL=http://localhost:3001
DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/myapp # if Drizzle
```
## Key Differences from Old NestJS Pattern
| Aspect | Old (NestJS) | New (Hono + Bun) |
| ----------- | ----------------------- | ------------------------------------- |
| Runtime | Node.js | Bun |
| Framework | NestJS (decorators, DI) | Hono (functional, minimal) |
| CRUD | Server handles all CRUD | Client-side (local-first + mana-sync) |
| Server role | Full backend | Compute-only endpoints |
| Auth | NestJS guards | `@manacore/shared-hono` middleware |
| Startup | `nest start --watch` | `bun run --watch src/index.ts` |
| Config | `ConfigModule` + DI | `process.env` directly |

View file

@ -1,659 +0,0 @@
# NestJS Backend Guidelines
## Overview
All backend services use NestJS with a consistent architecture. This guide covers controllers, services, DTOs, modules, and integration with the error handling system.
## Project Structure
```
apps/{project}/apps/backend/
├── src/
│ ├── main.ts # Bootstrap
│ ├── app.module.ts # Root module
│ ├── db/
│ │ ├── schema/ # Drizzle schemas
│ │ ├── connection.ts # DB singleton
│ │ ├── database.module.ts # NestJS module
│ │ └── migrations/ # Migration files
│ ├── common/
│ │ ├── filters/ # Exception filters
│ │ ├── guards/ # Custom guards
│ │ └── decorators/ # Custom decorators
│ ├── health/
│ │ ├── health.controller.ts
│ │ └── health.module.ts
│ └── {feature}/
│ ├── {feature}.controller.ts
│ ├── {feature}.service.ts
│ ├── {feature}.module.ts
│ ├── {feature}.spec.ts
│ └── dto/
│ ├── create-{feature}.dto.ts
│ └── update-{feature}.dto.ts
├── test/
│ ├── jest-e2e.json
│ └── app.e2e-spec.ts
├── drizzle.config.ts
├── nest-cli.json
├── package.json
└── tsconfig.json
```
## Bootstrap (main.ts)
```typescript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, Logger } from '@nestjs/common';
import { AppModule } from './app.module';
import { AppExceptionFilter } from './common/filters/app-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = new Logger('Bootstrap');
// CORS
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((o) => o.trim()) || [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:8081',
];
app.enableCors({
origin: corsOrigins,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
credentials: true,
});
// Global validation pipe
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip unknown properties
forbidNonWhitelisted: true, // Reject unknown properties
transform: true, // Auto-transform types
transformOptions: {
enableImplicitConversion: true,
},
})
);
// Global exception filter
app.useGlobalFilters(new AppExceptionFilter());
// API prefix
app.setGlobalPrefix('api/v1');
const port = process.env.PORT || 3000;
await app.listen(port);
logger.log(`Application running on http://localhost:${port}`);
}
bootstrap();
```
## App Module
```typescript
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { DatabaseModule } from './db/database.module';
import { HealthModule } from './health/health.module';
import { FileModule } from './file/file.module';
import { FolderModule } from './folder/folder.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
DatabaseModule,
HealthModule,
FileModule,
FolderModule,
],
})
export class AppModule {}
```
## Controllers
### Basic Pattern
```typescript
// src/file/file.controller.ts
import {
Controller,
Get,
Post,
Patch,
Delete,
Param,
Body,
Query,
UseGuards,
ParseUUIDPipe,
} from '@nestjs/common';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import { AppException } from '@manacore/shared-errors';
import { FileService } from './file.service';
import { CreateFileDto, UpdateFileDto, QueryFilesDto } from './dto';
@Controller('files')
@UseGuards(JwtAuthGuard) // Apply to all routes in controller
export class FileController {
constructor(private readonly fileService: FileService) {}
@Get()
async list(@CurrentUser() user: CurrentUserData, @Query() query: QueryFilesDto) {
const result = await this.fileService.findAll(user.userId, query);
if (!result.ok) throw new AppException(result.error);
return { files: result.data };
}
@Get(':id')
async getById(@Param('id', ParseUUIDPipe) id: string, @CurrentUser() user: CurrentUserData) {
const result = await this.fileService.findById(id, user.userId);
if (!result.ok) throw new AppException(result.error);
return { file: result.data };
}
@Post()
async create(@Body() dto: CreateFileDto, @CurrentUser() user: CurrentUserData) {
const result = await this.fileService.create(user.userId, dto);
if (!result.ok) throw new AppException(result.error);
return { file: result.data };
}
@Patch(':id')
async update(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: UpdateFileDto,
@CurrentUser() user: CurrentUserData
) {
const result = await this.fileService.update(id, user.userId, dto);
if (!result.ok) throw new AppException(result.error);
return { file: result.data };
}
@Delete(':id')
async delete(@Param('id', ParseUUIDPipe) id: string, @CurrentUser() user: CurrentUserData) {
const result = await this.fileService.delete(id, user.userId);
if (!result.ok) throw new AppException(result.error);
return { success: true };
}
}
```
### Public Endpoints (No Auth)
```typescript
@Controller('public')
export class PublicController {
@Get('shares/:token') // No @UseGuards - public access
async getSharedItem(@Param('token') token: string) {
const result = await this.shareService.findByToken(token);
if (!result.ok) throw new AppException(result.error);
return { item: result.data };
}
}
```
## Services
### Basic Pattern with Result Types
```typescript
// src/file/file.service.ts
import { Injectable, Inject, Logger } from '@nestjs/common';
import { Result, ok, err, ErrorCode } from '@manacore/shared-errors';
import { DATABASE_CONNECTION, Database } from '../db/database.module';
import { files, File, NewFile } from '../db/schema';
import { eq, and, desc } from 'drizzle-orm';
import { CreateFileDto, UpdateFileDto, QueryFilesDto } from './dto';
@Injectable()
export class FileService {
private readonly logger = new Logger(FileService.name);
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
async findAll(userId: string, query: QueryFilesDto): Promise<Result<File[]>> {
try {
const conditions = [eq(files.userId, userId), eq(files.isDeleted, false)];
if (query.folderId) {
conditions.push(eq(files.parentFolderId, query.folderId));
}
const result = await this.db
.select()
.from(files)
.where(and(...conditions))
.orderBy(desc(files.createdAt))
.limit(query.limit ?? 50)
.offset(query.offset ?? 0);
return ok(result);
} catch (error) {
this.logger.error('Failed to fetch files', { userId, error: error.message });
return err(ErrorCode.DATABASE_ERROR, 'Failed to fetch files');
}
}
async findById(id: string, userId: string): Promise<Result<File>> {
try {
const [file] = await this.db
.select()
.from(files)
.where(and(eq(files.id, id), eq(files.userId, userId), eq(files.isDeleted, false)));
if (!file) {
return err(ErrorCode.FILE_NOT_FOUND, `File ${id} not found`);
}
return ok(file);
} catch (error) {
this.logger.error('Failed to fetch file', { id, userId, error: error.message });
return err(ErrorCode.DATABASE_ERROR, 'Failed to fetch file');
}
}
async create(userId: string, dto: CreateFileDto): Promise<Result<File>> {
// Validation
if (!dto.name?.trim()) {
return err(ErrorCode.MISSING_REQUIRED_FIELD, 'File name is required');
}
try {
const newFile: NewFile = {
userId,
name: dto.name.trim(),
originalName: dto.originalName,
mimeType: dto.mimeType,
size: dto.size,
storagePath: dto.storagePath,
storageKey: dto.storageKey,
parentFolderId: dto.folderId ?? null,
};
const [created] = await this.db.insert(files).values(newFile).returning();
return ok(created);
} catch (error) {
if (error.code === '23505') {
return err(ErrorCode.DUPLICATE_ENTRY, 'A file with this name already exists');
}
this.logger.error('Failed to create file', { userId, error: error.message });
return err(ErrorCode.DATABASE_ERROR, 'Failed to create file');
}
}
async update(id: string, userId: string, dto: UpdateFileDto): Promise<Result<File>> {
// Check ownership first
const existingResult = await this.findById(id, userId);
if (!existingResult.ok) return existingResult;
try {
const [updated] = await this.db
.update(files)
.set({
...(dto.name && { name: dto.name.trim() }),
...(dto.parentFolderId !== undefined && { parentFolderId: dto.parentFolderId }),
updatedAt: new Date(),
})
.where(eq(files.id, id))
.returning();
return ok(updated);
} catch (error) {
this.logger.error('Failed to update file', { id, error: error.message });
return err(ErrorCode.DATABASE_ERROR, 'Failed to update file');
}
}
async delete(id: string, userId: string): Promise<Result<void>> {
// Check ownership first
const existingResult = await this.findById(id, userId);
if (!existingResult.ok) return existingResult;
try {
await this.db
.update(files)
.set({ isDeleted: true, deletedAt: new Date() })
.where(eq(files.id, id));
return ok(undefined);
} catch (error) {
this.logger.error('Failed to delete file', { id, error: error.message });
return err(ErrorCode.DATABASE_ERROR, 'Failed to delete file');
}
}
}
```
### Service with External Dependencies
```typescript
@Injectable()
export class UploadService {
private readonly logger = new Logger(UploadService.name);
constructor(
@Inject(DATABASE_CONNECTION) private db: Database,
private readonly storageService: StorageService,
private readonly fileService: FileService
) {}
async uploadFile(
userId: string,
file: Express.Multer.File,
folderId?: string
): Promise<Result<File>> {
// 1. Upload to storage
const storageResult = await this.storageService.upload(
generateStorageKey(userId, file.originalname),
file.buffer,
{ contentType: file.mimetype }
);
if (!storageResult.ok) {
return err(ErrorCode.UPLOAD_FAILED, 'Failed to upload file to storage');
}
// 2. Create database record
const createResult = await this.fileService.create(userId, {
name: file.originalname,
originalName: file.originalname,
mimeType: file.mimetype,
size: file.size,
storagePath: storageResult.data.path,
storageKey: storageResult.data.key,
folderId,
});
if (!createResult.ok) {
// Cleanup on failure
await this.storageService.delete(storageResult.data.key);
return createResult;
}
return createResult;
}
}
```
## DTOs
### Create DTO
```typescript
// src/file/dto/create-file.dto.ts
import { IsString, IsOptional, IsNumber, IsUUID, MaxLength, Min } from 'class-validator';
export class CreateFileDto {
@IsString()
@MaxLength(500)
name: string;
@IsOptional()
@IsString()
@MaxLength(500)
originalName?: string;
@IsString()
@MaxLength(255)
mimeType: string;
@IsNumber()
@Min(0)
size: number;
@IsString()
@MaxLength(1000)
storagePath: string;
@IsString()
@MaxLength(500)
storageKey: string;
@IsOptional()
@IsUUID()
folderId?: string;
}
```
### Update DTO (Partial)
```typescript
// src/file/dto/update-file.dto.ts
import { IsString, IsOptional, IsUUID, MaxLength } from 'class-validator';
export class UpdateFileDto {
@IsOptional()
@IsString()
@MaxLength(500)
name?: string;
@IsOptional()
@IsUUID()
parentFolderId?: string | null;
}
```
### Query DTO
```typescript
// src/file/dto/query-files.dto.ts
import { IsOptional, IsUUID, IsNumber, Min, Max } from 'class-validator';
import { Transform } from 'class-transformer';
export class QueryFilesDto {
@IsOptional()
@IsUUID()
folderId?: string;
@IsOptional()
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1)
@Max(100)
limit?: number = 50;
@IsOptional()
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(0)
offset?: number = 0;
}
```
### DTO Index
```typescript
// src/file/dto/index.ts
export * from './create-file.dto';
export * from './update-file.dto';
export * from './query-files.dto';
```
## Modules
```typescript
// src/file/file.module.ts
import { Module } from '@nestjs/common';
import { FileController } from './file.controller';
import { FileService } from './file.service';
import { UploadService } from './upload.service';
import { StorageModule } from '../storage/storage.module';
@Module({
imports: [StorageModule],
controllers: [FileController],
providers: [FileService, UploadService],
exports: [FileService], // Export for use in other modules
})
export class FileModule {}
```
## Exception Filter
```typescript
// src/common/filters/app-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus, Logger } from '@nestjs/common';
import { Response } from 'express';
import { AppException, ERROR_STATUS_MAP, ErrorCode } from '@manacore/shared-errors';
@Catch(AppException)
export class AppExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(AppExceptionFilter.name);
catch(exception: AppException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = ERROR_STATUS_MAP[exception.error.code] ?? HttpStatus.INTERNAL_SERVER_ERROR;
// Log server errors
if (status >= 500) {
this.logger.error('Server error', {
code: exception.error.code,
message: exception.error.message,
details: exception.error.details,
});
}
response.status(status).json({
ok: false,
error: {
code: exception.error.code,
message: exception.error.message,
...(process.env.NODE_ENV === 'development' && {
details: exception.error.details,
}),
},
});
}
}
```
## File Upload
```typescript
// src/file/file.controller.ts
import { UseInterceptors, UploadedFile, ParseFilePipe, MaxFileSizeValidator } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('files')
@UseGuards(JwtAuthGuard)
export class FileController {
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 100 * 1024 * 1024 }), // 100MB
],
})
)
file: Express.Multer.File,
@Query('folderId') folderId: string | undefined,
@CurrentUser() user: CurrentUserData
) {
const result = await this.uploadService.uploadFile(user.userId, file, folderId);
if (!result.ok) throw new AppException(result.error);
return { file: result.data };
}
}
```
## Health Check
```typescript
// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { Inject } from '@nestjs/common';
import { DATABASE_CONNECTION, Database } from '../db/database.module';
import { sql } from 'drizzle-orm';
@Controller('health')
export class HealthController {
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
@Get()
async check() {
try {
await this.db.execute(sql`SELECT 1`);
return {
status: 'ok',
timestamp: new Date().toISOString(),
database: 'connected',
};
} catch (error) {
return {
status: 'error',
timestamp: new Date().toISOString(),
database: 'disconnected',
};
}
}
}
```
## API Response Format
### Success Responses
```typescript
// Single resource
{ file: { id: '...', name: '...', ... } }
// Multiple resources
{ files: [...] }
// With pagination
{ files: [...], total: 100, page: 1, limit: 20 }
// Action success
{ success: true }
// Action with data
{ success: true, message: 'File moved', file: {...} }
```
### Error Responses
```typescript
{
ok: false,
error: {
code: 'ERR_4003',
message: 'File not found'
}
}
```
## Environment Variables
```env
# Required
NODE_ENV=development
PORT=3016
DATABASE_URL=postgresql://user:pass@localhost:5432/db
MANA_CORE_AUTH_URL=http://localhost:3001
# CORS
CORS_ORIGINS=http://localhost:5173,http://localhost:3000
# Storage
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
# Optional - Development bypass
DEV_BYPASS_AUTH=true
DEV_USER_ID=dev-user-123
```

View file

@ -546,7 +546,7 @@ pnpm test
pnpm test:cov pnpm test:cov
# Run specific project # Run specific project
pnpm --filter @storage/backend test pnpm --filter @storage/server test
# Run in watch mode # Run in watch mode
pnpm test:watch pnpm test:watch

View file

@ -259,16 +259,6 @@ jobs:
SERVICES="${{ steps.services.outputs.services }}" SERVICES="${{ steps.services.outputs.services }}"
DEPLOY_ALL="${{ steps.services.outputs.deploy-all }}" DEPLOY_ALL="${{ steps.services.outputs.deploy-all }}"
# Check if any backend service is being deployed
NEEDS_BASE=false
if [ "$DEPLOY_ALL" == "true" ]; then
NEEDS_BASE=true
else
for svc in $SERVICES; do
case "$svc" in *-backend) NEEDS_BASE=true; break ;; esac
done
fi
NEEDS_WEB_BASE=false NEEDS_WEB_BASE=false
if [ "$DEPLOY_ALL" == "true" ]; then if [ "$DEPLOY_ALL" == "true" ]; then
NEEDS_WEB_BASE=true NEEDS_WEB_BASE=true
@ -278,14 +268,6 @@ jobs:
done done
fi fi
if [ "$NEEDS_BASE" == "true" ]; then
echo "=== Building shared NestJS base image ==="
docker build -f docker/Dockerfile.nestjs-base -t nestjs-base:local . 2>&1 | tail -5
echo "NestJS base image built"
else
echo "No backends to deploy, skipping NestJS base image"
fi
if [ "$NEEDS_WEB_BASE" == "true" ]; then if [ "$NEEDS_WEB_BASE" == "true" ]; then
echo "=== Building shared SvelteKit base image ===" echo "=== Building shared SvelteKit base image ==="
docker build -f docker/Dockerfile.sveltekit-base -t sveltekit-base:local . 2>&1 | tail -5 docker build -f docker/Dockerfile.sveltekit-base -t sveltekit-base:local . 2>&1 | tail -5

144
CLAUDE.md
View file

@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Monorepo Overview ## Monorepo Overview
This is a pnpm workspace monorepo containing multiple product applications with shared packages. All projects use Supabase for database/auth and follow similar architectural patterns. This is a pnpm workspace monorepo containing multiple product applications with shared packages. All projects use a local-first architecture (Dexie.js + mana-sync) with Hono/Bun compute servers and follow consistent patterns.
**Package Manager:** pnpm 9.15.0 (use `pnpm` for all commands) **Package Manager:** pnpm 9.15.0 (use `pnpm` for all commands)
**Build System:** Turborepo **Build System:** Turborepo
@ -20,7 +20,7 @@ For comprehensive guidelines on code patterns and conventions, see the `.claude/
| [`.claude/guidelines/code-style.md`](.claude/guidelines/code-style.md) | Formatting, naming, linting | | [`.claude/guidelines/code-style.md`](.claude/guidelines/code-style.md) | Formatting, naming, linting |
| [`.claude/guidelines/database.md`](.claude/guidelines/database.md) | Drizzle ORM, schema patterns | | [`.claude/guidelines/database.md`](.claude/guidelines/database.md) | Drizzle ORM, schema patterns |
| [`.claude/guidelines/testing.md`](.claude/guidelines/testing.md) | Jest/Vitest, mock factories | | [`.claude/guidelines/testing.md`](.claude/guidelines/testing.md) | Jest/Vitest, mock factories |
| [`.claude/guidelines/nestjs-backend.md`](.claude/guidelines/nestjs-backend.md) | Controllers, services, DTOs | | [`.claude/guidelines/hono-server.md`](.claude/guidelines/hono-server.md) | Hono/Bun compute servers |
| [`.claude/guidelines/error-handling.md`](.claude/guidelines/error-handling.md) | Go-style Result types, error codes | | [`.claude/guidelines/error-handling.md`](.claude/guidelines/error-handling.md) | Go-style Result types, error codes |
| [`.claude/guidelines/sveltekit-web.md`](.claude/guidelines/sveltekit-web.md) | Svelte 5 runes, stores | | [`.claude/guidelines/sveltekit-web.md`](.claude/guidelines/sveltekit-web.md) | Svelte 5 runes, stores |
| [`.claude/guidelines/expo-mobile.md`](.claude/guidelines/expo-mobile.md) | React Native, NativeWind | | [`.claude/guidelines/expo-mobile.md`](.claude/guidelines/expo-mobile.md) | React Native, NativeWind |
@ -123,8 +123,9 @@ pnpm run contacts:dev
# Start specific app within project # Start specific app within project
pnpm run dev:chat:mobile # Just mobile app pnpm run dev:chat:mobile # Just mobile app
pnpm run dev:chat:backend # Just NestJS backend pnpm run dev:chat:server # Just Hono/Bun server
pnpm run dev:chat:app # Web + backend together pnpm run dev:chat:local # sync + server + web (no auth needed)
pnpm run dev:chat:app # Server + web together
# Build & quality # Build & quality
pnpm run build pnpm run build
@ -143,7 +144,7 @@ manacore-monorepo/
├── apps/ # Active SaaS product applications ├── apps/ # Active SaaS product applications
│ ├── chat/ │ ├── chat/
│ │ ├── apps/ │ │ ├── apps/
│ │ │ ├── backend/ # NestJS API │ │ │ ├── server/ # Hono/Bun compute server
│ │ │ ├── mobile/ # Expo React Native app │ │ │ ├── mobile/ # Expo React Native app
│ │ │ ├── web/ # SvelteKit web app │ │ │ ├── web/ # SvelteKit web app
│ │ │ └── landing/ # Astro marketing page │ │ │ └── landing/ # Astro marketing page
@ -193,7 +194,7 @@ manacore-monorepo/
``` ```
apps/{project}/ apps/{project}/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API (when present) │ ├── server/ # Hono/Bun compute server (when present)
│ ├── mobile/ # Expo React Native app │ ├── mobile/ # Expo React Native app
│ ├── web/ # SvelteKit web app │ ├── web/ # SvelteKit web app
│ └── landing/ # Astro marketing page │ └── landing/ # Astro marketing page
@ -259,11 +260,11 @@ Parent workspace packages (e.g., `apps/chat/package.json`, `apps/zitare/package.
- Tailwind CSS - Tailwind CSS
- Static site generation - Static site generation
**Backends (NestJS):** **Compute Servers (Hono + Bun):**
- NestJS 10-11 - Hono 4.x + Bun runtime
- TypeScript - TypeScript, Drizzle ORM (where needed)
- Supabase integration - `@manacore/shared-hono` for auth middleware
### Authentication Architecture ### Authentication Architecture
@ -271,8 +272,8 @@ All projects use **mana-core-auth** as the central authentication service:
``` ```
┌─────────────┐ ┌─────────────┐ ┌────────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────────┐
│ Client │────>│ Backend │────>│ mana-core-auth │ Client │────>│ Server │────>│ mana-auth
│ (Web/Mobile)│ │ (NestJS) │ │ (port 3001) │ │ (Web/Mobile)│ │ (Hono/Bun) │ │ (port 3001) │
└─────────────┘ └─────────────┘ └────────────────┘ └─────────────┘ └─────────────┘ └────────────────┘
│ │ │ │ │ │
│ Bearer token │ POST /validate │ │ Bearer token │ POST /validate │
@ -287,82 +288,36 @@ All projects use **mana-core-auth** as the central authentication service:
| Component | Purpose | | Component | Purpose |
| ------------------------------- | -------------------------------------------------- | | ------------------------------- | -------------------------------------------------- |
| `services/mana-core-auth` | Central auth service (Better Auth + EdDSA JWT) | | `services/mana-auth` | Central auth service (Better Auth + EdDSA JWT) |
| `@manacore/shared-nestjs-auth` | Shared NestJS guards/decorators for JWT validation | | `@manacore/shared-hono` | Shared Hono middleware for JWT validation |
| `@mana-core/nestjs-integration` | Extended NestJS module with auth + credits |
| `@manacore/shared-auth` | Client-side auth for web/mobile apps | | `@manacore/shared-auth` | Client-side auth for web/mobile apps |
#### NestJS Backend Integration #### Hono Server Auth Integration
**Option 1: Simple auth only** - Use `@manacore/shared-nestjs-auth`: All compute servers use `@manacore/shared-hono` for auth:
```typescript ```typescript
// In your controller import { authMiddleware, healthRoute, errorHandler, notFoundHandler } from '@manacore/shared-hono';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('api') const app = new Hono();
@UseGuards(JwtAuthGuard) app.onError(errorHandler);
export class MyController { app.notFound(notFoundHandler);
@Get('profile') app.route('/health', healthRoute('my-server'));
getProfile(@CurrentUser() user: CurrentUserData) { app.use('/api/*', authMiddleware());
return { userId: user.userId, email: user.email };
}
}
```
**Option 2: Auth + Credits** - Use `@mana-core/nestjs-integration`: // In route handlers, get user from context:
app.get('/api/v1/data', (c) => {
```typescript const userId = c.get('userId');
// app.module.ts // ...
import { ManaCoreModule } from '@mana-core/nestjs-integration'; });
@Module({
imports: [
ManaCoreModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
appId: config.get('APP_ID'),
serviceKey: config.get('MANA_CORE_SERVICE_KEY'),
debug: config.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
// In controller
import { AuthGuard } from '@mana-core/nestjs-integration/guards';
import { CurrentUser } from '@mana-core/nestjs-integration/decorators';
import { CreditClientService } from '@mana-core/nestjs-integration';
@Controller('api')
@UseGuards(AuthGuard)
export class ApiController {
constructor(private creditClient: CreditClientService) {}
@Post('generate')
async generate(@CurrentUser() user: any) {
await this.creditClient.consumeCredits(user.sub, 'generation', 10, 'AI generation');
// ... do work
}
}
``` ```
#### Required Environment Variables #### Required Environment Variables
```env ```env
# All backends need this # All servers need this
MANA_CORE_AUTH_URL=http://localhost:3001 MANA_CORE_AUTH_URL=http://localhost:3001
CORS_ORIGINS=http://localhost:5173
# For development bypass (optional)
NODE_ENV=development
DEV_BYPASS_AUTH=true
DEV_USER_ID=your-test-user-id
# For credit operations (optional)
MANA_CORE_SERVICE_KEY=your-service-key
APP_ID=your-app-id
``` ```
#### JWT Token Structure (EdDSA) #### JWT Token Structure (EdDSA)
@ -382,11 +337,11 @@ APP_ID=your-app-id
#### Testing Auth Integration #### Testing Auth Integration
```bash ```bash
# 1. Start mana-core-auth # 1. Start mana-auth
pnpm dev:auth pnpm dev:auth
# 2. Start a backend (e.g., Zitare) # 2. Start an app locally
pnpm dev:zitare:backend pnpm dev:contacts:local
# 3. Get a token # 3. Get a token
TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \ TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
@ -394,20 +349,10 @@ TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
-d '{"email": "test@example.com", "password": "password"}' | jq -r '.accessToken') -d '{"email": "test@example.com", "password": "password"}' | jq -r '.accessToken')
# 4. Call protected endpoint # 4. Call protected endpoint
curl http://localhost:3007/api/favorites \ curl http://localhost:3033/api/v1/import/vcard \
-H "Authorization: Bearer $TOKEN" -H "Authorization: Bearer $TOKEN"
``` ```
#### Integrated Backends
| Backend | Package | Port |
| -------- | ------------------------------- | ---- |
| Chat | `@mana-core/nestjs-integration` | 3002 |
| Picture | `@manacore/shared-nestjs-auth` | 3006 |
| Zitare | `@manacore/shared-nestjs-auth` | 3007 |
| Presi | Custom (same pattern) | 3008 |
| ManaDeck | `@mana-core/nestjs-integration` | 3009 |
#### Adding a New App to SSO #### Adding a New App to SSO
When adding a new app that should participate in cross-app SSO, update **all three** locations: When adding a new app that should participate in cross-app SSO, update **all three** locations:
@ -585,7 +530,7 @@ CACHE_SEARCH_TTL=3600
CACHE_EXTRACT_TTL=86400 CACHE_EXTRACT_TTL=86400
``` ```
#### Usage in Backend #### Usage in Server
```typescript ```typescript
// Direct fetch // Direct fetch
@ -674,8 +619,8 @@ Logged in: App → IndexedDB → UI → SyncEngine → mana-sync (Go) → Postg
pnpm dev:sync # Go sync server (port 3050) pnpm dev:sync # Go sync server (port 3050)
pnpm dev:sync:build # Compile Go binary pnpm dev:sync:build # Compile Go binary
pnpm dev:todo:server # Hono/Bun compute server (port 3019) pnpm dev:todo:server # Hono/Bun compute server (port 3019)
pnpm dev:todo:local # Web + sync + Hono (no auth/NestJS needed) pnpm dev:todo:local # Web + sync + server (no auth needed)
pnpm dev:todo:full # Everything incl. auth + NestJS legacy pnpm dev:todo:full # Everything incl. auth + DB setup
``` ```
### Adding Local-First to a New App ### Adding Local-First to a New App
@ -696,8 +641,7 @@ Full migration plan: `.claude/plans/local-first-architecture-migration.md`
| Package | Purpose | | Package | Purpose |
| ------------------------------- | ----------------------------------------------- | | ------------------------------- | ----------------------------------------------- |
| `@manacore/local-store` | Local-first data layer (Dexie.js + sync engine) | | `@manacore/local-store` | Local-first data layer (Dexie.js + sync engine) |
| `@manacore/shared-nestjs-auth` | NestJS JWT validation guards via mana-core-auth | | `@manacore/shared-hono` | Shared Hono middleware (auth, health, errors) |
| `@mana-core/nestjs-integration` | NestJS module with auth guards + credit client |
| `@manacore/shared-auth` | Client-side auth service for web/mobile apps | | `@manacore/shared-auth` | Client-side auth service for web/mobile apps |
| `@manacore/shared-storage` | S3-compatible storage (MinIO) | | `@manacore/shared-storage` | S3-compatible storage (MinIO) |
| `@manacore/shared-types` | Common TypeScript types | | `@manacore/shared-types` | Common TypeScript types |
@ -756,7 +700,7 @@ pnpm docker:up
| `contacts-storage` | Contacts | Contact avatars/files | | `contacts-storage` | Contacts | Contact avatars/files |
| `storage-storage` | Storage | Cloud drive files | | `storage-storage` | Storage | Cloud drive files |
### Usage in Backend ### Usage in Server
```typescript ```typescript
import { createPictureStorage, generateUserFileKey, getContentType } from '@manacore/shared-storage'; import { createPictureStorage, generateUserFileKey, getContentType } from '@manacore/shared-storage';
@ -944,7 +888,7 @@ docker compose -f docker-compose.macmini.yml logs -f # View logs
All apps build on shared base images to reduce build time and memory usage: All apps build on shared base images to reduce build time and memory usage:
- **`sveltekit-base:local`** (`docker/Dockerfile.sveltekit-base`) — All shared packages for SvelteKit web apps - **`sveltekit-base:local`** (`docker/Dockerfile.sveltekit-base`) — All shared packages for SvelteKit web apps
- **`nestjs-base:local`** (`docker/Dockerfile.nestjs-base`) — All shared packages for NestJS backends - **`hono-server`** (`docker/Dockerfile.hono-server`) — Hono/Bun compute server template
Rebuild base images after shared package changes: `./scripts/mac-mini/build-app.sh --base` Rebuild base images after shared package changes: `./scripts/mac-mini/build-app.sh --base`
@ -1010,7 +954,7 @@ The script reads `.env.development` and generates platform-specific `.env` files
- **Expo mobile**: `EXPO_PUBLIC_*` prefix - **Expo mobile**: `EXPO_PUBLIC_*` prefix
- **SvelteKit web**: `PUBLIC_*` prefix - **SvelteKit web**: `PUBLIC_*` prefix
- **NestJS backend**: No prefix - **Hono/Bun server**: No prefix
### Key Files ### Key Files
@ -1041,12 +985,12 @@ PUBLIC_SUPABASE_URL=...
PUBLIC_SUPABASE_ANON_KEY=... PUBLIC_SUPABASE_ANON_KEY=...
``` ```
**Backend (NestJS):** **Server (Hono/Bun):**
``` ```
SUPABASE_URL=...
SUPABASE_SERVICE_ROLE_KEY=...
PORT=... PORT=...
DATABASE_URL=...
MANA_CORE_AUTH_URL=...
``` ```
## Project-Specific Documentation ## Project-Specific Documentation

View file

@ -6,7 +6,7 @@
| App | Port | URL | | App | Port | URL |
|-----|------|-----| |-----|------|-----|
| Backend | 3014 | http://localhost:3014 | | Server | 3014 | http://localhost:3014 |
| Web App | 5179 | http://localhost:5179 | | Web App | 5179 | http://localhost:5179 |
| Landing Page | 4322 | http://localhost:4322 | | Landing Page | 4322 | http://localhost:4322 |
| Mobile | 8081 | Expo Go | | Mobile | 8081 | Expo Go |
@ -16,7 +16,7 @@
``` ```
apps/calendar/ apps/calendar/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@calendar/backend) │ ├── server/ # Hono/Bun compute server (@calendar/server)
│ │ └── src/ │ │ └── src/
│ │ ├── main.ts │ │ ├── main.ts
│ │ ├── app.module.ts │ │ ├── app.module.ts
@ -114,11 +114,12 @@ apps/calendar/
pnpm calendar:dev # Run all calendar apps pnpm calendar:dev # Run all calendar apps
# Einzelne Apps starten # Einzelne Apps starten
pnpm dev:calendar:backend # Start backend server (port 3014) pnpm dev:calendar:server # Start server (port 3014)
pnpm dev:calendar:web # Start web app (port 5179) pnpm dev:calendar:web # Start web app (port 5179)
pnpm dev:calendar:landing # Start landing page (port 4322) pnpm dev:calendar:landing # Start landing page (port 4322)
pnpm dev:calendar:mobile # Start mobile app [TODO] pnpm dev:calendar:mobile # Start mobile app [TODO]
pnpm dev:calendar:app # Start web + backend together pnpm dev:calendar:app # Start web + server together
pnpm dev:calendar:local # Start web + sync (no auth needed)
# Datenbank # Datenbank
pnpm calendar:db:push # Push schema to database pnpm calendar:db:push # Push schema to database
@ -126,7 +127,7 @@ pnpm calendar:db:studio # Open Drizzle Studio
pnpm calendar:db:seed # Seed initial data pnpm calendar:db:seed # Seed initial data
``` ```
### Backend (apps/calendar/apps/backend) ### Server (apps/calendar/apps/server)
```bash ```bash
pnpm dev # Start with hot reload pnpm dev # Start with hot reload
@ -157,7 +158,7 @@ pnpm preview # Preview build
| Layer | Technology | | Layer | Technology |
|-------|------------| |-------|------------|
| **Backend** | NestJS 10, Drizzle ORM, PostgreSQL | | **Server** | Hono + Bun, Drizzle ORM, PostgreSQL |
| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | | **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 |
| **Landing** | Astro 5.x, Tailwind CSS | | **Landing** | Astro 5.x, Tailwind CSS |
| **Mobile** | React Native 0.81 + Expo SDK 54, NativeWind [TODO] | | **Mobile** | React Native 0.81 + Expo SDK 54, NativeWind [TODO] |
@ -224,7 +225,7 @@ eventsStore.updateEvent(id, data)
eventsStore.deleteEvent(id) eventsStore.deleteEvent(id)
``` ```
### Backend API Endpoints ### Server API Endpoints
#### Health #### Health
@ -400,7 +401,7 @@ FREQ=WEEKLY;UNTIL=20241231T235959Z # Wöchentlich bis Ende 2024
## Environment Variables ## Environment Variables
### Backend (.env) ### Server (.env)
```env ```env
NODE_ENV=development NODE_ENV=development
@ -544,11 +545,11 @@ pnpm calendar:db:push
### 2. Apps starten ### 2. Apps starten
```bash ```bash
# Backend + Web zusammen # Server + Web zusammen
pnpm dev:calendar:app pnpm dev:calendar:app
# Oder einzeln: # Oder einzeln:
pnpm dev:calendar:backend # Terminal 1 pnpm dev:calendar:server # Terminal 1
pnpm dev:calendar:web # Terminal 2 pnpm dev:calendar:web # Terminal 2
pnpm dev:calendar:landing # Terminal 3 (optional) pnpm dev:calendar:landing # Terminal 3 (optional)
``` ```
@ -653,7 +654,7 @@ pnpm --filter @calendar/web test:e2e
1. **Authentication**: Nutzt Mana Core Auth (JWT im Authorization Header) 1. **Authentication**: Nutzt Mana Core Auth (JWT im Authorization Header)
2. **Database**: PostgreSQL mit Drizzle ORM (Port 5432) 2. **Database**: PostgreSQL mit Drizzle ORM (Port 5432)
3. **Port**: Backend läuft auf Port 3014 3. **Port**: Server läuft auf Port 3014
4. **Recurrence**: Verwendet RFC 5545 RRULE Format 4. **Recurrence**: Verwendet RFC 5545 RRULE Format
5. **i18n**: 5 Sprachen unterstützt (DE, EN, FR, ES, IT) 5. **i18n**: 5 Sprachen unterstützt (DE, EN, FR, ES, IT)
6. **Theme**: Ocean-Theme (Blautöne) als Standard 6. **Theme**: Ocean-Theme (Blautöne) als Standard

View file

@ -5,13 +5,13 @@
"description": "Calendar App - Personal and Shared Calendars with CalDAV/iCal Sync", "description": "Calendar App - Personal and Shared Calendars with CalDAV/iCal Sync",
"scripts": { "scripts": {
"dev": "pnpm run --filter=@calendar/* --parallel dev", "dev": "pnpm run --filter=@calendar/* --parallel dev",
"dev:backend": "pnpm --filter @calendar/backend dev", "dev:server": "pnpm --filter @calendar/server dev",
"dev:web": "pnpm --filter @calendar/web dev", "dev:web": "pnpm --filter @calendar/web dev",
"dev:landing": "pnpm --filter @calendar/landing dev", "dev:landing": "pnpm --filter @calendar/landing dev",
"dev:mobile": "pnpm --filter @calendar/mobile dev", "dev:mobile": "pnpm --filter @calendar/mobile dev",
"db:push": "pnpm --filter @calendar/backend db:push", "db:push": "pnpm --filter @calendar/server db:push",
"db:studio": "pnpm --filter @calendar/backend db:studio", "db:studio": "pnpm --filter @calendar/server db:studio",
"db:seed": "pnpm --filter @calendar/backend db:seed" "db:seed": "pnpm --filter @calendar/server db:seed"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3" "typescript": "^5.9.3"

View file

@ -5,7 +5,7 @@
``` ```
apps/chat/ apps/chat/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@chat/backend) │ ├── server/ # Hono/Bun compute server (@chat/server)
│ ├── landing/ # Astro marketing landing page (@chat/landing) │ ├── landing/ # Astro marketing landing page (@chat/landing)
│ ├── web/ # SvelteKit web application (@chat/web) │ ├── web/ # SvelteKit web application (@chat/web)
│ └── mobile/ # Expo/React Native mobile app (@chat/mobile) │ └── mobile/ # Expo/React Native mobile app (@chat/mobile)
@ -23,8 +23,9 @@ pnpm chat:dev # Run all chat apps
pnpm dev:chat:mobile # Start mobile app pnpm dev:chat:mobile # Start mobile app
pnpm dev:chat:web # Start web app pnpm dev:chat:web # Start web app
pnpm dev:chat:landing # Start landing page pnpm dev:chat:landing # Start landing page
pnpm dev:chat:backend # Start backend server pnpm dev:chat:server # Start server
pnpm dev:chat:full # Start backend + web + auth together pnpm dev:chat:local # Start web + sync (no auth needed)
pnpm dev:chat:full # Start server + web + auth together
``` ```
### Mobile App (chat/apps/mobile) ### Mobile App (chat/apps/mobile)
@ -38,7 +39,7 @@ pnpm build:preview # Build preview version
pnpm build:prod # Build production version pnpm build:prod # Build production version
``` ```
### Backend (apps/chat/apps/backend) ### Server (apps/chat/apps/server)
```bash ```bash
pnpm start:dev # Start with hot reload pnpm start:dev # Start with hot reload
@ -70,13 +71,13 @@ pnpm preview # Preview production build
- **Mobile**: React Native 0.76.7 + Expo SDK 52, NativeWind, Expo Router - **Mobile**: React Native 0.76.7 + Expo SDK 52, NativeWind, Expo Router
- **Web**: SvelteKit 2.x, Svelte 5, Tailwind CSS 4 - **Web**: SvelteKit 2.x, Svelte 5, Tailwind CSS 4
- **Landing**: Astro 5.16, Tailwind CSS - **Landing**: Astro 5.16, Tailwind CSS
- **Backend**: NestJS 10, OpenRouter AI + mana-llm (local), Drizzle ORM, PostgreSQL - **Server**: Hono + Bun, OpenRouter AI + mana-llm (local), Drizzle ORM, PostgreSQL
- **Auth**: Mana Core Auth (JWT) - **Auth**: Mana Core Auth (JWT)
- **Types**: TypeScript 5.x - **Types**: TypeScript 5.x
## Architecture ## Architecture
### Backend API Endpoints ### Server API Endpoints
| Endpoint | Method | Description | | Endpoint | Method | Description |
| --------------------------------- | ------ | --------------------------- | | --------------------------------- | ------ | --------------------------- |
@ -91,7 +92,7 @@ pnpm preview # Preview production build
### Environment Variables ### Environment Variables
#### Backend (.env) #### Server (.env)
```env ```env
# Cloud AI models via OpenRouter (optional if using only local models) # Cloud AI models via OpenRouter (optional if using only local models)
@ -162,13 +163,13 @@ PUBLIC_BACKEND_URL=http://localhost:3002
```bash ```bash
# Add new models to existing database # Add new models to existing database
pnpm --filter @chat/backend db:add-local-models pnpm --filter @chat/server db:add-local-models
``` ```
## Quick Start ## Quick Start
1. **Get OpenRouter API key** at https://openrouter.ai/keys 1. **Get OpenRouter API key** at https://openrouter.ai/keys
2. **Create `.env`** in `apps/chat/apps/backend/`: 2. **Create `.env`** in `apps/chat/apps/server/`:
```env ```env
OPENROUTER_API_KEY=sk-or-v1-xxx OPENROUTER_API_KEY=sk-or-v1-xxx
DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/chat DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/chat
@ -182,13 +183,13 @@ pnpm --filter @chat/backend db:add-local-models
``` ```
4. **Seed database** (first time only): 4. **Seed database** (first time only):
```bash ```bash
pnpm --filter @chat/backend db:push pnpm --filter @chat/server db:push
pnpm --filter @chat/backend db:seed pnpm --filter @chat/server db:seed
``` ```
## Important Notes ## Important Notes
1. **Security**: API keys are stored in the backend only - never in client apps 1. **Security**: API keys are stored in the server only - never in client apps
2. **Authentication**: Uses Mana Core Auth (JWT tokens) 2. **Authentication**: Uses Mana Core Auth (JWT tokens)
3. **Database**: PostgreSQL with Drizzle ORM (uses shared Docker container) 3. **Database**: PostgreSQL with Drizzle ORM (uses shared Docker container)
4. **Deployment**: Backend runs on port 3002 4. **Deployment**: Server runs on port 3002

View file

@ -5,7 +5,7 @@
``` ```
apps/contacts/ apps/contacts/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@contacts/backend) - Port 3015 │ ├── server/ # Hono/Bun compute server (@contacts/server) - Port 3015
│ ├── landing/ # Astro marketing landing page (@contacts/landing) │ ├── landing/ # Astro marketing landing page (@contacts/landing)
│ ├── web/ # SvelteKit web application (@contacts/web) - Port 5184 │ ├── web/ # SvelteKit web application (@contacts/web) - Port 5184
│ └── mobile/ # Expo/React Native mobile app (@contacts/mobile) │ └── mobile/ # Expo/React Native mobile app (@contacts/mobile)
@ -23,8 +23,9 @@ pnpm contacts:dev # Run all contacts apps
pnpm dev:contacts:mobile # Start mobile app pnpm dev:contacts:mobile # Start mobile app
pnpm dev:contacts:web # Start web app pnpm dev:contacts:web # Start web app
pnpm dev:contacts:landing # Start landing page pnpm dev:contacts:landing # Start landing page
pnpm dev:contacts:backend # Start backend server pnpm dev:contacts:server # Start server
pnpm dev:contacts:app # Start web + backend together pnpm dev:contacts:app # Start web + server together
pnpm dev:contacts:local # Start web + sync (no auth needed)
``` ```
### Mobile App (apps/contacts/apps/mobile) ### Mobile App (apps/contacts/apps/mobile)

View file

@ -5,13 +5,13 @@
"description": "Contacts App - Contact Management with Manacore Integration", "description": "Contacts App - Contact Management with Manacore Integration",
"scripts": { "scripts": {
"dev": "pnpm run --filter=@contacts/* --parallel dev", "dev": "pnpm run --filter=@contacts/* --parallel dev",
"dev:backend": "pnpm --filter @contacts/backend dev", "dev:server": "pnpm --filter @contacts/server dev",
"dev:web": "pnpm --filter @contacts/web dev", "dev:web": "pnpm --filter @contacts/web dev",
"dev:landing": "pnpm --filter @contacts/landing dev", "dev:landing": "pnpm --filter @contacts/landing dev",
"dev:mobile": "pnpm --filter @contacts/mobile dev", "dev:mobile": "pnpm --filter @contacts/mobile dev",
"db:push": "pnpm --filter @contacts/backend db:push", "db:push": "pnpm --filter @contacts/server db:push",
"db:studio": "pnpm --filter @contacts/backend db:studio", "db:studio": "pnpm --filter @contacts/server db:studio",
"db:seed": "pnpm --filter @contacts/backend db:seed" "db:seed": "pnpm --filter @contacts/server db:seed"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3" "typescript": "^5.9.3"

View file

@ -13,7 +13,7 @@ AI-powered document management and context system for knowledge organization.
``` ```
apps/context/ apps/context/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@context/backend) │ ├── backend/ # Hono/Bun compute server (@context/server)
│ │ └── src/ │ │ └── src/
│ │ ├── main.ts │ │ ├── main.ts
│ │ ├── app.module.ts │ │ ├── app.module.ts
@ -45,7 +45,7 @@ apps/context/
```bash ```bash
# From monorepo root # From monorepo root
pnpm dev:context:full # Start auth + backend + web (with DB setup) pnpm dev:context:full # Start auth + backend + web (with DB setup)
pnpm dev:context:backend # Start backend only (port 3020) pnpm dev:context:server # Start backend only (port 3020)
pnpm dev:context:web # Start web only (port 5192) pnpm dev:context:web # Start web only (port 5192)
pnpm dev:context:app # Start web + backend together pnpm dev:context:app # Start web + backend together
pnpm dev:context:mobile # Start mobile app pnpm dev:context:mobile # Start mobile app
@ -61,7 +61,7 @@ pnpm setup:db:context # Create DB + push schema
| Layer | Technology | | Layer | Technology |
|-------|------------| |-------|------------|
| **Backend** | NestJS 10, Drizzle ORM, PostgreSQL | | **Backend** | Hono + Bun, Drizzle ORM, PostgreSQL |
| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | | **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 |
| **Mobile** | React Native 0.76 + Expo SDK 52, NativeWind | | **Mobile** | React Native 0.76 + Expo SDK 52, NativeWind |
| **Auth** | Mana Core Auth (JWT) | | **Auth** | Mana Core Auth (JWT) |

View file

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"dev": "turbo run dev", "dev": "turbo run dev",
"dev:web": "pnpm --filter @context/web dev", "dev:web": "pnpm --filter @context/web dev",
"dev:backend": "pnpm --filter @context/backend dev", "dev:server": "pnpm --filter @context/server dev",
"dev:mobile": "pnpm --filter @context/mobile dev" "dev:mobile": "pnpm --filter @context/mobile dev"
} }
} }

View file

@ -25,16 +25,16 @@ Each backend service has its own database and schema:
```bash ```bash
# Push schema changes (development) # Push schema changes (development)
pnpm --filter @chat/backend db:push pnpm --filter @chat/server db:push
# Generate migration files # Generate migration files
pnpm --filter @chat/backend db:generate pnpm --filter @chat/server db:generate
# Run migrations # Run migrations
pnpm --filter @chat/backend db:migrate pnpm --filter @chat/server db:migrate
# Open Drizzle Studio (database GUI) # Open Drizzle Studio (database GUI)
pnpm --filter @chat/backend db:studio pnpm --filter @chat/server db:studio
``` ```
## Development Workflow ## Development Workflow
@ -60,13 +60,13 @@ For local development, use `db:push` to quickly sync schema changes:
2. **Push changes** 2. **Push changes**
```bash ```bash
pnpm --filter @chat/backend db:push pnpm --filter @chat/server db:push
``` ```
3. **Verify in Studio** 3. **Verify in Studio**
```bash ```bash
pnpm --filter @chat/backend db:studio pnpm --filter @chat/server db:studio
``` ```
</Steps> </Steps>
@ -82,7 +82,7 @@ For production, use proper migration files:
1. **Generate migration** 1. **Generate migration**
```bash ```bash
pnpm --filter @chat/backend db:generate pnpm --filter @chat/server db:generate
``` ```
This creates a timestamped SQL file in `drizzle/migrations/`. This creates a timestamped SQL file in `drizzle/migrations/`.
@ -99,7 +99,7 @@ For production, use proper migration files:
3. **Run migration** 3. **Run migration**
```bash ```bash
pnpm --filter @chat/backend db:migrate pnpm --filter @chat/server db:migrate
``` ```
</Steps> </Steps>
@ -241,7 +241,7 @@ export class UsersService {
Schema not pushed: Schema not pushed:
```bash ```bash
pnpm --filter @chat/backend db:push --force pnpm --filter @chat/server db:push --force
``` ```
### Migration conflicts ### Migration conflicts
@ -253,7 +253,7 @@ Reset and regenerate:
rm -rf apps/chat/apps/backend/drizzle/migrations/* rm -rf apps/chat/apps/backend/drizzle/migrations/*
# Regenerate # Regenerate
pnpm --filter @chat/backend db:generate pnpm --filter @chat/server db:generate
``` ```
### Connection refused ### Connection refused

View file

@ -139,7 +139,7 @@ If you see errors about missing tables/columns:
```bash ```bash
# Push the latest schema # Push the latest schema
pnpm --filter @chat/backend db:push --force pnpm --filter @chat/server db:push --force
``` ```
### Port already in use ### Port already in use

View file

@ -16,13 +16,13 @@ Manacore uses Jest for backend testing and Vitest for frontend testing.
pnpm test pnpm test
# Run tests for specific project # Run tests for specific project
pnpm --filter @chat/backend test pnpm --filter @chat/server test
# Run tests in watch mode # Run tests in watch mode
pnpm --filter @chat/backend test:watch pnpm --filter @chat/server test:watch
# Run with coverage # Run with coverage
pnpm --filter @chat/backend test:cov pnpm --filter @chat/server test:cov
``` ```
## Test Structure ## Test Structure

View file

@ -124,7 +124,7 @@ Turborepo handles task orchestration with:
| Type | Pattern | Example | | Type | Pattern | Example |
|------|---------|---------| |------|---------|---------|
| App package | `@{project}/{app}` | `@chat/backend` | | App package | `@{project}/{app}` | `@chat/server` |
| Shared package | `@manacore/shared-{name}` | `@manacore/shared-auth` | | Shared package | `@manacore/shared-{name}` | `@manacore/shared-auth` |
| Service | `@mana-{name}/service` | `@mana-search/service` | | Service | `@mana-{name}/service` | `@mana-search/service` |

View file

@ -15,7 +15,7 @@ AI chat application supporting multiple language models including local (Ollama)
| Component | Technology | Port | | Component | Technology | Port |
|-----------|------------|------| |-----------|------------|------|
| Backend | NestJS | 3002 | | Server | Hono/Bun | 3002 |
| Web | SvelteKit | 5173 | | Web | SvelteKit | 5173 |
| Mobile | Expo | - | | Mobile | Expo | - |
| Landing | Astro | - | | Landing | Astro | - |
@ -27,7 +27,7 @@ AI chat application supporting multiple language models including local (Ollama)
pnpm dev:chat:full pnpm dev:chat:full
# Or individual components # Or individual components
pnpm dev:chat:backend pnpm dev:chat:server
pnpm dev:chat:web pnpm dev:chat:web
pnpm dev:chat:mobile pnpm dev:chat:mobile
``` ```
@ -36,8 +36,8 @@ pnpm dev:chat:mobile
``` ```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────>│ Backend │────>│ AI Models │ │ Client │────>│ Server │────>│ AI Models │
│ (Web/Mobile)│ │ (NestJS) │ │ Ollama/API │ │ (Web/Mobile)│ │ (Hono/Bun) │ │ Ollama/API │
└─────────────┘ └──────┬──────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ └─────────────┘
@ -147,21 +147,21 @@ export const messages = pgTable('messages', {
First time setup requires seeding AI models: First time setup requires seeding AI models:
```bash ```bash
pnpm --filter @chat/backend db:push pnpm --filter @chat/server db:push
pnpm --filter @chat/backend db:seed pnpm --filter @chat/server db:seed
``` ```
### Run Tests ### Run Tests
```bash ```bash
pnpm --filter @chat/backend test pnpm --filter @chat/server test
pnpm --filter @chat/backend test:e2e pnpm --filter @chat/server test:e2e
``` ```
### Open Database GUI ### Open Database GUI
```bash ```bash
pnpm --filter @chat/backend db:studio pnpm --filter @chat/server db:studio
``` ```
## Links ## Links

View file

@ -7,7 +7,7 @@ Mukke is a web application for managing your music library, playing tracks, and
``` ```
apps/mukke/ apps/mukke/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API (port 3010) │ ├── backend/ # Hono/Bun server (port 3010)
│ ├── web/ # SvelteKit app (port 5180) │ ├── web/ # SvelteKit app (port 5180)
│ └── landing/ # Astro marketing page │ └── landing/ # Astro marketing page
├── packages/ ├── packages/
@ -23,7 +23,7 @@ pnpm dev:mukke:full
# Or start components individually # Or start components individually
pnpm docker:up # Start PostgreSQL, Redis, MinIO pnpm docker:up # Start PostgreSQL, Redis, MinIO
pnpm --filter @mukke/backend dev # Backend on port 3010 pnpm --filter @mukke/server dev # Server on port 3010
pnpm --filter @mukke/web dev # Web on port 5180 pnpm --filter @mukke/web dev # Web on port 5180
pnpm --filter @mukke/landing dev # Landing page pnpm --filter @mukke/landing dev # Landing page
``` ```
@ -150,7 +150,7 @@ Playback uses HTML5 Audio (browser-native codec support). Upload accepts any `au
| Waveform | wavesurfer.js 7.x | | Waveform | wavesurfer.js 7.x |
| BPM Detection | Web Audio API (peak detection) | | BPM Detection | Web Audio API (peak detection) |
| Metadata | music-metadata (server-side) | | Metadata | music-metadata (server-side) |
| Backend | NestJS 10, Drizzle ORM | | Backend | Hono + Bun, Drizzle ORM |
| Database | PostgreSQL | | Database | PostgreSQL |
| Storage | MinIO (S3-compatible) | | Storage | MinIO (S3-compatible) |
| Auth | mana-core-auth | | Auth | mana-core-auth |
@ -178,14 +178,14 @@ PUBLIC_BACKEND_URL=http://localhost:3010
```bash ```bash
# Database # Database
pnpm --filter @mukke/backend db:push # Push schema pnpm --filter @mukke/server db:push # Push schema
pnpm --filter @mukke/backend db:studio # Open Drizzle Studio pnpm --filter @mukke/server db:studio # Open Drizzle Studio
# Type checking # Type checking
pnpm --filter @mukke/backend type-check pnpm --filter @mukke/server type-check
pnpm --filter @mukke/web type-check pnpm --filter @mukke/web type-check
# Build # Build
pnpm --filter @mukke/backend build pnpm --filter @mukke/server build
pnpm --filter @mukke/web build pnpm --filter @mukke/web build
``` ```

View file

@ -15,7 +15,7 @@
``` ```
apps/nutriphi/ apps/nutriphi/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@nutriphi/backend) │ ├── backend/ # Hono/Bun compute server (@nutriphi/server)
│ │ └── src/ │ │ └── src/
│ │ ├── main.ts │ │ ├── main.ts
│ │ ├── app.module.ts │ │ ├── app.module.ts
@ -71,7 +71,7 @@ apps/nutriphi/
pnpm nutriphi:dev pnpm nutriphi:dev
# Individual apps # Individual apps
pnpm dev:nutriphi:backend # Backend (port 3015) pnpm dev:nutriphi:server # Backend (port 3015)
pnpm dev:nutriphi:web # Web app (port 5180) pnpm dev:nutriphi:web # Web app (port 5180)
pnpm dev:nutriphi:landing # Landing page (port 4323) pnpm dev:nutriphi:landing # Landing page (port 4323)
pnpm dev:nutriphi:app # Web + backend together pnpm dev:nutriphi:app # Web + backend together
@ -108,7 +108,7 @@ pnpm build # Build for production
| Layer | Technology | | Layer | Technology |
|-------|------------| |-------|------------|
| **Backend** | NestJS 10, Drizzle ORM, PostgreSQL | | **Backend** | Hono + Bun, Drizzle ORM, PostgreSQL |
| **AI** | Google Gemini 2.5 Flash | | **AI** | Google Gemini 2.5 Flash |
| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | | **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 |
| **Landing** | Astro 5.x, Tailwind CSS | | **Landing** | Astro 5.x, Tailwind CSS |
@ -306,7 +306,7 @@ GEMINI_API_KEY=your-gemini-api-key
pnpm dev:nutriphi:app pnpm dev:nutriphi:app
# Or individually: # Or individually:
pnpm dev:nutriphi:backend # Terminal 1 pnpm dev:nutriphi:server # Terminal 1
pnpm dev:nutriphi:web # Terminal 2 pnpm dev:nutriphi:web # Terminal 2
pnpm dev:nutriphi:landing # Terminal 3 pnpm dev:nutriphi:landing # Terminal 3
``` ```

View file

@ -5,17 +5,17 @@
"description": "NutriPhi - AI-powered nutrition tracking with photo analysis", "description": "NutriPhi - AI-powered nutrition tracking with photo analysis",
"scripts": { "scripts": {
"dev": "pnpm run --filter=@nutriphi/* --parallel dev", "dev": "pnpm run --filter=@nutriphi/* --parallel dev",
"dev:backend": "pnpm --filter @nutriphi/backend dev", "dev:server": "pnpm --filter @nutriphi/server dev",
"dev:web": "pnpm --filter @nutriphi/web dev", "dev:web": "pnpm --filter @nutriphi/web dev",
"dev:landing": "pnpm --filter @nutriphi/landing dev", "dev:landing": "pnpm --filter @nutriphi/landing dev",
"db:push": "pnpm --filter @nutriphi/backend db:push", "db:push": "pnpm --filter @nutriphi/server db:push",
"db:studio": "pnpm --filter @nutriphi/backend db:studio", "db:studio": "pnpm --filter @nutriphi/server db:studio",
"db:seed": "pnpm --filter @nutriphi/backend db:seed", "db:seed": "pnpm --filter @nutriphi/server db:seed",
"test": "pnpm --filter @nutriphi/backend test && pnpm --filter @nutriphi/shared test && pnpm --filter @nutriphi/web test", "test": "pnpm --filter @nutriphi/server test && pnpm --filter @nutriphi/shared test && pnpm --filter @nutriphi/web test",
"test:backend": "pnpm --filter @nutriphi/backend test", "test:backend": "pnpm --filter @nutriphi/server test",
"test:web": "pnpm --filter @nutriphi/web test", "test:web": "pnpm --filter @nutriphi/web test",
"test:shared": "pnpm --filter @nutriphi/shared test", "test:shared": "pnpm --filter @nutriphi/shared test",
"test:cov": "pnpm --filter @nutriphi/backend test:cov" "test:cov": "pnpm --filter @nutriphi/server test:cov"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3" "typescript": "^5.9.3"

View file

@ -7,7 +7,7 @@ AI image generation app using Replicate API with freemium credit system.
``` ```
apps/picture/ apps/picture/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API (port 3006) │ ├── backend/ # Hono/Bun server (port 3006)
│ ├── mobile/ # Expo React Native app │ ├── mobile/ # Expo React Native app
│ ├── web/ # SvelteKit web app │ ├── web/ # SvelteKit web app
│ └── landing/ # Astro marketing page │ └── landing/ # Astro marketing page
@ -21,7 +21,7 @@ apps/picture/
pnpm dev:picture:full # Start backend + web + auto DB setup pnpm dev:picture:full # Start backend + web + auto DB setup
# Individual apps # Individual apps
pnpm --filter @picture/backend dev # Backend only (port 3006) pnpm --filter @picture/server dev # Backend only (port 3006)
pnpm --filter @picture/web dev # Web only pnpm --filter @picture/web dev # Web only
pnpm --filter @picture/mobile dev # Mobile only pnpm --filter @picture/mobile dev # Mobile only
``` ```

View file

@ -5,7 +5,7 @@
``` ```
apps/planta/ apps/planta/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@planta/backend) │ ├── backend/ # Hono/Bun compute server (@planta/server)
│ └── web/ # SvelteKit web application (@planta/web) │ └── web/ # SvelteKit web application (@planta/web)
├── packages/ ├── packages/
│ └── shared/ # Shared types, utils (@planta/shared) │ └── shared/ # Shared types, utils (@planta/shared)
@ -19,7 +19,7 @@ apps/planta/
```bash ```bash
pnpm planta:dev # Run all planta apps pnpm planta:dev # Run all planta apps
pnpm dev:planta:web # Start web app pnpm dev:planta:web # Start web app
pnpm dev:planta:backend # Start backend server pnpm dev:planta:server # Start backend server
pnpm dev:planta:app # Start web + backend together pnpm dev:planta:app # Start web + backend together
pnpm dev:planta:full # Start auth + backend + web with DB setup pnpm dev:planta:full # Start auth + backend + web with DB setup
``` ```
@ -45,7 +45,7 @@ pnpm preview # Preview production build
## Technology Stack ## Technology Stack
- **Web**: SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS - **Web**: SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS
- **Backend**: NestJS 10, Drizzle ORM, PostgreSQL - **Backend**: Hono + Bun, Drizzle ORM, PostgreSQL
- **AI**: Google Gemini Vision for plant analysis - **AI**: Google Gemini Vision for plant analysis
- **Storage**: MinIO (S3-compatible) - **Storage**: MinIO (S3-compatible)
- **Auth**: Mana Core Auth (JWT) - **Auth**: Mana Core Auth (JWT)

View file

@ -5,11 +5,11 @@
"description": "Planta - Plant Documentation & Care App", "description": "Planta - Plant Documentation & Care App",
"scripts": { "scripts": {
"dev": "turbo run dev", "dev": "turbo run dev",
"dev:backend": "pnpm --filter @planta/backend dev", "dev:server": "pnpm --filter @planta/server dev",
"dev:web": "pnpm --filter @planta/web dev", "dev:web": "pnpm --filter @planta/web dev",
"db:push": "pnpm --filter @planta/backend db:push", "db:push": "pnpm --filter @planta/server db:push",
"db:studio": "pnpm --filter @planta/backend db:studio", "db:studio": "pnpm --filter @planta/server db:studio",
"db:seed": "pnpm --filter @planta/backend db:seed" "db:seed": "pnpm --filter @planta/server db:seed"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3" "typescript": "^5.9.3"

View file

@ -5,7 +5,7 @@
``` ```
apps/storage/ apps/storage/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@storage/backend) - Port 3016 │ ├── backend/ # Hono/Bun compute server (@storage/server) - Port 3016
│ ├── landing/ # Astro marketing landing page (@storage/landing) │ ├── landing/ # Astro marketing landing page (@storage/landing)
│ └── web/ # SvelteKit web application (@storage/web) - Port 5185 │ └── web/ # SvelteKit web application (@storage/web) - Port 5185
├── packages/ ├── packages/

View file

@ -15,7 +15,7 @@
``` ```
apps/todo/ apps/todo/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API server (@todo/backend) │ ├── backend/ # Hono/Bun compute server (@todo/server)
│ ├── web/ # SvelteKit web application (@todo/web) │ ├── web/ # SvelteKit web application (@todo/web)
│ └── landing/ # Astro marketing landing page (@todo/landing) │ └── landing/ # Astro marketing landing page (@todo/landing)
├── packages/ ├── packages/
@ -75,7 +75,7 @@ pnpm preview # Preview build
| Layer | Technology | | Layer | Technology |
|-------|------------| |-------|------------|
| **Backend** | NestJS 10, Drizzle ORM, PostgreSQL | | **Backend** | Hono + Bun, Drizzle ORM, PostgreSQL |
| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 | | **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 |
| **Landing** | Astro 5.x, Tailwind CSS | | **Landing** | Astro 5.x, Tailwind CSS |
| **Auth** | Mana Core Auth (JWT) | | **Auth** | Mana Core Auth (JWT) |
@ -300,7 +300,7 @@ Priority: explicit duration in text > history estimate > default fallback > none
```bash ```bash
# Unit tests # Unit tests
pnpm --filter @todo/backend test pnpm --filter @todo/server test
pnpm --filter @todo/web test pnpm --filter @todo/web test
# E2E tests # E2E tests

View file

@ -5,12 +5,12 @@
"description": "Todo App - Task Management for ManaCore Ecosystem", "description": "Todo App - Task Management for ManaCore Ecosystem",
"scripts": { "scripts": {
"dev": "pnpm run --filter=@todo/* --parallel dev", "dev": "pnpm run --filter=@todo/* --parallel dev",
"dev:backend": "pnpm --filter @todo/backend dev", "dev:server": "pnpm --filter @todo/server dev",
"dev:web": "pnpm --filter @todo/web dev", "dev:web": "pnpm --filter @todo/web dev",
"dev:landing": "pnpm --filter @todo/landing dev", "dev:landing": "pnpm --filter @todo/landing dev",
"db:push": "pnpm --filter @todo/backend db:push", "db:push": "pnpm --filter @todo/server db:push",
"db:studio": "pnpm --filter @todo/backend db:studio", "db:studio": "pnpm --filter @todo/server db:studio",
"db:seed": "pnpm --filter @todo/backend db:seed" "db:seed": "pnpm --filter @todo/server db:seed"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.3" "typescript": "^5.9.3"

View file

@ -8,7 +8,7 @@ GPS tracking app with AI city guides. Location tracking runs locally via AsyncSt
apps/traces/ apps/traces/
├── package.json # Orchestrator (name: traces) ├── package.json # Orchestrator (name: traces)
├── apps/ ├── apps/
│ ├── backend/ # @traces/backend (NestJS, Port 3026) │ ├── backend/ # @traces/server (NestJS, Port 3026)
│ │ └── src/ │ │ └── src/
│ │ ├── main.ts │ │ ├── main.ts
│ │ ├── app.module.ts │ │ ├── app.module.ts
@ -31,7 +31,7 @@ apps/traces/
```bash ```bash
# Development # Development
pnpm dev:traces:mobile # Start Expo app pnpm dev:traces:mobile # Start Expo app
pnpm dev:traces:backend # Start NestJS backend pnpm dev:traces:server # Start NestJS backend
pnpm dev:traces:full # Start auth + backend + mobile pnpm dev:traces:full # Start auth + backend + mobile
# Database # Database

View file

@ -1,58 +0,0 @@
# syntax=docker/dockerfile:1
# Shared builder base for all NestJS backends
# Contains pre-built shared packages and dependencies
#
# Usage in backend Dockerfiles:
# FROM nestjs-base:local AS builder
# COPY apps/myapp/apps/backend ./apps/myapp/apps/backend
# RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile --ignore-scripts
# WORKDIR /app/apps/myapp/apps/backend
# RUN pnpm build
FROM node:20-alpine
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
WORKDIR /app
# Copy root workspace files
COPY pnpm-workspace.yaml ./
COPY package.json ./
COPY pnpm-lock.yaml ./
COPY patches ./patches
# Copy ALL shared packages used by NestJS backends
COPY packages/credit-operations ./packages/credit-operations
COPY packages/mana-core-nestjs-integration ./packages/mana-core-nestjs-integration
COPY packages/manadeck-database ./packages/manadeck-database
COPY packages/shared-drizzle-config ./packages/shared-drizzle-config
COPY packages/shared-errors ./packages/shared-errors
COPY packages/shared-error-tracking ./packages/shared-error-tracking
COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
COPY packages/shared-nestjs-health ./packages/shared-nestjs-health
COPY packages/shared-nestjs-metrics ./packages/shared-nestjs-metrics
COPY packages/shared-nestjs-setup ./packages/shared-nestjs-setup
COPY packages/shared-llm ./packages/shared-llm
COPY packages/shared-storage ./packages/shared-storage
COPY packages/shared-tsconfig ./packages/shared-tsconfig
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile --ignore-scripts
# Build all shared packages (in dependency order)
RUN cd packages/shared-errors && pnpm build \
&& cd /app/packages/shared-nestjs-auth && pnpm build \
&& cd /app/packages/shared-nestjs-health && pnpm build \
&& cd /app/packages/shared-nestjs-metrics && pnpm build \
&& cd /app/packages/shared-nestjs-setup && pnpm build \
&& cd /app/packages/shared-error-tracking && pnpm build \
&& cd /app/packages/shared-storage && pnpm build \
&& cd /app/packages/shared-llm && pnpm build \
&& cd /app/packages/shared-drizzle-config && pnpm build 2>/dev/null || true \
&& cd /app/packages/credit-operations && pnpm build \
&& cd /app/packages/mana-core-nestjs-integration && pnpm build \
&& cd /app/packages/manadeck-database && pnpm build 2>/dev/null || true
WORKDIR /app

View file

@ -1,89 +0,0 @@
# syntax=docker/dockerfile:1
# Multi-stage Dockerfile for NestJS backend services
# This is a template - copy and customize for each backend service
# ============================================
# Build Stage
# ============================================
FROM node:20-alpine AS builder
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
WORKDIR /app
# Copy workspace files
COPY pnpm-workspace.yaml ./
COPY package.json ./
COPY pnpm-lock.yaml ./
# Copy all shared packages (adjust based on dependencies)
COPY packages/ ./packages/
# Copy the specific backend service
# CUSTOMIZE THIS: Replace with your service path
# Example: COPY apps/chat/apps/backend ./apps/chat/apps/backend
ARG SERVICE_PATH
COPY ${SERVICE_PATH} ./${SERVICE_PATH}
# Install all dependencies (including devDependencies for build)
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile
# Build shared packages first
RUN pnpm run build:packages
# Build the backend service
WORKDIR /app/${SERVICE_PATH}
RUN pnpm build
# ============================================
# Production Stage
# ============================================
FROM node:20-alpine AS production
# Install pnpm and system dependencies
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
&& apk add --no-cache \
postgresql-client \
curl \
wget
WORKDIR /app
# Copy workspace files
COPY --from=builder /app/pnpm-workspace.yaml ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
# Copy built packages and service
COPY --from=builder /app/packages ./packages
ARG SERVICE_PATH
COPY --from=builder /app/${SERVICE_PATH} ./${SERVICE_PATH}
# Install production dependencies only
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --prod --frozen-lockfile
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nestjs -u 1001
# Change ownership
RUN chown -R nestjs:nodejs /app
# Switch to non-root user
USER nestjs
# Set working directory to service
WORKDIR /app/${SERVICE_PATH}
# Expose port (customize per service)
ARG PORT=3000
EXPOSE ${PORT}
# Health check (customize endpoint per service)
ARG HEALTH_PATH=/health
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT}${HEALTH_PATH} || exit 1
# Start the application
CMD ["node", "dist/main.js"]

View file

@ -1,945 +0,0 @@
# Backend-Architektur im Manacore Monorepo
Diese Dokumentation beschreibt die Backend-Implementierungen aller Projekte im Manacore Monorepo.
## Übersicht
Das Monorepo enthält 6 Hauptprojekte mit unterschiedlichen Backend-Architekturen:
| Projekt | Backend-Typ | Datenbank | Status |
|---------|-------------|-----------|--------|
| **Maerchenzauber** | NestJS v10 | Supabase (PostgreSQL) | Aktiv |
| **Manadeck** | NestJS v11 | PostgreSQL + Drizzle ORM | Aktiv |
| **Uload** | NestJS v11 | PostgreSQL + Drizzle ORM | Aktiv |
| **Picture** | Kein Backend | - | Frontend-only |
| **Memoro** | Kein Backend | - | Frontend-only |
| **Manacore** | Kein Backend (extern) | - | Externes Backend |
---
## 1. Maerchenzauber
**Pfad:** `/maerchenzauber/apps/backend`
**Zweck:** KI-gestützte Kindergeschichten-Generierung mit benutzerdefinierten Charakteren.
### Technologie-Stack
- **Framework:** NestJS 10.0.0
- **Datenbank:** Supabase (PostgreSQL)
- **ORM:** `@supabase/supabase-js` v2.81.1
- **AI-Services:** Azure OpenAI, Google Gemini, Replicate
### Architektur
```
apps/backend/
├── src/
│ ├── character/ # Charakter-Modul
│ │ ├── character.controller.ts
│ │ ├── character.service.ts
│ │ └── character.repository.ts
│ ├── story/ # Story-Modul
│ │ ├── story.controller.ts
│ │ ├── story.service.ts
│ │ └── pipelines/ # Story-Generierung-Pipelines
│ ├── core/ # Kern-Services
│ │ └── services/
│ │ └── prompting.service.ts
│ ├── settings/ # Benutzereinstellungen
│ ├── health/ # Health-Checks
│ └── feedback/ # Feedback-Modul
```
### Datenbank-Schema
**Tabellen:**
- `characters` - Benutzercharaktere
- `stories` - Generierte Geschichten
- `story_collections` - Sammlungen von Geschichten
- `user_settings` - Benutzereinstellungen
**Sicherheit:** Row-Level Security (RLS) für Datenzugriffskontrolle
### Authentifizierung
Mana Core Integration via `@mana-core/nestjs-integration`:
```typescript
// Beispiel: Geschützter Endpoint
@UseGuards(AuthGuard)
@Get('characters')
async getCharacters(@CurrentUser() user: User) {
return this.characterService.findByUser(user.id);
}
```
**Auth-Endpoint:** `https://mana-core-middleware-111768794939.europe-west3.run.app`
### AI-Services
| Service | Verwendung | API |
|---------|------------|-----|
| Azure OpenAI (GPT-4) | Story-Generierung | `MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT` |
| Google Gemini | Charakter-Generierung | `GOOGLE_GEMINI_API_KEY` |
| Replicate (Flux) | Bildgenerierung | `REPLICATE_API_TOKEN` |
### File Storage
- **Provider:** Supabase Storage
- **Bucket:** `maerchenzauber`
- **Verwendung:** Charakter- und Story-Bilder
### Deployment
- **Plattform:** Google Cloud Run
- **Region:** europe-west3
- **URL:** `https://storyteller-backend-111768794939.europe-west3.run.app`
- **Port:** 3002 (Development)
---
## 2. Manadeck
**Pfad:** `/manadeck/apps/backend`
**Zweck:** KI-gestützte Lernkarten-Generierung (Flashcards, Quizzes, Mixed).
### Technologie-Stack
- **Framework:** NestJS 11.0.1
- **Datenbank:** PostgreSQL 16
- **ORM:** Drizzle ORM
- **AI-Service:** Google Gemini API
### Architektur
```
apps/backend/
├── src/
│ ├── api.controller.ts # Haupt-API-Endpoints
│ ├── public.controller.ts # Öffentliche Endpoints
│ ├── health.controller.ts # Health-Checks
│ ├── ai.service.ts # AI-Generierung
│ └── repositories/
│ ├── deck.repository.ts
│ ├── card.repository.ts
│ ├── user-stats.repository.ts
│ └── deck-template.repository.ts
```
### Datenbank-Package
Das Datenbank-Schema ist in einem separaten Package ausgelagert:
**Pfad:** `/packages/manadeck-database`
```typescript
// Verwendung im Backend
import { db, schema } from '@manacore/manadeck-database';
const decks = await db.query.decks.findMany({
where: eq(schema.decks.userId, userId)
});
```
**Drizzle-Konfiguration:**
```typescript
// drizzle.config.ts
export default {
schema: './src/schema/*',
out: './migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL
}
};
```
### Authentifizierung
```typescript
import { AuthGuard, CurrentUser } from '@mana-core/nestjs-integration';
@Controller('api')
@UseGuards(AuthGuard)
export class ApiController {
@Post('decks')
async createDeck(@CurrentUser() user: User, @Body() dto: CreateDeckDto) {
// Credit-Prüfung und Deck-Erstellung
}
}
```
### Credit-System
Integration mit Mana Core Credit Service:
```typescript
import { CreditClientService } from '@mana-core/nestjs-integration';
@Injectable()
export class AiService {
constructor(private creditClient: CreditClientService) {}
async generateDeck(userId: string, input: GenerateInput) {
// 1. Credit-Balance prüfen
const hasCredits = await this.creditClient.checkBalance(userId, 'DECK_CREATION');
// 2. Deck generieren
const deck = await this.generateWithGemini(input);
// 3. Credits abziehen
await this.creditClient.deduct(userId, 'DECK_CREATION');
return deck;
}
}
```
### AI-Generierung
**Unterstützte Kartentypen:**
- `text` - Textbasierte Karten
- `flashcard` - Klassische Lernkarten
- `quiz` - Multiple-Choice Quiz
- `mixed` - Gemischte Inhalte
**Schwierigkeitsgrade:**
- `beginner`
- `intermediate`
- `advanced`
### Docker-Setup
```yaml
# docker-compose.yml (Lokale Entwicklung)
services:
postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: manadeck
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
pgadmin:
image: dpage/pgadmin4
ports:
- "5050:80"
```
### Deployment
- **Docker Image:** Multi-stage Build (Node 18-alpine)
- **Port:** 8080
- **Health-Check:** `/health`
---
## 3. Uload
**Pfad:** `/uload/apps/backend`
**Zweck:** URL-Shortener mit Link-Analytics.
### Technologie-Stack
- **Framework:** NestJS 11.0.1
- **Datenbank:** PostgreSQL 16
- **ORM:** Drizzle ORM
- **Cache:** Redis (optional)
### Architektur
```
uload/apps/backend/
├── src/
│ ├── main.ts
│ ├── app.module.ts
│ ├── config/
│ │ └── validation.schema.ts
│ ├── controllers/
│ │ ├── redirect.controller.ts # GET /:code (public redirect)
│ │ ├── links.controller.ts # CRUD /api/links
│ │ ├── analytics.controller.ts # GET /api/analytics
│ │ └── health.controller.ts
│ ├── services/
│ │ ├── links.service.ts
│ │ ├── redirect.service.ts
│ │ └── analytics.service.ts
│ └── database/
│ ├── database.module.ts
│ └── repositories/
│ ├── link.repository.ts
│ └── click.repository.ts
├── Dockerfile
└── package.json
```
### Datenbank-Package
**Pfad:** `/packages/uload-database`
```typescript
// Verwendung im Backend
import { db, links, clicks, eq, desc } from '@manacore/uload-database';
const userLinks = await db.query.links.findMany({
where: eq(links.userId, userId),
orderBy: desc(links.createdAt)
});
```
### API Endpoints
| Endpoint | Method | Auth | Beschreibung |
|----------|--------|------|--------------|
| `/:code` | GET | Public | Redirect zu Original-URL |
| `/api/links` | GET | Protected | Liste aller Links |
| `/api/links` | POST | Protected | Link erstellen |
| `/api/links/:id` | GET | Protected | Link Details |
| `/api/links/:id` | PATCH | Protected | Link aktualisieren |
| `/api/links/:id` | DELETE | Protected | Link löschen |
| `/api/analytics/:linkId` | GET | Protected | Link-Statistiken |
| `/health` | GET | Public | Health Check |
### Authentifizierung
Mana Core Integration via `@mana-core/nestjs-integration`:
```typescript
import { AuthGuard, CurrentUser } from '@mana-core/nestjs-integration';
@Controller('api/links')
@UseGuards(AuthGuard)
export class LinksController {
@Get()
async getLinks(@CurrentUser() user: any) {
return this.linksService.getLinks(user.sub);
}
}
```
### Deployment
- **Docker Image:** Multi-stage Build (Node 20-alpine)
- **Port:** 3003
- **Health-Check:** `/health`
---
## 4. Picture
**Pfad:** `/picture`
**Zweck:** Bild- und Medienverwaltung.
### Architektur
**Kein dediziertes Backend.** Picture verwendet:
- SvelteKit Server-Routes für Backend-Logik
- Mana Core für Authentifizierung
- Shared Packages aus `/packages`
```
picture/
├── apps/
│ ├── mobile/ # React Native Expo
│ ├── web/ # SvelteKit
│ └── landing/ # Astro
└── packages/
├── design-tokens/ # Design System
├── mobile-ui/ # Mobile UI Components
└── shared/ # Utilities
```
---
## 5. Memoro
**Pfad:** `/memoro`
**Zweck:** Legacy-Content und Memory-Preservation.
### Architektur
**Kein dediziertes Backend.** Memoro verwendet:
- SvelteKit Server-Routes
- Mana Core für Authentifizierung
- Supabase (Legacy-Konfiguration vorhanden)
```
memoro/
├── apps/
│ ├── mobile/
│ ├── web/
│ └── landing/
└── supabase/ # Legacy Supabase Config
```
---
## 6. Manacore
**Pfad:** `/manacore`
**Zweck:** Core-Authentifizierung und Credit-System.
### Architektur
Das Manacore-Backend ist **extern gehostet** und nicht Teil des Monorepos:
- **URL:** `https://mana-core-middleware-111768794939.europe-west3.run.app`
- **Integration:** Via `@mana-core/nestjs-integration` Package
```
manacore/
├── apps/
│ ├── mobile/ # Auth-Flow UI
│ ├── web/ # Dashboard
│ └── landing/ # Marketing
```
---
## Shared Packages für Backend
### @manacore/manadeck-database
PostgreSQL-Datenbankschema für Manadeck.
```
packages/manadeck-database/
├── src/
│ ├── schema/ # Drizzle Schema
│ ├── client.ts # DB Client
│ └── index.ts # Exports
├── drizzle.config.ts
└── docker-compose.yml
```
### @manacore/uload-database
PostgreSQL-Datenbankschema für Uload URL-Shortener.
```
packages/uload-database/
├── src/
│ ├── schema/
│ │ ├── users.ts
│ │ ├── links.ts
│ │ ├── clicks.ts
│ │ ├── tags.ts
│ │ ├── workspaces.ts
│ │ ├── accounts.ts
│ │ └── relations.ts
│ ├── client.ts # DB Client
│ └── index.ts # Exports
├── drizzle.config.ts
└── docker-compose.yml
```
### @mana-core/nestjs-integration
Externe Dependency für Backend-Integration:
```typescript
// Installation via git
"@mana-core/nestjs-integration": "git+https://github.com/mana-core/nestjs-integration.git"
```
**Bereitgestellte Features:**
- `AuthGuard` - JWT-Authentifizierung
- `@CurrentUser()` - User-Context Decorator
- `CreditClientService` - Credit-Operationen
- Konfigurationsmodule
---
## Authentifizierungs-Pattern
Alle Projekte nutzen zentrale **Mana Core Authentifizierung**:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │────▶│ Project Backend │────▶│ Mana Core │
│ (Web/Mobile) │ │ (NestJS/etc.) │ │ Middleware │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
shared-auth AuthGuard JWT Validation
shared-auth-ui @CurrentUser Credit Service
shared-auth-stores CreditClient User Management
```
### Frontend-Integration
```typescript
// Shared Auth Store (Svelte)
import { authStore } from '@manacore/shared-auth-stores';
// Login
await authStore.login(email, password);
// Token für API-Requests
const token = authStore.getAccessToken();
```
### Backend-Integration
```typescript
// NestJS Module Setup
@Module({
imports: [
ManaCoreModule.forRoot({
serviceKey: process.env.MANA_CORE_SERVICE_KEY,
baseUrl: process.env.MANA_CORE_URL,
}),
],
})
export class AppModule {}
```
---
## Datenbank-Migrationen
### Manadeck (Drizzle)
```bash
# Migration generieren
pnpm --filter @manacore/manadeck-database drizzle-kit generate
# Migration ausführen
pnpm --filter @manacore/manadeck-database drizzle-kit push
```
### Maerchenzauber (Supabase)
```bash
# Supabase CLI
supabase migration new <name>
supabase db push
```
---
## Umgebungsvariablen
### Maerchenzauber Backend
```env
# Supabase
SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
# Mana Core
MANA_CORE_URL=https://mana-core-middleware-111768794939.europe-west3.run.app
MANA_CORE_SERVICE_KEY=
# AI Services
MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_API_KEY=
GOOGLE_GEMINI_API_KEY=
REPLICATE_API_TOKEN=
```
### Manadeck Backend
```env
# Database
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/manadeck
# Mana Core
MANA_CORE_URL=
MANA_CORE_SERVICE_KEY=
# AI
GOOGLE_GEMINI_API_KEY=
# Server
PORT=8080
```
### Uload
```env
# Database
DATABASE_URL=postgresql://...
# Redis
REDIS_URL=redis://localhost:6379
# PocketBase
POCKETBASE_URL=
```
---
## Lokale Entwicklung
### Maerchenzauber Backend
```bash
cd maerchenzauber/apps/backend
pnpm install
pnpm run start:dev
# Läuft auf Port 3002
```
### Manadeck Backend
```bash
# 1. Datenbank starten
cd packages/manadeck-database
docker-compose up -d
# 2. Backend starten
cd manadeck/apps/backend
pnpm install
pnpm run start:dev
# Läuft auf Port 8080
```
### Uload
```bash
cd uload
docker-compose up -d # PostgreSQL + Redis
pnpm install
pnpm run dev
```
---
## Zusammenfassung
Das Manacore Monorepo verwendet verschiedene Backend-Strategien:
1. **Full Backend (NestJS):** Maerchenzauber, Manadeck - Für komplexe Geschäftslogik und AI-Integration
2. **Embedded Database (PocketBase):** Uload - Für einfache CRUD-Operationen
3. **Frontend-only:** Picture, Memoro - Server-Routes in SvelteKit
4. **External Backend:** Manacore - Zentrale Auth/Credit-Services
Alle Projekte teilen sich:
- Gemeinsame Authentifizierung via Mana Core
- Shared Packages für UI, Auth, Types
- Einheitliches Deployment-Pattern (Docker + Cloud Run)
---
## Vereinheitlichungs-Roadmap
### Aktuelle Fragmentierung
| Aspekt | Maerchenzauber | Manadeck | Uload |
|--------|----------------|----------|-------|
| Framework | NestJS v10 | NestJS v11 | PocketBase |
| Datenbank | Supabase | PostgreSQL | PocketBase + PG |
| ORM | @supabase/js | Drizzle | Drizzle |
| Auth | Mana Core | Mana Core | PocketBase + Mana Core |
---
### Strategie 1: Shared NestJS Backend Package
**Ziel:** Ein gemeinsames `@manacore/shared-backend` Package mit wiederverwendbaren Modulen.
```
packages/shared-backend/
├── src/
│ ├── auth/
│ │ ├── auth.module.ts
│ │ ├── auth.guard.ts
│ │ └── current-user.decorator.ts
│ ├── database/
│ │ ├── database.module.ts
│ │ ├── drizzle.provider.ts
│ │ └── base.repository.ts
│ ├── health/
│ │ └── health.module.ts
│ ├── credits/
│ │ └── credits.module.ts
│ └── common/
│ ├── filters/
│ ├── interceptors/
│ └── pipes/
```
**Vorteile:**
- Einheitliche Auth-Integration
- Wiederverwendbare Module
- Konsistente Error-Handling
**Aufwand:** Mittel
---
### Strategie 2: Einheitliche Datenbank-Strategie
#### Option A: Alles auf Drizzle + PostgreSQL (Empfohlen)
```typescript
// packages/shared-database/src/base-schema.ts
export const baseColumns = {
id: uuid('id').primaryKey().defaultRandom(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
userId: text('user_id').notNull(),
};
// Projekt-spezifische Erweiterung
// maerchenzauber/database/schema/characters.ts
import { baseColumns } from '@manacore/shared-database';
export const characters = pgTable('characters', {
...baseColumns,
name: text('name').notNull(),
traits: jsonb('traits'),
});
```
**Migration von Supabase:**
- Supabase ist PostgreSQL → Schema kann übernommen werden
- RLS-Policies in Application-Layer verschieben
- Storage → S3/Cloudflare R2
#### Option B: Alles auf Supabase
```typescript
export const createProjectClient = (project: 'maerchenzauber' | 'manadeck' | 'uload') => {
return createClient(
process.env[`${project.toUpperCase()}_SUPABASE_URL`],
process.env[`${project.toUpperCase()}_SUPABASE_KEY`]
);
};
```
**Vorteile Supabase:**
- Eingebaute Auth (optional nutzbar)
- Storage inklusive
- Realtime-Subscriptions
- Edge Functions möglich
**Nachteile Supabase:**
- Vendor Lock-in
- Weniger Kontrolle über Schema
**Empfehlung:** Drizzle + PostgreSQL wegen Type-Safety, moderner API und keinem Vendor Lock-in.
---
### Strategie 3: Einheitliche Monorepo Backend Struktur
**Ziel-Architektur:**
```
packages/
├── shared-backend/ # Gemeinsame NestJS Module
│ ├── auth/
│ ├── database/
│ ├── health/
│ └── credits/
├── shared-database/ # Drizzle Basis-Schema
│ ├── base-schema.ts
│ ├── migrations/
│ └── client.ts
├── maerchenzauber-database/ # Projekt-Schema
├── manadeck-database/ # ✓ Existiert bereits
└── uload-database/ # Neu
apps/
├── maerchenzauber-backend/ # Nutzt shared-backend
├── manadeck-backend/ # Nutzt shared-backend
└── uload-backend/ # Neues NestJS Backend (ersetzt PocketBase)
```
---
### Strategie 4: Shared Backend als Service-Layer
**Ziel:** Gemeinsamer Service-Layer, projekt-spezifische Controller.
```typescript
// packages/shared-backend/src/services/ai.service.ts
@Injectable()
export class BaseAiService {
constructor(
private gemini: GeminiClient,
private credits: CreditService,
) {}
protected async generateWithCredits<T>(
userId: string,
operation: string,
generator: () => Promise<T>
): Promise<T> {
await this.credits.check(userId, operation);
const result = await generator();
await this.credits.deduct(userId, operation);
return result;
}
}
// maerchenzauber/backend/src/story/story.service.ts
@Injectable()
export class StoryService extends BaseAiService {
async generateStory(userId: string, input: StoryInput) {
return this.generateWithCredits(userId, 'STORY_GENERATION', async () => {
// Projekt-spezifische Logik
});
}
}
```
---
### Strategie 5: API-Gateway Pattern (Optional)
**Ziel:** Ein zentrales Gateway vor allen Backends.
```
┌─────────────────┐
│ API Gateway │
│ (Kong/Traefik) │
└────────┬────────┘
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Maerchenzauber│ │ Manadeck │ │ Uload │
│ Backend │ │ Backend │ │ Backend │
└───────────────┘ └───────────────┘ └───────────────┘
```
**Vorteile:**
- Zentrale Auth-Validierung
- Rate Limiting
- Request Logging
- Einheitliche API-Struktur
**Aufwand:** Hoch - Empfohlen erst bei Skalierungsbedarf
---
### Empfohlene Implementierungsreihenfolge
#### Phase 1: Shared Backend Package
**Priorität:** Hoch
**Aufwand:** 2-3 Wochen
Neues Package `packages/shared-backend/` mit:
- Auth Module (wraps @mana-core/nestjs-integration)
- Health Module
- Credits Module
- Base Repository Pattern
- Common Decorators, Guards, Filters
#### Phase 2: Datenbank-Vereinheitlichung
**Priorität:** Hoch
**Aufwand:** 3-4 Wochen
1. `packages/shared-database/` mit Drizzle Basis-Schema erstellen
2. Maerchenzauber von Supabase auf Drizzle migrieren
3. Uload PocketBase durch PostgreSQL + Drizzle ersetzen
#### Phase 3: Uload Backend Neubau (Optional)
**Priorität:** Mittel
**Aufwand:** 2-3 Wochen
PocketBase → NestJS Migration:
- Konsistenz mit anderen Projekten
- Bessere Integration mit Mana Core
- Einheitliches Deployment
---
### Optionen-Vergleich
| Option | Aufwand | Benefit | Empfehlung |
|--------|---------|---------|------------|
| Shared Backend Package | Mittel | Hoch | ✅ Priorität 1 |
| Drizzle überall | Mittel-Hoch | Hoch | ✅ Priorität 2 |
| Uload auf NestJS | Hoch | Mittel | ⚡ Optional |
| API Gateway | Sehr Hoch | Mittel | ⏳ Später |
---
### Quick Wins (sofort umsetzbar)
1. **NestJS Version angleichen** → Alle auf v11
2. **Einheitliche Health-Endpoints**`/health`, `/health/ready`
3. **Gemeinsame ESLint/Prettier Config**`@manacore/eslint-config-backend`
4. **Einheitliche Error-Response-Struktur:**
```typescript
// Einheitliches Error-Format für alle Backends
interface ApiError {
statusCode: number;
error: string;
message: string;
timestamp: string;
path: string;
}
```
5. **Einheitliche Logging-Struktur:**
```typescript
// packages/shared-backend/src/logging/logger.service.ts
@Injectable()
export class AppLogger {
log(context: string, message: string, meta?: Record<string, any>) {
console.log(JSON.stringify({
level: 'info',
context,
message,
timestamp: new Date().toISOString(),
...meta,
}));
}
}
```
---
### Ziel-Architektur nach Vereinheitlichung
```
┌─────────────────────────────────────────────────────────────┐
│ Shared Packages │
├─────────────────┬─────────────────┬─────────────────────────┤
│ shared-backend │ shared-database │ shared-types │
│ - AuthModule │ - baseColumns │ - ApiError │
│ - HealthModule │ - drizzleClient │ - User │
│ - CreditsModule │ - migrations │ - CreditOperation │
│ - BaseRepo │ │ │
└────────┬────────┴────────┬────────┴────────┬────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Maerchenzauber │ │ Manadeck │ │ Uload │
│ Backend │ │ Backend │ │ Backend │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ NestJS v11 │ │ NestJS v11 │ │ NestJS v11 │
│ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │
│ Drizzle ORM │ │ Drizzle ORM │ │ Drizzle ORM │
│ Port: 3002 │ │ Port: 8080 │ │ Port: 3003 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└─────────────────┼─────────────────┘
┌─────────────────────┐
│ Mana Core │
│ (Auth + Credits) │
└─────────────────────┘
```

View file

@ -246,8 +246,8 @@ pnpm --filter mana-core-auth db:generate
pnpm --filter mana-core-auth db:migrate pnpm --filter mana-core-auth db:migrate
# chat-backend # chat-backend
pnpm --filter @chat/backend db:push pnpm --filter @chat/server db:push
pnpm --filter @chat/backend db:migrate pnpm --filter @chat/server db:migrate
``` ```
--- ---

View file

@ -32,7 +32,7 @@ The generator reads `.env.development` and creates app-specific `.env` files wit
|----------|--------|---------| |----------|--------|---------|
| Expo (mobile) | `EXPO_PUBLIC_` | `EXPO_PUBLIC_SUPABASE_URL` | | Expo (mobile) | `EXPO_PUBLIC_` | `EXPO_PUBLIC_SUPABASE_URL` |
| SvelteKit (web) | `PUBLIC_` | `PUBLIC_SUPABASE_URL` | | SvelteKit (web) | `PUBLIC_` | `PUBLIC_SUPABASE_URL` |
| NestJS (backend) | None | `SUPABASE_URL` | | Hono/Bun (server) | None | `DATABASE_URL` |
## File Locations ## File Locations
@ -40,19 +40,17 @@ The generator reads `.env.development` and creates app-specific `.env` files wit
- **`.env.development`** - Single source of truth, committed to git - **`.env.development`** - Single source of truth, committed to git
### Generated Files (gitignored) ### Generated Files (gitignored)
- `services/mana-core-auth/.env` - `services/mana-auth/.env`
- `apps/chat/apps/backend/.env` - `apps/chat/apps/server/.env`
- `apps/chat/apps/mobile/.env` - `apps/chat/apps/mobile/.env`
- `apps/chat/apps/web/.env` - `apps/chat/apps/web/.env`
- `apps/maerchenzauber/apps/backend/.env`
- `apps/maerchenzauber/apps/mobile/.env`
- `apps/maerchenzauber/apps/web/.env`
- `apps/manacore/apps/mobile/.env` - `apps/manacore/apps/mobile/.env`
- `apps/manacore/apps/web/.env` - `apps/manacore/apps/web/.env`
- `apps/memoro/apps/mobile/.env` - `apps/manadeck/apps/server/.env`
- `apps/memoro/apps/web/.env`
- `apps/manadeck/apps/backend/.env`
- `apps/manadeck/apps/web/.env` - `apps/manadeck/apps/web/.env`
- `apps/*/apps/server/.env` (all apps with compute servers)
- `apps/*/apps/web/.env` (all web apps)
- `apps/*/apps/mobile/.env` (all mobile apps)
## Variable Reference ## Variable Reference
@ -98,28 +96,6 @@ The generator reads `.env.development` and creates app-specific `.env` files wit
| `CHAT_SUPABASE_URL` | Supabase project URL | - | | `CHAT_SUPABASE_URL` | Supabase project URL | - |
| `CHAT_SUPABASE_ANON_KEY` | Supabase anonymous key | - | | `CHAT_SUPABASE_ANON_KEY` | Supabase anonymous key | - |
### Maerchenzauber Project
| Variable | Description | Default |
|----------|-------------|---------|
| `MAERCHENZAUBER_BACKEND_PORT` | Backend service port | `3003` |
| `MAERCHENZAUBER_APP_ID` | Mana Core app ID | - |
| `MAERCHENZAUBER_SUPABASE_URL` | Supabase project URL | - |
| `MAERCHENZAUBER_SUPABASE_ANON_KEY` | Supabase anonymous key | - |
| `MAERCHENZAUBER_JWT_SECRET` | JWT secret for Supabase | - |
| `MAERCHENZAUBER_AZURE_OPENAI_KEY` | Azure OpenAI key | - |
| `MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint | - |
| `MAERCHENZAUBER_REPLICATE_API_KEY` | Replicate API key (images) | - |
### Memoro Project
| Variable | Description |
|----------|-------------|
| `MEMORO_SUPABASE_URL` | Supabase project URL |
| `MEMORO_SUPABASE_ANON_KEY` | Supabase anonymous key |
| `MEMORO_MIDDLEWARE_API_URL` | Middleware API URL |
| `MEMORO_APPID` | Mana Core app ID |
### Manacore Project ### Manacore Project
| Variable | Description | | Variable | Description |
@ -168,9 +144,9 @@ Edit `scripts/generate-env.mjs` and add your app config:
}, },
}, },
{ {
path: 'apps/my-project/apps/backend/.env', path: 'apps/my-project/apps/server/.env',
vars: { vars: {
// For NestJS, no prefix needed // For Hono/Bun servers, no prefix needed
API_KEY: (env) => env.MY_NEW_PROJECT_API_KEY, API_KEY: (env) => env.MY_NEW_PROJECT_API_KEY,
API_URL: (env) => env.MY_NEW_PROJECT_URL, API_URL: (env) => env.MY_NEW_PROJECT_URL,
}, },

View file

@ -1,35 +1,94 @@
# Local Development Guide # Local Development Guide
This guide explains how to set up and run applications locally with automatic database setup. This guide explains how to set up and run applications locally. All apps use a **local-first architecture** with IndexedDB (Dexie.js) for reads/writes and mana-sync for background synchronization. Server-side compute (AI, file operations, RRULE expansion, etc.) is handled by lightweight **Hono/Bun servers**.
## Quick Start ## Quick Start
For any project with a backend, use the `dev:*:full` command: ### Fastest: `dev:*:local` (recommended for daily development)
No auth service needed. Starts mana-sync + server (if any) + web:
```bash ```bash
pnpm dev:chat:full # Start chat with auth + database setup pnpm dev:todo:local # sync + server + web
pnpm dev:zitare:full # Start zitare with auth + database setup pnpm dev:chat:local # sync + server + web
pnpm dev:contacts:full # Start contacts with auth + database setup pnpm dev:zitare:local # sync + web (no server needed)
# ... etc pnpm dev:clock:local # sync + web (no server needed)
```
Guest mode works out of the box -- data lives in IndexedDB, no login required.
### Full stack: `dev:*:full` (with auth + database setup)
Use this when you need authentication, cross-device sync, or to test the full production flow:
```bash
pnpm dev:todo:full # DB setup + auth + sync + server + web
pnpm dev:chat:full # DB setup + auth + sync + server + web
pnpm dev:zitare:full # auth + sync + web
pnpm dev:calendar:full # DB setup + auth + sync + server + web
``` ```
These commands automatically: These commands automatically:
1. Create the database if it doesn't exist 1. Create the database if it doesn't exist (for apps with Drizzle schemas)
2. Push the latest schema (Drizzle `db:push`) 2. Push the latest schema (`drizzle-kit push --force`)
3. Start the auth service (mana-core-auth) 3. Start the auth service (mana-auth)
4. Start the backend and web app with colored output 4. Start mana-sync (Go WebSocket server)
5. Start the Hono/Bun server (if the app has one)
6. Start the web app with colored output
## Available Full Dev Commands ## Available Dev Commands
| Command | Database | Backend Port | Web Port | ### Apps with Hono/Bun Servers
|---------|----------|--------------|----------|
| `pnpm dev:chat:full` | chat | 3002 | 5173 | These apps have server-side compute and support both `local` and `full` modes:
| `pnpm dev:zitare:full` | zitare | 3007 | 5177 |
| `pnpm dev:contacts:full` | contacts | 3015 | 5184 | | App | Server Port | Web Port | `local` | `full` |
| `pnpm dev:calendar:full` | calendar | 3014 | 5179 | |-----|-------------|----------|---------|--------|
| `pnpm dev:clock:full` | clock | 3017 | 5187 | | Todo | 3019 | 5188 | Yes | Yes |
| `pnpm dev:todo:full` | todo | 3018 | 5188 | | Chat | 3002 | 5174 | Yes | Yes |
| `pnpm dev:picture:full` | picture | 3006 | 5175 | | Calendar | 3003 | 5179 | Yes | Yes |
| Contacts | 3004 | 5184 | Yes | Yes |
| Picture | 3006 | 5175 | Yes | Yes |
| ManaDeck | 3009 | 5176 | Yes | Yes |
| Mukke | 3010 | 5180 | Yes | Yes |
| Questions | 3011 | 5111 | Yes | Yes |
| Storage | 3016 | 5185 | Yes | Yes |
| Context | 3020 | 5192 | Yes | Yes |
| Planta | 3022 | 5191 | Yes | Yes |
| NutriPhi | 3023 | 5180 | Yes | Yes |
| Traces | 3026 | mobile | Yes | Yes |
| Presi | 3008 | 5178 | Yes | Yes |
| uLoad | 3070 | 5173 | Yes | Yes |
| News | 3071 | 5173 | Yes | Yes |
| WiseKeep | 3072 | 5173 | Yes | Yes |
| Moodlit | 3073 | 5173 | Yes | Yes |
### Apps without Servers (sync + web only)
These apps use only IndexedDB + mana-sync, no server-side compute:
| App | Web Port | `local` | `full` |
|-----|----------|---------|--------|
| Zitare | 5107 | Yes | Yes |
| Clock | 5187 | Yes | Yes |
| SkilltTree | 5195 | Yes | Yes |
| Photos | 5189 | Yes | Yes |
| CityCorners | 5196 | Yes | Yes |
| Inventar | 5190 | Yes | Yes |
| Times | 5197 | Yes | Yes |
| Calc | 5198 | Yes | Yes |
| ManaVoxel | 5028 | Yes | Yes |
### Infrastructure Ports
| Service | Port | Description |
|---------|------|-------------|
| mana-auth | 3001 | Central auth (Better Auth + EdDSA JWT) |
| mana-sync | 3050 | Data sync (Go, WebSocket, field-level LWW) |
| PostgreSQL | 5432 | Database |
| Redis | 6379 | Cache |
| MinIO API | 9000 | S3-compatible storage |
| MinIO Console | 9001 | Storage web UI |
## Prerequisites ## Prerequisites
@ -39,41 +98,65 @@ Before running any `dev:*:full` command:
# 1. Start Docker infrastructure (PostgreSQL, Redis, MinIO) # 1. Start Docker infrastructure (PostgreSQL, Redis, MinIO)
pnpm docker:up pnpm docker:up
# 2. Generate environment files (runs automatically on pnpm install) # 2. Build mana-sync (first time only, or after Go changes)
pnpm dev:sync:build
# 3. Generate environment files (runs automatically on pnpm install)
pnpm setup:env pnpm setup:env
``` ```
For `dev:*:local`, only mana-sync needs to be built. No Docker required unless your server uses a database.
## Database Setup Commands ## Database Setup Commands
### Individual Service Setup ### Individual Service Setup
```bash ```bash
pnpm setup:db:auth # Setup mana-core-auth database + schema pnpm setup:db:auth # Setup mana-auth database + schema
pnpm setup:db:chat # Setup chat database + schema pnpm setup:db:chat # Setup chat database + schema
pnpm setup:db:zitare # Setup zitare database + schema pnpm setup:db:todo # Setup todo database + schema
pnpm setup:db:contacts # Setup contacts database + schema pnpm setup:db:contacts # Setup contacts database + schema
pnpm setup:db:calendar # Setup calendar database + schema pnpm setup:db:calendar # Setup calendar database + schema
pnpm setup:db:clock # Setup clock database + schema pnpm setup:db:picture # Setup picture database + schema
pnpm setup:db:todo # Setup todo database + schema pnpm setup:db:uload # Setup uload database + schema
pnpm setup:db:picture # Setup picture database + schema pnpm setup:db:context # Setup context database + schema
pnpm setup:db:storage # Setup storage database + schema
pnpm setup:db:mukke # Setup mukke database + schema
pnpm setup:db:planta # Setup planta database + schema
pnpm setup:db:nutriphi # Setup nutriphi database + schema
pnpm setup:db:questions # Setup questions database + schema
pnpm setup:db:traces # Setup traces database + schema
``` ```
### Setup All Databases ### Setup All Databases
```bash ```bash
pnpm setup:db # Creates ALL databases and pushes ALL schemas pnpm setup:db # Creates ALL databases and pushes ALL schemas
``` ```
This is useful when setting up a fresh environment or after pulling new schema changes. This is useful when setting up a fresh environment or after pulling new schema changes.
## How It Works ## How It Works
### Architecture
```
Guest: App -> IndexedDB (Dexie.js) -> UI (no sync)
Logged in: App -> IndexedDB -> UI -> SyncEngine -> mana-sync (Go) -> PostgreSQL
<- WebSocket push <-
```
**mana-sync** (Go, port 3050) handles WebSocket-based sync with field-level last-write-wins conflict resolution. All CRUD goes through IndexedDB first, making the UI instant regardless of network.
**Hono/Bun servers** handle operations that cannot run client-side:
- AI/LLM calls (chat, questions, picture generation)
- File operations (storage, media processing)
- RRULE expansion and reminder scheduling (todo, calendar)
- Server-side data processing (news extraction, transcription)
### Docker Init Script ### Docker Init Script
On first `pnpm docker:up`, the PostgreSQL container runs `docker/init-db/01-create-databases.sql` which creates all databases: On first `pnpm docker:up`, the PostgreSQL container runs `docker/init-db/01-create-databases.sql` which creates all databases.
- manacore, chat, zitare, contacts, calendar, clock, todo, manadeck
- storage, mail, moodlit, finance, inventory, techbase, voxel_lava, figgos
### Setup Script ### Setup Script
@ -84,6 +167,16 @@ The `scripts/setup-databases.sh` script:
The `--force` flag auto-approves schema changes without interactive prompts. The `--force` flag auto-approves schema changes without interactive prompts.
### What each command starts
| Command pattern | What it runs |
|-----------------|-------------|
| `dev:*:local` | mana-sync + server (if any) + web |
| `dev:*:full` | DB setup + mana-auth + mana-sync + server (if any) + web |
| `dev:*:server` | Just the Hono/Bun server (`bun run --watch src/index.ts`) |
| `dev:*:web` | Just the SvelteKit web app |
| `dev:*:app` | Server + web (no sync, no auth) |
## Troubleshooting ## Troubleshooting
### Database doesn't exist ### Database doesn't exist
@ -103,17 +196,26 @@ PGPASSWORD=devpassword psql -h localhost -U manacore -d postgres -c "CREATE DATA
If you see errors about missing tables/columns: If you see errors about missing tables/columns:
```bash ```bash
# Push the latest schema # Push the latest schema from the server package
pnpm --filter @chat/backend db:push --force pnpm --filter @chat/server db:push --force
```
### mana-sync not built
If you see `./server: No such file or directory`:
```bash
pnpm dev:sync:build
``` ```
### Port already in use ### Port already in use
If auth (port 3001) is already running: If auth (port 3001) or sync (port 3050) is already running:
```bash ```bash
# Check what's using the port # Check what's using the port
lsof -i :3001 lsof -i :3001
lsof -i :3050
# Kill the process if needed # Kill the process if needed
kill <PID> kill <PID>
@ -134,23 +236,6 @@ pnpm docker:up
pnpm setup:db pnpm setup:db
``` ```
## Apps Without Full Commands
Some apps don't have backends or don't use Drizzle:
| App | Reason |
|-----|--------|
| manacore | No backend (uses other services) |
| manadeck | Backend exists but no db:push |
| bauntown, context, maerchenzauber, memoro, news, nutriphi, presi, quote, reader, storage, wisekeep | No backends |
For these, use the regular dev commands:
```bash
pnpm dev:manacore:web
pnpm dev:manadeck:app
```
## Adding a New Application ## Adding a New Application
### Step 1: Create Project Structure ### Step 1: Create Project Structure
@ -160,7 +245,7 @@ Create the standard project structure under `apps/`:
``` ```
apps/newproject/ apps/newproject/
├── apps/ ├── apps/
│ ├── backend/ # NestJS API (if needed) │ ├── server/ # Hono/Bun server (if needed for compute)
│ ├── mobile/ # Expo React Native app │ ├── mobile/ # Expo React Native app
│ ├── web/ # SvelteKit web app │ ├── web/ # SvelteKit web app
│ └── landing/ # Astro marketing page │ └── landing/ # Astro marketing page
@ -170,9 +255,24 @@ apps/newproject/
└── CLAUDE.md # Project documentation └── CLAUDE.md # Project documentation
``` ```
### Step 2: Configure Backend Database (if applicable) ### Step 2: Set Up Local-First Data Layer
If your backend uses Drizzle ORM: 1. Create `apps/newproject/apps/web/src/lib/data/local-store.ts` with `createLocalStore()`
2. Create `apps/newproject/apps/web/src/lib/data/guest-seed.ts` for onboarding data
3. Use `collection.getAll()` / `collection.insert()` in stores (not API calls)
4. In layout: `await store.initialize()`, `store.startSync()` on login
### Step 3: Create Hono/Bun Server (if needed)
Only needed if your app requires server-side compute (AI, file ops, etc.).
Create `apps/newproject/apps/server/` with:
- `src/index.ts` -- Hono app entry point
- `src/config.ts` -- Port and env config
- `src/routes/health.ts` -- Health check endpoint
- `package.json` with `"name": "@newproject/server"`
### Step 4: Configure Database (if the server uses Drizzle)
1. **Add database to Docker init** (`docker/init-db/01-create-databases.sql`): 1. **Add database to Docker init** (`docker/init-db/01-create-databases.sql`):
```sql ```sql
@ -181,84 +281,59 @@ If your backend uses Drizzle ORM:
``` ```
2. **Add to setup script** (`scripts/setup-databases.sh`): 2. **Add to setup script** (`scripts/setup-databases.sh`):
In the `setup_service()` function, add a new case:
```bash ```bash
newproject) newproject)
create_db_if_not_exists "newproject" create_db_if_not_exists "newproject"
push_schema "@newproject/backend" "newproject" push_schema "@newproject/server" "newproject"
;; ;;
``` ```
Also add to the `ALL_DATABASES` array:
```bash
ALL_DATABASES=(
...
"newproject"
)
```
And to the services loop at the bottom:
```bash
for service in auth chat ... newproject; do
```
3. **Add DATABASE_URL to `.env.development`**: 3. **Add DATABASE_URL to `.env.development`**:
```env ```env
NEWPROJECT_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/newproject NEWPROJECT_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/newproject
``` ```
4. **Update `scripts/generate-env.mjs`** to generate the backend `.env` file. 4. **Update `scripts/generate-env.mjs`** to generate the server `.env` file.
### Step 3: Add Package.json Scripts ### Step 5: Add Package.json Scripts
Add to root `package.json`: Add to root `package.json`:
```json ```json
{ {
"scripts": { "scripts": {
// Project-level dev (all apps)
"newproject:dev": "turbo run dev --filter=newproject...",
// Individual app commands
"dev:newproject:web": "pnpm --filter @newproject/web dev", "dev:newproject:web": "pnpm --filter @newproject/web dev",
"dev:newproject:mobile": "pnpm --filter @newproject/mobile dev", "dev:newproject:server": "cd apps/newproject/apps/server && bun run --watch src/index.ts",
"dev:newproject:backend": "pnpm --filter @newproject/backend dev", "dev:newproject:local": "concurrently -n sync,server,web -c magenta,yellow,cyan \"pnpm dev:sync\" \"pnpm dev:newproject:server\" \"pnpm dev:newproject:web\"",
"dev:newproject:landing": "pnpm --filter @newproject/landing dev", "dev:newproject:full": "./scripts/setup-databases.sh newproject && ./scripts/setup-databases.sh auth && concurrently -n auth,sync,server,web -c blue,magenta,yellow,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:newproject:server\" \"pnpm dev:newproject:web\"",
"dev:newproject:app": "turbo run dev --filter=@newproject/web --filter=@newproject/backend",
// Full dev with auto database setup
"dev:newproject:full": "./scripts/setup-databases.sh newproject && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:newproject:backend\" \"pnpm dev:newproject:web\"",
// Database shortcuts
"newproject:db:push": "pnpm --filter @newproject/backend db:push",
"newproject:db:studio": "pnpm --filter @newproject/backend db:studio",
// Setup shortcut
"setup:db:newproject": "./scripts/setup-databases.sh newproject" "setup:db:newproject": "./scripts/setup-databases.sh newproject"
} }
} }
``` ```
### Step 4: Create Project CLAUDE.md For apps **without** a server (sync + web only):
Create `apps/newproject/CLAUDE.md` with: ```json
- Project overview {
- Structure diagram "scripts": {
- Available commands "dev:newproject:web": "pnpm --filter @newproject/web dev",
- API endpoints (if backend) "dev:newproject:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:newproject:web\"",
- Environment variables "dev:newproject:full": "concurrently -n auth,sync,web -c blue,magenta,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:newproject:web\""
- Tech stack details }
}
```
See existing projects like `apps/chat/CLAUDE.md` for reference. ### Step 6: Create Project CLAUDE.md
### Step 5: Test the Setup Create `apps/newproject/CLAUDE.md` with project overview, structure, commands, and API endpoints.
### Step 7: Test the Setup
```bash ```bash
# Create database and push schema # Quick start (no auth needed)
pnpm setup:db:newproject pnpm dev:newproject:local
# Start with full dev command # Full stack (with auth + DB setup)
pnpm dev:newproject:full pnpm dev:newproject:full
``` ```
@ -266,10 +341,13 @@ pnpm dev:newproject:full
- [ ] Create project structure under `apps/newproject/` - [ ] Create project structure under `apps/newproject/`
- [ ] Add `pnpm-workspace.yaml` in project root - [ ] Add `pnpm-workspace.yaml` in project root
- [ ] Add database to `docker/init-db/01-create-databases.sql` - [ ] Set up local-store with Dexie.js collections
- [ ] Add service to `scripts/setup-databases.sh` - [ ] Create guest seed data
- [ ] Add DATABASE_URL to `.env.development` - [ ] Add Hono/Bun server (if compute needed)
- [ ] Add database to `docker/init-db/01-create-databases.sql` (if using Drizzle)
- [ ] Add service to `scripts/setup-databases.sh` (if using Drizzle)
- [ ] Add DATABASE_URL to `.env.development` (if using Drizzle)
- [ ] Update `scripts/generate-env.mjs` for env generation - [ ] Update `scripts/generate-env.mjs` for env generation
- [ ] Add scripts to root `package.json` - [ ] Add scripts to root `package.json`
- [ ] Create `CLAUDE.md` with project documentation - [ ] Create `CLAUDE.md` with project documentation
- [ ] Test with `pnpm dev:newproject:full` - [ ] Test with `pnpm dev:newproject:local`

View file

@ -454,16 +454,15 @@ git pull
### Docker Base Images ### Docker Base Images
Alle Apps werden auf vorgebauten Base Images aufgebaut, um Build-Zeit und Memory-Verbrauch zu reduzieren: Alle Web-Apps werden auf einem vorgebauten Base Image aufgebaut, um Build-Zeit und Memory-Verbrauch zu reduzieren. Backend-Server verwenden `docker/Dockerfile.hono-server` (Hono + Bun) direkt.
| Base Image | Dockerfile | Verwendet von | | Base Image | Dockerfile | Verwendet von |
|------------|-----------|---------------| |------------|-----------|---------------|
| `sveltekit-base:local` | `docker/Dockerfile.sveltekit-base` | Alle SvelteKit Web-Apps | | `sveltekit-base:local` | `docker/Dockerfile.sveltekit-base` | Alle SvelteKit Web-Apps |
| `nestjs-base:local` | `docker/Dockerfile.nestjs-base` | Alle NestJS Backends |
Die Base Images enthalten alle Shared Packages (`packages/`) vorinstalliert und vorgebaut. App-Dockerfiles müssen nur noch ihren app-spezifischen Code kopieren. Das Base Image enthaelt alle Shared Packages (`packages/`) vorinstalliert und vorgebaut. App-Dockerfiles muessen nur noch ihren app-spezifischen Code kopieren.
**Base Images neu bauen** wenn sich Shared Packages ändern: **Base Image neu bauen** wenn sich Shared Packages aendern:
```bash ```bash
./scripts/mac-mini/build-app.sh --base ./scripts/mac-mini/build-app.sh --base

View file

@ -46,23 +46,23 @@ Canonical port assignments for all ManaCore services. This is the single source
| 3024 | mana-voice-bot | Python | Voice-to-voice assistant | | 3024 | mana-voice-bot | Python | Voice-to-voice assistant |
| 3025-3029 | *(reserved)* | | | | 3025-3029 | *(reserved)* | | |
## 3030-3059: App Backends ## 3030-3059: App Compute Servers
Only apps that need server-side compute (AI, external APIs, file operations). Only apps that need server-side compute (AI, external APIs, file operations).
Pure CRUD apps use mana-sync directly. Pure CRUD apps use mana-sync directly.
| Port | Service | Runtime | Description | | Port | Service | Runtime | Description |
|------|---------|---------|-------------| |------|---------|---------|-------------|
| 3030 | chat-backend | NestJS | AI chat, streaming, spaces | | 3030 | chat-server | Hono/Bun | AI chat, streaming, spaces |
| 3031 | todo-server | Hono/Bun | RRULE expansion, reminders | | 3031 | todo-server | Hono/Bun | RRULE expansion, reminders |
| 3032 | calendar-backend | NestJS | CalDAV sync, Google Calendar, notifications | | 3032 | calendar-server | Hono/Bun | CalDAV sync, Google Calendar, notifications |
| 3033 | contacts-backend | NestJS | Google Contacts, vCard import/export | | 3033 | contacts-server | Hono/Bun | Google Contacts, vCard import/export |
| 3034 | storage-backend | NestJS | S3 file ops, versioning, shares | | 3034 | storage-server | Hono/Bun | S3 file ops, versioning, shares |
| 3035 | picture-backend | NestJS | Replicate AI, generation orchestration | | 3035 | picture-server | Hono/Bun | Replicate AI, generation orchestration |
| 3036 | manadeck-backend | NestJS | AI card generation | | 3036 | manadeck-server | Hono/Bun | AI card generation |
| 3037 | mukke-backend | NestJS | Audio processing, BPM, ID3 tags | | 3037 | mukke-server | Hono/Bun | Audio processing, BPM, ID3 tags |
| 3038 | nutriphi-backend | NestJS | Gemini meal analysis | | 3038 | nutriphi-server | Hono/Bun | Gemini meal analysis |
| 3039 | planta-backend | NestJS | Gemini plant analysis | | 3039 | planta-server | Hono/Bun | Gemini plant analysis |
| 3040 | presi-server | Hono/Bun | Share links | | 3040 | presi-server | Hono/Bun | Share links |
| 3041-3059 | *(reserved)* | | | | 3041-3059 | *(reserved)* | | |
@ -78,28 +78,28 @@ Pure CRUD apps use mana-sync directly.
## 5000-5059: Web Frontends (SvelteKit) ## 5000-5059: Web Frontends (SvelteKit)
| Port | Service | Corresponds to Backend | | Port | Service | Corresponds to Server |
|------|---------|----------------------| |------|---------|----------------------|
| 5000 | mana-web | Hub/Dashboard | | 5000 | mana-web | Hub/Dashboard |
| 5010 | chat-web | 3030 chat-backend | | 5010 | chat-web | 3030 chat-server |
| 5011 | todo-web | 3031 todo-server | | 5011 | todo-web | 3031 todo-server |
| 5012 | calendar-web | 3032 calendar-backend | | 5012 | calendar-web | 3032 calendar-server |
| 5013 | clock-web | *(local-first only)* | | 5013 | clock-web | *(local-first only)* |
| 5014 | contacts-web | 3033 contacts-backend | | 5014 | contacts-web | 3033 contacts-server |
| 5015 | storage-web | 3034 storage-backend | | 5015 | storage-web | 3034 storage-server |
| 5016 | presi-web | 3040 presi-server | | 5016 | presi-web | 3040 presi-server |
| 5017 | nutriphi-web | 3038 nutriphi-backend | | 5017 | nutriphi-web | 3038 nutriphi-server |
| 5018 | zitare-web | *(local-first only)* | | 5018 | zitare-web | *(local-first only)* |
| 5019 | photos-web | *(local-first + mana-media)* | | 5019 | photos-web | *(local-first + mana-media)* |
| 5020 | skilltree-web | *(local-first only)* | | 5020 | skilltree-web | *(local-first only)* |
| 5021 | picture-web | 3035 picture-backend | | 5021 | picture-web | 3035 picture-server |
| 5022 | citycorners-web | *(local-first only)* | | 5022 | citycorners-web | *(local-first only)* |
| 5023 | manadeck-web | 3036 manadeck-backend | | 5023 | manadeck-web | 3036 manadeck-server |
| 5024 | mukke-web | 3037 mukke-backend | | 5024 | mukke-web | 3037 mukke-server |
| 5025 | inventar-web | *(local-first only)* | | 5025 | inventar-web | *(local-first only)* |
| 5026 | context-web | *(local-first only)* | | 5026 | context-web | *(local-first only)* |
| 5027 | questions-web | *(local-first only)* | | 5027 | questions-web | *(local-first only)* |
| 5028 | planta-web | 3039 planta-backend | | 5028 | planta-web | 3039 planta-server |
| 5029 | moodlit-web | *(future)* | | 5029 | moodlit-web | *(future)* |
| 5030-5049 | *(reserved)* | | | 5030-5049 | *(reserved)* | |

View file

@ -412,7 +412,7 @@ nutriphi/
│ ├── mobile/ # Expo React Native App (@nutriphi/mobile) │ ├── mobile/ # Expo React Native App (@nutriphi/mobile)
│ ├── web/ # SvelteKit Web App (@nutriphi/web) │ ├── web/ # SvelteKit Web App (@nutriphi/web)
│ └── landing/ # Astro Landing Page (@nutriphi/landing) │ └── landing/ # Astro Landing Page (@nutriphi/landing)
├── backend/ # NestJS API Server (@nutriphi/backend) ├── server/ # Hono/Bun server (@nutriphi/server)
``` ```
#### API Endpoints #### API Endpoints

View file

@ -70,9 +70,9 @@ const APP_CONFIGS = [
}, },
}, },
// Chat Backend // Chat Server (Hono/Bun)
{ {
path: 'apps/chat/apps/backend/.env', path: 'apps/chat/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.CHAT_BACKEND_PORT || '3002', PORT: (env) => env.CHAT_BACKEND_PORT || '3002',
@ -130,9 +130,9 @@ const APP_CONFIGS = [
}, },
}, },
// Manadeck Backend // Manadeck Server (Hono/Bun)
{ {
path: 'apps/manadeck/apps/backend/.env', path: 'apps/manadeck/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.MANADECK_BACKEND_PORT || '3004', PORT: (env) => env.MANADECK_BACKEND_PORT || '3004',
@ -154,9 +154,9 @@ const APP_CONFIGS = [
}, },
}, },
// Picture Backend (NestJS) // Picture Server (Hono/Bun)
{ {
path: 'apps/picture/apps/backend/.env', path: 'apps/picture/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.PICTURE_BACKEND_PORT || '3006', PORT: (env) => env.PICTURE_BACKEND_PORT || '3006',
@ -204,9 +204,9 @@ const APP_CONFIGS = [
}, },
}, },
// Nutriphi Backend (NestJS) // Nutriphi Server (Hono/Bun)
{ {
path: 'apps/nutriphi/apps/backend/.env', path: 'apps/nutriphi/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.NUTRIPHI_BACKEND_PORT || '3002', PORT: (env) => env.NUTRIPHI_BACKEND_PORT || '3002',
@ -282,9 +282,9 @@ const APP_CONFIGS = [
}, },
}, },
// Mana Games Backend (NestJS) // Arcade Backend (NestJS)
{ {
path: 'games/mana-games/apps/backend/.env', path: 'games/arcade/apps/backend/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.MANA_GAMES_BACKEND_PORT || '3011', PORT: (env) => env.MANA_GAMES_BACKEND_PORT || '3011',
@ -304,17 +304,17 @@ const APP_CONFIGS = [
}, },
}, },
// Mana Games Web (Astro) // Arcade Web (Astro)
{ {
path: 'games/mana-games/apps/web/.env', path: 'games/arcade/apps/web/.env',
vars: { vars: {
PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.MANA_GAMES_BACKEND_PORT || '3011'}`, PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.MANA_GAMES_BACKEND_PORT || '3011'}`,
}, },
}, },
// Context Backend (NestJS) // Context Server (Hono/Bun)
{ {
path: 'apps/context/apps/backend/.env', path: 'apps/context/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.CONTEXT_BACKEND_PORT || '3020', PORT: (env) => env.CONTEXT_BACKEND_PORT || '3020',
@ -338,9 +338,9 @@ const APP_CONFIGS = [
}, },
}, },
// Calendar Backend (NestJS) // Calendar Server (Hono/Bun)
{ {
path: 'apps/calendar/apps/backend/.env', path: 'apps/calendar/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.CALENDAR_BACKEND_PORT || '3014', PORT: (env) => env.CALENDAR_BACKEND_PORT || '3014',
@ -379,9 +379,9 @@ const APP_CONFIGS = [
}, },
}, },
// Contacts Backend (NestJS) // Contacts Server (Hono/Bun)
{ {
path: 'apps/contacts/apps/backend/.env', path: 'apps/contacts/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.CONTACTS_BACKEND_PORT || '3015', PORT: (env) => env.CONTACTS_BACKEND_PORT || '3015',
@ -422,9 +422,9 @@ const APP_CONFIGS = [
}, },
}, },
// Storage Backend (NestJS) // Storage Server (Hono/Bun)
{ {
path: 'apps/storage/apps/backend/.env', path: 'apps/storage/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.STORAGE_BACKEND_PORT || '3016', PORT: (env) => env.STORAGE_BACKEND_PORT || '3016',
@ -467,9 +467,9 @@ const APP_CONFIGS = [
}, },
}, },
// Todo Backend (NestJS) // Todo Server (Hono/Bun)
{ {
path: 'apps/todo/apps/backend/.env', path: 'apps/todo/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.TODO_BACKEND_PORT || '3018', PORT: (env) => env.TODO_BACKEND_PORT || '3018',
@ -492,9 +492,9 @@ const APP_CONFIGS = [
}, },
}, },
// Moodlit Backend (NestJS) // Moodlit Server (Hono/Bun)
{ {
path: 'apps/moodlit/apps/backend/.env', path: 'apps/moodlit/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.MOODLIT_BACKEND_PORT || '3012', PORT: (env) => env.MOODLIT_BACKEND_PORT || '3012',
@ -524,37 +524,7 @@ const APP_CONFIGS = [
}, },
}, },
// Finance Backend (NestJS) // Finance: REMOVED
{
path: 'apps/finance/apps/backend/.env',
vars: {
NODE_ENV: () => 'development',
PORT: (env) => env.FINANCE_BACKEND_PORT || '3019',
DATABASE_URL: (env) => env.FINANCE_DATABASE_URL,
MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
DEV_BYPASS_AUTH: () => 'true',
DEV_USER_ID: (env) => env.DEV_USER_ID || '00000000-0000-0000-0000-000000000000',
CORS_ORIGINS: (env) => env.CORS_ORIGINS,
},
},
// Finance Mobile (Expo)
{
path: 'apps/finance/apps/mobile/.env',
vars: {
EXPO_PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.FINANCE_BACKEND_PORT || '3019'}`,
EXPO_PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
},
},
// Finance Web (SvelteKit)
{
path: 'apps/finance/apps/web/.env',
vars: {
PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.FINANCE_BACKEND_PORT || '3019'}`,
PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
},
},
// Worldream Web (SvelteKit) // Worldream Web (SvelteKit)
{ {
@ -576,29 +546,11 @@ const APP_CONFIGS = [
}, },
}, },
// TechBase Backend (NestJS) // TechBase: REMOVED
{
path: 'apps/techbase/apps/backend/.env',
vars: {
NODE_ENV: () => 'development',
PORT: (env) => env.TECHBASE_BACKEND_PORT || '3021',
DATABASE_URL: (env) => env.TECHBASE_DATABASE_URL,
MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
CORS_ORIGINS: () => 'http://localhost:4321,http://localhost:5173',
},
},
// TechBase Web (Astro) // Traces Server (Hono/Bun)
{ {
path: 'apps/techbase/apps/web/.env', path: 'apps/traces/apps/server/.env',
vars: {
PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.TECHBASE_BACKEND_PORT || '3021'}`,
},
},
// Traces Backend (NestJS)
{
path: 'apps/traces/apps/backend/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.TRACES_BACKEND_PORT || '3026', PORT: (env) => env.TRACES_BACKEND_PORT || '3026',
@ -657,9 +609,9 @@ const APP_CONFIGS = [
}, },
}, },
// Mukke Backend (NestJS) // Mukke Server (Hono/Bun)
{ {
path: 'apps/mukke/apps/backend/.env', path: 'apps/mukke/apps/server/.env',
vars: { vars: {
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.MUKKE_BACKEND_PORT || '3010', PORT: (env) => env.MUKKE_BACKEND_PORT || '3010',

View file

@ -111,11 +111,6 @@ build_base_images() {
$DOCKER build -f "$PROJECT_ROOT/docker/Dockerfile.sveltekit-base" -t sveltekit-base:local "$PROJECT_ROOT" $DOCKER build -f "$PROJECT_ROOT/docker/Dockerfile.sveltekit-base" -t sveltekit-base:local "$PROJECT_ROOT"
echo "sveltekit-base:local built." echo "sveltekit-base:local built."
echo "" echo ""
echo "=== Building nestjs-base image ==="
$DOCKER build -f "$PROJECT_ROOT/docker/Dockerfile.nestjs-base" -t nestjs-base:local "$PROJECT_ROOT"
echo "nestjs-base:local built."
echo ""
} }
build_services() { build_services() {

View file

@ -66,13 +66,8 @@ ALL_DATABASES=(
"manadeck" "manadeck"
"storage" "storage"
"presi" "presi"
"mail"
"moodlit" "moodlit"
"finance"
"inventory" "inventory"
"techbase"
"voxel_lava"
"figgos"
"planta" "planta"
"nutriphi" "nutriphi"
"photos" "photos"
@ -108,7 +103,7 @@ setup_service() {
;; ;;
chat) chat)
create_db_if_not_exists "chat" create_db_if_not_exists "chat"
push_schema "@chat/backend" "chat" push_schema "@chat/server" "chat"
;; ;;
zitare) zitare)
create_db_if_not_exists "zitare" create_db_if_not_exists "zitare"
@ -116,11 +111,11 @@ setup_service() {
;; ;;
contacts) contacts)
create_db_if_not_exists "contacts" create_db_if_not_exists "contacts"
push_schema "@contacts/backend" "contacts" # Schema managed by mana-sync (local-first)
;; ;;
calendar) calendar)
create_db_if_not_exists "calendar" create_db_if_not_exists "calendar"
push_schema "@calendar/backend" "calendar" # Schema managed by mana-sync (local-first)
;; ;;
clock) clock)
create_db_if_not_exists "clock" create_db_if_not_exists "clock"
@ -128,47 +123,31 @@ setup_service() {
;; ;;
todo) todo)
create_db_if_not_exists "todo" create_db_if_not_exists "todo"
push_schema "@todo/backend" "todo" push_schema "@todo/server" "todo"
;; ;;
manadeck) manadeck)
create_db_if_not_exists "manadeck" create_db_if_not_exists "manadeck"
push_schema "@manadeck/backend" "manadeck" # Schema managed by mana-sync (local-first)
;;
mail)
create_db_if_not_exists "mail"
push_schema "@mail/backend" "mail"
;; ;;
moodlit) moodlit)
create_db_if_not_exists "moodlit" create_db_if_not_exists "moodlit"
push_schema "@moodlit/backend" "moodlit" push_schema "@moodlit/server" "moodlit"
;; ;;
picture) picture)
create_db_if_not_exists "picture" create_db_if_not_exists "picture"
push_schema "@picture/backend" "picture" # Schema managed by mana-sync (local-first)
;; ;;
photos) photos)
create_db_if_not_exists "photos" create_db_if_not_exists "photos"
# Schema managed by mana-sync (backend removed) # Schema managed by mana-sync (backend removed)
;; ;;
finance)
create_db_if_not_exists "finance"
push_schema "@finance/backend" "finance"
;;
voxel-lava)
create_db_if_not_exists "voxel_lava"
push_schema "@voxel-lava/backend" "voxel-lava"
;;
figgos)
create_db_if_not_exists "figgos"
push_schema "@figgos/backend" "figgos"
;;
planta) planta)
create_db_if_not_exists "planta" create_db_if_not_exists "planta"
push_schema "@planta/backend" "planta" push_schema "@planta/server" "planta"
;; ;;
nutriphi) nutriphi)
create_db_if_not_exists "nutriphi" create_db_if_not_exists "nutriphi"
push_schema "@nutriphi/backend" "nutriphi" # Schema managed by mana-sync (local-first)
;; ;;
presi) presi)
create_db_if_not_exists "presi" create_db_if_not_exists "presi"
@ -176,7 +155,7 @@ setup_service() {
;; ;;
storage) storage)
create_db_if_not_exists "storage" create_db_if_not_exists "storage"
push_schema "@storage/backend" "storage" # Schema managed by mana-sync (local-first)
;; ;;
projectdoc) projectdoc)
create_db_if_not_exists "projectdoc" create_db_if_not_exists "projectdoc"
@ -196,7 +175,7 @@ setup_service() {
;; ;;
questions) questions)
create_db_if_not_exists "questions" create_db_if_not_exists "questions"
push_schema "@questions/backend" "questions" # Schema managed by mana-sync (local-first)
;; ;;
skilltree) skilltree)
create_db_if_not_exists "skilltree" create_db_if_not_exists "skilltree"
@ -204,15 +183,15 @@ setup_service() {
;; ;;
mukke) mukke)
create_db_if_not_exists "mukke" create_db_if_not_exists "mukke"
push_schema "@mukke/backend" "mukke" push_schema "@mukke/server" "mukke"
;; ;;
traces) traces)
create_db_if_not_exists "traces" create_db_if_not_exists "traces"
push_schema "@traces/backend" "traces" push_schema "@traces/server" "traces"
;; ;;
context) context)
create_db_if_not_exists "context" create_db_if_not_exists "context"
push_schema "@context/backend" "context" push_schema "@context/server" "context"
;; ;;
citycorners) citycorners)
create_db_if_not_exists "citycorners" create_db_if_not_exists "citycorners"
@ -224,7 +203,7 @@ setup_service() {
;; ;;
*) *)
echo -e "${RED}Unknown service: $service${NC}" echo -e "${RED}Unknown service: $service${NC}"
echo "Available services: auth, chat, zitare, contacts, calendar, clock, todo, manadeck, mail, moodlit, picture, photos, finance, voxel-lava, figgos, planta, nutriphi, presi, storage, projectdoc, zitare_bot, todo_bot, nutriphi_bot, questions, skilltree, mukke, traces, context, citycorners, uload" echo "Available services: auth, chat, zitare, contacts, calendar, clock, todo, manadeck, moodlit, picture, photos, planta, nutriphi, presi, storage, projectdoc, zitare_bot, todo_bot, nutriphi_bot, questions, skilltree, mukke, traces, context, citycorners, uload"
exit 1 exit 1
;; ;;
esac esac
@ -248,7 +227,7 @@ echo -e "\n${GREEN}Step 2: Pushing schemas${NC}"
echo "--------------------------------------" echo "--------------------------------------"
# Push schemas for all known services # Push schemas for all known services
for service in auth chat zitare contacts calendar clock todo manadeck picture photos mail moodlit finance voxel-lava figgos planta nutriphi presi storage questions skilltree mukke traces context citycorners; do for service in auth chat zitare contacts calendar clock todo manadeck picture photos moodlit planta nutriphi presi storage questions skilltree mukke traces context citycorners; do
setup_service "$service" 2>/dev/null || true setup_service "$service" 2>/dev/null || true
done done

View file

@ -90,12 +90,6 @@ function getWorkspaceDeps(pkgJsonPath) {
return deps; return deps;
} }
// Check if a Dockerfile uses nestjs-base:local as its base image
function usesNestjsBase(dockerfilePath) {
const content = readFileSync(dockerfilePath, 'utf8');
return content.includes('FROM nestjs-base:local');
}
// Check if a Dockerfile is a non-monorepo build (standalone, no workspace COPY needed) // Check if a Dockerfile is a non-monorepo build (standalone, no workspace COPY needed)
function isStandaloneBuild(dockerfilePath) { function isStandaloneBuild(dockerfilePath) {
const content = readFileSync(dockerfilePath, 'utf8'); const content = readFileSync(dockerfilePath, 'utf8');
@ -133,22 +127,6 @@ function getDockerfileCopyPaths(dockerfilePath) {
return { copyPaths, hasPatchesCopy }; return { copyPaths, hasPatchesCopy };
} }
// Get packages pre-built in nestjs-base image
function getNestjsBasePackages() {
const baseDockerfile = join(ROOT, 'docker', 'Dockerfile.nestjs-base');
if (!existsSync(baseDockerfile)) return new Set();
const content = readFileSync(baseDockerfile, 'utf8');
const paths = new Set();
for (const line of content.split('\n')) {
const trimmed = line.trim();
const copyMatch = trimmed.match(/^COPY\s+(packages\/\S+)/);
if (copyMatch) {
paths.add(copyMatch[1]);
}
}
return paths;
}
// Extract @scope/package imports from a source file // Extract @scope/package imports from a source file
function extractImports(filePath) { function extractImports(filePath) {
if (!existsSync(filePath)) return []; if (!existsSync(filePath)) return [];
@ -173,13 +151,7 @@ function extractImports(filePath) {
// Validate a single Dockerfile and return result // Validate a single Dockerfile and return result
function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, opts = {}) { function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, opts = {}) {
const { const { checkImports = false, appDir = null, checkPatches = false } = opts;
isNestjsBase = false,
nestjsBasePackagePaths = new Set(),
checkImports = false,
appDir = null,
checkPatches = false,
} = opts;
if (!existsSync(pkgJsonPath)) { if (!existsSync(pkgJsonPath)) {
return { return {
@ -203,14 +175,6 @@ function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, op
continue; continue;
} }
// For nestjs-base backends, packages/* are pre-built in the base image
if (isNestjsBase && dirPath.startsWith('packages/')) {
const isCoveredByBase = [...nestjsBasePackagePaths].some(
(bp) => bp === dirPath || dirPath.startsWith(bp + '/') || bp.startsWith(dirPath)
);
if (isCoveredByBase) continue;
}
// Check if any COPY path matches or is a parent directory of this package // Check if any COPY path matches or is a parent directory of this package
const found = [...copyPaths].some( const found = [...copyPaths].some(
(cp) => cp === dirPath || dirPath.startsWith(cp + '/') || cp.startsWith(dirPath) (cp) => cp === dirPath || dirPath.startsWith(cp + '/') || cp.startsWith(dirPath)
@ -221,7 +185,7 @@ function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, op
} }
// Check patches (only for web apps that need them) // Check patches (only for web apps that need them)
if (checkPatches && !isNestjsBase && !hasPatchesCopy) { if (checkPatches && !hasPatchesCopy) {
errors.push('MISSING: patches/ directory → add: COPY patches/ ./patches/'); errors.push('MISSING: patches/ directory → add: COPY patches/ ./patches/');
} }
@ -281,7 +245,6 @@ function main() {
const servicesDir = join(ROOT, 'services'); const servicesDir = join(ROOT, 'services');
let hasErrors = false; let hasErrors = false;
const results = []; const results = [];
const nestjsBasePackagePaths = getNestjsBasePackages();
// Find all app directories // Find all app directories
const appDirs = readdirSync(appsDir, { withFileTypes: true }) const appDirs = readdirSync(appsDir, { withFileTypes: true })
@ -317,12 +280,8 @@ function main() {
const pkgJsonPath = join(appsDir, appName, 'apps', 'backend', 'package.json'); const pkgJsonPath = join(appsDir, appName, 'apps', 'backend', 'package.json');
const relPath = `apps/${appName}/apps/backend/Dockerfile`; const relPath = `apps/${appName}/apps/backend/Dockerfile`;
const isNestjsBase = usesNestjsBase(dockerfilePath);
const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, { const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap);
isNestjsBase,
nestjsBasePackagePaths,
});
if (result.errors.length > 0) hasErrors = true; if (result.errors.length > 0) hasErrors = true;
results.push(result); results.push(result);
printResult(result); printResult(result);