mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 16:41:08 +02:00
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:
parent
708299b35e
commit
ab387b9b3d
43 changed files with 598 additions and 2398 deletions
|
|
@ -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 |
|
||||
| [Database](./guidelines/database.md) | Drizzle ORM patterns, schema design, migrations |
|
||||
| [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 |
|
||||
| [SvelteKit Web](./guidelines/sveltekit-web.md) | Svelte 5 runes, stores, routing |
|
||||
| [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 |
|
||||
| **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 |
|
||||
| **Mobile** | Expo SDK 52-54 | React Native, NativeWind |
|
||||
| **Database** | PostgreSQL | Via Drizzle ORM |
|
||||
|
|
@ -62,15 +62,15 @@ manacore-monorepo/
|
|||
├── apps/ # Product applications
|
||||
│ └── {project}/
|
||||
│ ├── apps/
|
||||
│ │ ├── backend/ # NestJS API
|
||||
│ │ ├── server/ # Hono/Bun compute server
|
||||
│ │ ├── web/ # SvelteKit web
|
||||
│ │ ├── mobile/ # Expo app
|
||||
│ │ └── landing/ # Astro landing
|
||||
│ └── packages/ # Project-specific shared
|
||||
├── packages/ # Monorepo-wide shared
|
||||
│ ├── shared-errors/ # Error codes & Result types
|
||||
│ ├── shared-nestjs-auth/ # NestJS auth guards
|
||||
│ ├── shared-auth/ # Client auth service
|
||||
│ ├── local-store/ # Local-first data layer (Dexie.js + sync)
|
||||
│ └── ...
|
||||
├── services/ # Standalone microservices
|
||||
│ └── mana-core-auth/ # Central auth service
|
||||
|
|
@ -114,7 +114,8 @@ See [Error Handling](./guidelines/error-handling.md) for complete details.
|
|||
# Development
|
||||
pnpm install # Install dependencies
|
||||
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
|
||||
|
||||
# Quality
|
||||
|
|
@ -122,7 +123,7 @@ pnpm type-check # TypeScript validation
|
|||
pnpm format # Format code
|
||||
pnpm test # Run tests
|
||||
|
||||
# Database
|
||||
pnpm {project}:db:push # Push schema changes
|
||||
pnpm {project}:db:studio # Open Drizzle Studio
|
||||
# Database (for apps with Drizzle in server)
|
||||
pnpm --filter @{project}/server db:push # Push schema changes
|
||||
pnpm --filter @{project}/server db:studio # Open Drizzle Studio
|
||||
```
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ All authentication is handled by **Mana Core Auth**, a centralized authenticatio
|
|||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐
|
||||
│ Web/Mobile │────>│ Backend API │────>│ mana-core-auth │
|
||||
│ Client │ │ (NestJS) │ │ (port 3001) │
|
||||
│ Web/Mobile │────>│ Compute Server │────>│ mana-auth │
|
||||
│ Client │ │ (Hono/Bun) │ │ (port 3001) │
|
||||
└─────────────────┘ └─────────────────┘ └──────────────────┘
|
||||
│ │ │
|
||||
│ 1. Login │ │
|
||||
|
|
@ -85,101 +85,48 @@ Always use `text` type for `user_id` columns in all database schemas.
|
|||
|
||||
| Package | Purpose | Use Case |
|
||||
|---------|---------|----------|
|
||||
| `@manacore/shared-nestjs-auth` | NestJS guards/decorators | Backend APIs |
|
||||
| `@mana-core/nestjs-integration` | Auth + Credits integration | Backends with credits |
|
||||
| `@manacore/shared-hono` | Hono auth middleware + helpers | All compute servers (Hono/Bun) |
|
||||
| `@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
|
||||
|
||||
Use `@manacore/shared-nestjs-auth` for JWT validation:
|
||||
All compute servers use `@manacore/shared-hono`:
|
||||
|
||||
```typescript
|
||||
// app.module.ts
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { Hono } from 'hono';
|
||||
import { authMiddleware, healthRoute, errorHandler, notFoundHandler } from '@manacore/shared-hono';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
// No auth module needed - guards handle it
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
const app = new Hono();
|
||||
app.onError(errorHandler);
|
||||
app.notFound(notFoundHandler);
|
||||
app.route('/health', healthRoute('my-server'));
|
||||
|
||||
// Protect all /api/* routes
|
||||
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
|
||||
// file.controller.ts
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
### NestJS (arcade only)
|
||||
|
||||
@Controller('files')
|
||||
@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:
|
||||
`@arcade/backend` still uses NestJS with `@mana-core/nestjs-integration`:
|
||||
|
||||
```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 { CurrentUser } from '@mana-core/nestjs-integration/decorators';
|
||||
import { CreditClientService } from '@mana-core/nestjs-integration';
|
||||
|
||||
@Controller('generations')
|
||||
@Controller('api')
|
||||
@UseGuards(AuthGuard)
|
||||
export class GenerationController {
|
||||
constructor(private creditClient: CreditClientService) {}
|
||||
|
||||
@Post()
|
||||
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);
|
||||
export class ApiController {
|
||||
@Get('data')
|
||||
getData(@CurrentUser() user: any) {
|
||||
return this.service.findAll(user.sub);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -187,7 +134,7 @@ export class GenerationController {
|
|||
## Environment Variables
|
||||
|
||||
```env
|
||||
# Required for all backends
|
||||
# Required for all servers
|
||||
MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
|
||||
# Development bypass (optional)
|
||||
|
|
|
|||
|
|
@ -236,13 +236,12 @@ All SvelteKit web apps use **Phosphor icons** via `@manacore/shared-icons` (re-e
|
|||
- **Ideal**: 10-25 lines
|
||||
- Extract complex logic into helper functions
|
||||
|
||||
### Module Structure (NestJS)
|
||||
### Module Structure (Hono/Bun Server)
|
||||
|
||||
```
|
||||
feature/
|
||||
├── feature.controller.ts # HTTP layer
|
||||
├── feature.routes.ts # HTTP routes
|
||||
├── feature.service.ts # Business logic
|
||||
├── feature.module.ts # DI configuration
|
||||
├── feature.spec.ts # Tests
|
||||
└── dto/
|
||||
├── create-feature.dto.ts
|
||||
|
|
@ -340,5 +339,5 @@ pnpm format
|
|||
pnpm format:check
|
||||
|
||||
# Format specific project
|
||||
pnpm --filter @chat/backend format
|
||||
pnpm --filter @chat/server format
|
||||
```
|
||||
|
|
|
|||
137
.claude/guidelines/hono-server.md
Normal file
137
.claude/guidelines/hono-server.md
Normal 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 |
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -546,7 +546,7 @@ pnpm test
|
|||
pnpm test:cov
|
||||
|
||||
# Run specific project
|
||||
pnpm --filter @storage/backend test
|
||||
pnpm --filter @storage/server test
|
||||
|
||||
# Run in watch mode
|
||||
pnpm test:watch
|
||||
|
|
|
|||
18
.github/workflows/cd-macmini.yml
vendored
18
.github/workflows/cd-macmini.yml
vendored
|
|
@ -259,16 +259,6 @@ jobs:
|
|||
SERVICES="${{ steps.services.outputs.services }}"
|
||||
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
|
||||
if [ "$DEPLOY_ALL" == "true" ]; then
|
||||
NEEDS_WEB_BASE=true
|
||||
|
|
@ -278,14 +268,6 @@ jobs:
|
|||
done
|
||||
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
|
||||
echo "=== Building shared SvelteKit base image ==="
|
||||
docker build -f docker/Dockerfile.sveltekit-base -t sveltekit-base:local . 2>&1 | tail -5
|
||||
|
|
|
|||
144
CLAUDE.md
144
CLAUDE.md
|
|
@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||
|
||||
## 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)
|
||||
**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/database.md`](.claude/guidelines/database.md) | Drizzle ORM, schema patterns |
|
||||
| [`.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/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 |
|
||||
|
|
@ -123,8 +123,9 @@ pnpm run contacts:dev
|
|||
|
||||
# Start specific app within project
|
||||
pnpm run dev:chat:mobile # Just mobile app
|
||||
pnpm run dev:chat:backend # Just NestJS backend
|
||||
pnpm run dev:chat:app # Web + backend together
|
||||
pnpm run dev:chat:server # Just Hono/Bun server
|
||||
pnpm run dev:chat:local # sync + server + web (no auth needed)
|
||||
pnpm run dev:chat:app # Server + web together
|
||||
|
||||
# Build & quality
|
||||
pnpm run build
|
||||
|
|
@ -143,7 +144,7 @@ manacore-monorepo/
|
|||
├── apps/ # Active SaaS product applications
|
||||
│ ├── chat/
|
||||
│ │ ├── apps/
|
||||
│ │ │ ├── backend/ # NestJS API
|
||||
│ │ │ ├── server/ # Hono/Bun compute server
|
||||
│ │ │ ├── mobile/ # Expo React Native app
|
||||
│ │ │ ├── web/ # SvelteKit web app
|
||||
│ │ │ └── landing/ # Astro marketing page
|
||||
|
|
@ -193,7 +194,7 @@ manacore-monorepo/
|
|||
```
|
||||
apps/{project}/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API (when present)
|
||||
│ ├── server/ # Hono/Bun compute server (when present)
|
||||
│ ├── mobile/ # Expo React Native app
|
||||
│ ├── web/ # SvelteKit web app
|
||||
│ └── landing/ # Astro marketing page
|
||||
|
|
@ -259,11 +260,11 @@ Parent workspace packages (e.g., `apps/chat/package.json`, `apps/zitare/package.
|
|||
- Tailwind CSS
|
||||
- Static site generation
|
||||
|
||||
**Backends (NestJS):**
|
||||
**Compute Servers (Hono + Bun):**
|
||||
|
||||
- NestJS 10-11
|
||||
- TypeScript
|
||||
- Supabase integration
|
||||
- Hono 4.x + Bun runtime
|
||||
- TypeScript, Drizzle ORM (where needed)
|
||||
- `@manacore/shared-hono` for auth middleware
|
||||
|
||||
### Authentication Architecture
|
||||
|
||||
|
|
@ -271,8 +272,8 @@ All projects use **mana-core-auth** as the central authentication service:
|
|||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌────────────────┐
|
||||
│ Client │────>│ Backend │────>│ mana-core-auth │
|
||||
│ (Web/Mobile)│ │ (NestJS) │ │ (port 3001) │
|
||||
│ Client │────>│ Server │────>│ mana-auth │
|
||||
│ (Web/Mobile)│ │ (Hono/Bun) │ │ (port 3001) │
|
||||
└─────────────┘ └─────────────┘ └────────────────┘
|
||||
│ │ │
|
||||
│ Bearer token │ POST /validate │
|
||||
|
|
@ -287,82 +288,36 @@ All projects use **mana-core-auth** as the central authentication service:
|
|||
|
||||
| Component | Purpose |
|
||||
| ------------------------------- | -------------------------------------------------- |
|
||||
| `services/mana-core-auth` | Central auth service (Better Auth + EdDSA JWT) |
|
||||
| `@manacore/shared-nestjs-auth` | Shared NestJS guards/decorators for JWT validation |
|
||||
| `@mana-core/nestjs-integration` | Extended NestJS module with auth + credits |
|
||||
| `services/mana-auth` | Central auth service (Better Auth + EdDSA JWT) |
|
||||
| `@manacore/shared-hono` | Shared Hono middleware for JWT validation |
|
||||
| `@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
|
||||
// In your controller
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { authMiddleware, healthRoute, errorHandler, notFoundHandler } from '@manacore/shared-hono';
|
||||
|
||||
@Controller('api')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class MyController {
|
||||
@Get('profile')
|
||||
getProfile(@CurrentUser() user: CurrentUserData) {
|
||||
return { userId: user.userId, email: user.email };
|
||||
}
|
||||
}
|
||||
```
|
||||
const app = new Hono();
|
||||
app.onError(errorHandler);
|
||||
app.notFound(notFoundHandler);
|
||||
app.route('/health', healthRoute('my-server'));
|
||||
app.use('/api/*', authMiddleware());
|
||||
|
||||
**Option 2: Auth + Credits** - Use `@mana-core/nestjs-integration`:
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
}
|
||||
}
|
||||
// In route handlers, get user from context:
|
||||
app.get('/api/v1/data', (c) => {
|
||||
const userId = c.get('userId');
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
#### Required Environment Variables
|
||||
|
||||
```env
|
||||
# All backends need this
|
||||
# All servers need this
|
||||
MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
|
||||
# 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
|
||||
CORS_ORIGINS=http://localhost:5173
|
||||
```
|
||||
|
||||
#### JWT Token Structure (EdDSA)
|
||||
|
|
@ -382,11 +337,11 @@ APP_ID=your-app-id
|
|||
#### Testing Auth Integration
|
||||
|
||||
```bash
|
||||
# 1. Start mana-core-auth
|
||||
# 1. Start mana-auth
|
||||
pnpm dev:auth
|
||||
|
||||
# 2. Start a backend (e.g., Zitare)
|
||||
pnpm dev:zitare:backend
|
||||
# 2. Start an app locally
|
||||
pnpm dev:contacts:local
|
||||
|
||||
# 3. Get a token
|
||||
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')
|
||||
|
||||
# 4. Call protected endpoint
|
||||
curl http://localhost:3007/api/favorites \
|
||||
curl http://localhost:3033/api/v1/import/vcard \
|
||||
-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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
#### Usage in Backend
|
||||
#### Usage in Server
|
||||
|
||||
```typescript
|
||||
// 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:build # Compile Go binary
|
||||
pnpm dev:todo:server # Hono/Bun compute server (port 3019)
|
||||
pnpm dev:todo:local # Web + sync + Hono (no auth/NestJS needed)
|
||||
pnpm dev:todo:full # Everything incl. auth + NestJS legacy
|
||||
pnpm dev:todo:local # Web + sync + server (no auth needed)
|
||||
pnpm dev:todo:full # Everything incl. auth + DB setup
|
||||
```
|
||||
|
||||
### Adding Local-First to a New App
|
||||
|
|
@ -696,8 +641,7 @@ Full migration plan: `.claude/plans/local-first-architecture-migration.md`
|
|||
| Package | Purpose |
|
||||
| ------------------------------- | ----------------------------------------------- |
|
||||
| `@manacore/local-store` | Local-first data layer (Dexie.js + sync engine) |
|
||||
| `@manacore/shared-nestjs-auth` | NestJS JWT validation guards via mana-core-auth |
|
||||
| `@mana-core/nestjs-integration` | NestJS module with auth guards + credit client |
|
||||
| `@manacore/shared-hono` | Shared Hono middleware (auth, health, errors) |
|
||||
| `@manacore/shared-auth` | Client-side auth service for web/mobile apps |
|
||||
| `@manacore/shared-storage` | S3-compatible storage (MinIO) |
|
||||
| `@manacore/shared-types` | Common TypeScript types |
|
||||
|
|
@ -756,7 +700,7 @@ pnpm docker:up
|
|||
| `contacts-storage` | Contacts | Contact avatars/files |
|
||||
| `storage-storage` | Storage | Cloud drive files |
|
||||
|
||||
### Usage in Backend
|
||||
### Usage in Server
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
- **`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`
|
||||
|
||||
|
|
@ -1010,7 +954,7 @@ The script reads `.env.development` and generates platform-specific `.env` files
|
|||
|
||||
- **Expo mobile**: `EXPO_PUBLIC_*` prefix
|
||||
- **SvelteKit web**: `PUBLIC_*` prefix
|
||||
- **NestJS backend**: No prefix
|
||||
- **Hono/Bun server**: No prefix
|
||||
|
||||
### Key Files
|
||||
|
||||
|
|
@ -1041,12 +985,12 @@ PUBLIC_SUPABASE_URL=...
|
|||
PUBLIC_SUPABASE_ANON_KEY=...
|
||||
```
|
||||
|
||||
**Backend (NestJS):**
|
||||
**Server (Hono/Bun):**
|
||||
|
||||
```
|
||||
SUPABASE_URL=...
|
||||
SUPABASE_SERVICE_ROLE_KEY=...
|
||||
PORT=...
|
||||
DATABASE_URL=...
|
||||
MANA_CORE_AUTH_URL=...
|
||||
```
|
||||
|
||||
## Project-Specific Documentation
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
| App | Port | URL |
|
||||
|-----|------|-----|
|
||||
| Backend | 3014 | http://localhost:3014 |
|
||||
| Server | 3014 | http://localhost:3014 |
|
||||
| Web App | 5179 | http://localhost:5179 |
|
||||
| Landing Page | 4322 | http://localhost:4322 |
|
||||
| Mobile | 8081 | Expo Go |
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
```
|
||||
apps/calendar/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API server (@calendar/backend)
|
||||
│ ├── server/ # Hono/Bun compute server (@calendar/server)
|
||||
│ │ └── src/
|
||||
│ │ ├── main.ts
|
||||
│ │ ├── app.module.ts
|
||||
|
|
@ -114,11 +114,12 @@ apps/calendar/
|
|||
pnpm calendar:dev # Run all calendar apps
|
||||
|
||||
# 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:landing # Start landing page (port 4322)
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
### Backend (apps/calendar/apps/backend)
|
||||
### Server (apps/calendar/apps/server)
|
||||
|
||||
```bash
|
||||
pnpm dev # Start with hot reload
|
||||
|
|
@ -157,7 +158,7 @@ pnpm preview # Preview build
|
|||
|
||||
| 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 |
|
||||
| **Landing** | Astro 5.x, Tailwind CSS |
|
||||
| **Mobile** | React Native 0.81 + Expo SDK 54, NativeWind [TODO] |
|
||||
|
|
@ -224,7 +225,7 @@ eventsStore.updateEvent(id, data)
|
|||
eventsStore.deleteEvent(id)
|
||||
```
|
||||
|
||||
### Backend API Endpoints
|
||||
### Server API Endpoints
|
||||
|
||||
#### Health
|
||||
|
||||
|
|
@ -400,7 +401,7 @@ FREQ=WEEKLY;UNTIL=20241231T235959Z # Wöchentlich bis Ende 2024
|
|||
|
||||
## Environment Variables
|
||||
|
||||
### Backend (.env)
|
||||
### Server (.env)
|
||||
|
||||
```env
|
||||
NODE_ENV=development
|
||||
|
|
@ -544,11 +545,11 @@ pnpm calendar:db:push
|
|||
### 2. Apps starten
|
||||
|
||||
```bash
|
||||
# Backend + Web zusammen
|
||||
# Server + Web zusammen
|
||||
pnpm dev:calendar:app
|
||||
|
||||
# Oder einzeln:
|
||||
pnpm dev:calendar:backend # Terminal 1
|
||||
pnpm dev:calendar:server # Terminal 1
|
||||
pnpm dev:calendar:web # Terminal 2
|
||||
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)
|
||||
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
|
||||
5. **i18n**: 5 Sprachen unterstützt (DE, EN, FR, ES, IT)
|
||||
6. **Theme**: Ocean-Theme (Blautöne) als Standard
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
"description": "Calendar App - Personal and Shared Calendars with CalDAV/iCal Sync",
|
||||
"scripts": {
|
||||
"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:landing": "pnpm --filter @calendar/landing dev",
|
||||
"dev:mobile": "pnpm --filter @calendar/mobile dev",
|
||||
"db:push": "pnpm --filter @calendar/backend db:push",
|
||||
"db:studio": "pnpm --filter @calendar/backend db:studio",
|
||||
"db:seed": "pnpm --filter @calendar/backend db:seed"
|
||||
"db:push": "pnpm --filter @calendar/server db:push",
|
||||
"db:studio": "pnpm --filter @calendar/server db:studio",
|
||||
"db:seed": "pnpm --filter @calendar/server db:seed"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
```
|
||||
apps/chat/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API server (@chat/backend)
|
||||
│ ├── server/ # Hono/Bun compute server (@chat/server)
|
||||
│ ├── landing/ # Astro marketing landing page (@chat/landing)
|
||||
│ ├── web/ # SvelteKit web application (@chat/web)
|
||||
│ └── 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:web # Start web app
|
||||
pnpm dev:chat:landing # Start landing page
|
||||
pnpm dev:chat:backend # Start backend server
|
||||
pnpm dev:chat:full # Start backend + web + auth together
|
||||
pnpm dev:chat:server # Start server
|
||||
pnpm dev:chat:local # Start web + sync (no auth needed)
|
||||
pnpm dev:chat:full # Start server + web + auth together
|
||||
```
|
||||
|
||||
### Mobile App (chat/apps/mobile)
|
||||
|
|
@ -38,7 +39,7 @@ pnpm build:preview # Build preview version
|
|||
pnpm build:prod # Build production version
|
||||
```
|
||||
|
||||
### Backend (apps/chat/apps/backend)
|
||||
### Server (apps/chat/apps/server)
|
||||
|
||||
```bash
|
||||
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
|
||||
- **Web**: SvelteKit 2.x, Svelte 5, Tailwind CSS 4
|
||||
- **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)
|
||||
- **Types**: TypeScript 5.x
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend API Endpoints
|
||||
### Server API Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------------------------- | ------ | --------------------------- |
|
||||
|
|
@ -91,7 +92,7 @@ pnpm preview # Preview production build
|
|||
|
||||
### Environment Variables
|
||||
|
||||
#### Backend (.env)
|
||||
#### Server (.env)
|
||||
|
||||
```env
|
||||
# Cloud AI models via OpenRouter (optional if using only local models)
|
||||
|
|
@ -162,13 +163,13 @@ PUBLIC_BACKEND_URL=http://localhost:3002
|
|||
|
||||
```bash
|
||||
# Add new models to existing database
|
||||
pnpm --filter @chat/backend db:add-local-models
|
||||
pnpm --filter @chat/server db:add-local-models
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
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
|
||||
OPENROUTER_API_KEY=sk-or-v1-xxx
|
||||
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):
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:push
|
||||
pnpm --filter @chat/backend db:seed
|
||||
pnpm --filter @chat/server db:push
|
||||
pnpm --filter @chat/server db:seed
|
||||
```
|
||||
|
||||
## 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)
|
||||
3. **Database**: PostgreSQL with Drizzle ORM (uses shared Docker container)
|
||||
4. **Deployment**: Backend runs on port 3002
|
||||
4. **Deployment**: Server runs on port 3002
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
```
|
||||
apps/contacts/
|
||||
├── 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)
|
||||
│ ├── web/ # SvelteKit web application (@contacts/web) - Port 5184
|
||||
│ └── 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:web # Start web app
|
||||
pnpm dev:contacts:landing # Start landing page
|
||||
pnpm dev:contacts:backend # Start backend server
|
||||
pnpm dev:contacts:app # Start web + backend together
|
||||
pnpm dev:contacts:server # Start server
|
||||
pnpm dev:contacts:app # Start web + server together
|
||||
pnpm dev:contacts:local # Start web + sync (no auth needed)
|
||||
```
|
||||
|
||||
### Mobile App (apps/contacts/apps/mobile)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
"description": "Contacts App - Contact Management with Manacore Integration",
|
||||
"scripts": {
|
||||
"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:landing": "pnpm --filter @contacts/landing dev",
|
||||
"dev:mobile": "pnpm --filter @contacts/mobile dev",
|
||||
"db:push": "pnpm --filter @contacts/backend db:push",
|
||||
"db:studio": "pnpm --filter @contacts/backend db:studio",
|
||||
"db:seed": "pnpm --filter @contacts/backend db:seed"
|
||||
"db:push": "pnpm --filter @contacts/server db:push",
|
||||
"db:studio": "pnpm --filter @contacts/server db:studio",
|
||||
"db:seed": "pnpm --filter @contacts/server db:seed"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ AI-powered document management and context system for knowledge organization.
|
|||
```
|
||||
apps/context/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API server (@context/backend)
|
||||
│ ├── backend/ # Hono/Bun compute server (@context/server)
|
||||
│ │ └── src/
|
||||
│ │ ├── main.ts
|
||||
│ │ ├── app.module.ts
|
||||
|
|
@ -45,7 +45,7 @@ apps/context/
|
|||
```bash
|
||||
# From monorepo root
|
||||
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:app # Start web + backend together
|
||||
pnpm dev:context:mobile # Start mobile app
|
||||
|
|
@ -61,7 +61,7 @@ pnpm setup:db:context # Create DB + push schema
|
|||
|
||||
| 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 |
|
||||
| **Mobile** | React Native 0.76 + Expo SDK 52, NativeWind |
|
||||
| **Auth** | Mana Core Auth (JWT) |
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"scripts": {
|
||||
"dev": "turbo run 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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,16 +25,16 @@ Each backend service has its own database and schema:
|
|||
|
||||
```bash
|
||||
# Push schema changes (development)
|
||||
pnpm --filter @chat/backend db:push
|
||||
pnpm --filter @chat/server db:push
|
||||
|
||||
# Generate migration files
|
||||
pnpm --filter @chat/backend db:generate
|
||||
pnpm --filter @chat/server db:generate
|
||||
|
||||
# Run migrations
|
||||
pnpm --filter @chat/backend db:migrate
|
||||
pnpm --filter @chat/server db:migrate
|
||||
|
||||
# Open Drizzle Studio (database GUI)
|
||||
pnpm --filter @chat/backend db:studio
|
||||
pnpm --filter @chat/server db:studio
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
|
@ -60,13 +60,13 @@ For local development, use `db:push` to quickly sync schema changes:
|
|||
2. **Push changes**
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:push
|
||||
pnpm --filter @chat/server db:push
|
||||
```
|
||||
|
||||
3. **Verify in Studio**
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:studio
|
||||
pnpm --filter @chat/server db:studio
|
||||
```
|
||||
</Steps>
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ For production, use proper migration files:
|
|||
1. **Generate migration**
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:generate
|
||||
pnpm --filter @chat/server db:generate
|
||||
```
|
||||
|
||||
This creates a timestamped SQL file in `drizzle/migrations/`.
|
||||
|
|
@ -99,7 +99,7 @@ For production, use proper migration files:
|
|||
3. **Run migration**
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:migrate
|
||||
pnpm --filter @chat/server db:migrate
|
||||
```
|
||||
</Steps>
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ export class UsersService {
|
|||
Schema not pushed:
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:push --force
|
||||
pnpm --filter @chat/server db:push --force
|
||||
```
|
||||
|
||||
### Migration conflicts
|
||||
|
|
@ -253,7 +253,7 @@ Reset and regenerate:
|
|||
rm -rf apps/chat/apps/backend/drizzle/migrations/*
|
||||
|
||||
# Regenerate
|
||||
pnpm --filter @chat/backend db:generate
|
||||
pnpm --filter @chat/server db:generate
|
||||
```
|
||||
|
||||
### Connection refused
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ If you see errors about missing tables/columns:
|
|||
|
||||
```bash
|
||||
# Push the latest schema
|
||||
pnpm --filter @chat/backend db:push --force
|
||||
pnpm --filter @chat/server db:push --force
|
||||
```
|
||||
|
||||
### Port already in use
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ Manacore uses Jest for backend testing and Vitest for frontend testing.
|
|||
pnpm test
|
||||
|
||||
# Run tests for specific project
|
||||
pnpm --filter @chat/backend test
|
||||
pnpm --filter @chat/server test
|
||||
|
||||
# Run tests in watch mode
|
||||
pnpm --filter @chat/backend test:watch
|
||||
pnpm --filter @chat/server test:watch
|
||||
|
||||
# Run with coverage
|
||||
pnpm --filter @chat/backend test:cov
|
||||
pnpm --filter @chat/server test:cov
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ Turborepo handles task orchestration with:
|
|||
|
||||
| Type | Pattern | Example |
|
||||
|------|---------|---------|
|
||||
| App package | `@{project}/{app}` | `@chat/backend` |
|
||||
| App package | `@{project}/{app}` | `@chat/server` |
|
||||
| Shared package | `@manacore/shared-{name}` | `@manacore/shared-auth` |
|
||||
| Service | `@mana-{name}/service` | `@mana-search/service` |
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ AI chat application supporting multiple language models including local (Ollama)
|
|||
|
||||
| Component | Technology | Port |
|
||||
|-----------|------------|------|
|
||||
| Backend | NestJS | 3002 |
|
||||
| Server | Hono/Bun | 3002 |
|
||||
| Web | SvelteKit | 5173 |
|
||||
| Mobile | Expo | - |
|
||||
| Landing | Astro | - |
|
||||
|
|
@ -27,7 +27,7 @@ AI chat application supporting multiple language models including local (Ollama)
|
|||
pnpm dev:chat:full
|
||||
|
||||
# Or individual components
|
||||
pnpm dev:chat:backend
|
||||
pnpm dev:chat:server
|
||||
pnpm dev:chat:web
|
||||
pnpm dev:chat:mobile
|
||||
```
|
||||
|
|
@ -36,8 +36,8 @@ pnpm dev:chat:mobile
|
|||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Client │────>│ Backend │────>│ AI Models │
|
||||
│ (Web/Mobile)│ │ (NestJS) │ │ Ollama/API │
|
||||
│ Client │────>│ Server │────>│ AI Models │
|
||||
│ (Web/Mobile)│ │ (Hono/Bun) │ │ Ollama/API │
|
||||
└─────────────┘ └──────┬──────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
|
|
@ -147,21 +147,21 @@ export const messages = pgTable('messages', {
|
|||
First time setup requires seeding AI models:
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:push
|
||||
pnpm --filter @chat/backend db:seed
|
||||
pnpm --filter @chat/server db:push
|
||||
pnpm --filter @chat/server db:seed
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend test
|
||||
pnpm --filter @chat/backend test:e2e
|
||||
pnpm --filter @chat/server test
|
||||
pnpm --filter @chat/server test:e2e
|
||||
```
|
||||
|
||||
### Open Database GUI
|
||||
|
||||
```bash
|
||||
pnpm --filter @chat/backend db:studio
|
||||
pnpm --filter @chat/server db:studio
|
||||
```
|
||||
|
||||
## Links
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Mukke is a web application for managing your music library, playing tracks, and
|
|||
```
|
||||
apps/mukke/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API (port 3010)
|
||||
│ ├── backend/ # Hono/Bun server (port 3010)
|
||||
│ ├── web/ # SvelteKit app (port 5180)
|
||||
│ └── landing/ # Astro marketing page
|
||||
├── packages/
|
||||
|
|
@ -23,7 +23,7 @@ pnpm dev:mukke:full
|
|||
|
||||
# Or start components individually
|
||||
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/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 |
|
||||
| BPM Detection | Web Audio API (peak detection) |
|
||||
| Metadata | music-metadata (server-side) |
|
||||
| Backend | NestJS 10, Drizzle ORM |
|
||||
| Backend | Hono + Bun, Drizzle ORM |
|
||||
| Database | PostgreSQL |
|
||||
| Storage | MinIO (S3-compatible) |
|
||||
| Auth | mana-core-auth |
|
||||
|
|
@ -178,14 +178,14 @@ PUBLIC_BACKEND_URL=http://localhost:3010
|
|||
|
||||
```bash
|
||||
# Database
|
||||
pnpm --filter @mukke/backend db:push # Push schema
|
||||
pnpm --filter @mukke/backend db:studio # Open Drizzle Studio
|
||||
pnpm --filter @mukke/server db:push # Push schema
|
||||
pnpm --filter @mukke/server db:studio # Open Drizzle Studio
|
||||
|
||||
# Type checking
|
||||
pnpm --filter @mukke/backend type-check
|
||||
pnpm --filter @mukke/server type-check
|
||||
pnpm --filter @mukke/web type-check
|
||||
|
||||
# Build
|
||||
pnpm --filter @mukke/backend build
|
||||
pnpm --filter @mukke/server build
|
||||
pnpm --filter @mukke/web build
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
```
|
||||
apps/nutriphi/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API server (@nutriphi/backend)
|
||||
│ ├── backend/ # Hono/Bun compute server (@nutriphi/server)
|
||||
│ │ └── src/
|
||||
│ │ ├── main.ts
|
||||
│ │ ├── app.module.ts
|
||||
|
|
@ -71,7 +71,7 @@ apps/nutriphi/
|
|||
pnpm nutriphi:dev
|
||||
|
||||
# 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:landing # Landing page (port 4323)
|
||||
pnpm dev:nutriphi:app # Web + backend together
|
||||
|
|
@ -108,7 +108,7 @@ pnpm build # Build for production
|
|||
|
||||
| Layer | Technology |
|
||||
|-------|------------|
|
||||
| **Backend** | NestJS 10, Drizzle ORM, PostgreSQL |
|
||||
| **Backend** | Hono + Bun, Drizzle ORM, PostgreSQL |
|
||||
| **AI** | Google Gemini 2.5 Flash |
|
||||
| **Web** | SvelteKit 2.x, Svelte 5 (runes mode), Tailwind CSS 4 |
|
||||
| **Landing** | Astro 5.x, Tailwind CSS |
|
||||
|
|
@ -306,7 +306,7 @@ GEMINI_API_KEY=your-gemini-api-key
|
|||
pnpm dev:nutriphi:app
|
||||
|
||||
# Or individually:
|
||||
pnpm dev:nutriphi:backend # Terminal 1
|
||||
pnpm dev:nutriphi:server # Terminal 1
|
||||
pnpm dev:nutriphi:web # Terminal 2
|
||||
pnpm dev:nutriphi:landing # Terminal 3
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@
|
|||
"description": "NutriPhi - AI-powered nutrition tracking with photo analysis",
|
||||
"scripts": {
|
||||
"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:landing": "pnpm --filter @nutriphi/landing dev",
|
||||
"db:push": "pnpm --filter @nutriphi/backend db:push",
|
||||
"db:studio": "pnpm --filter @nutriphi/backend db:studio",
|
||||
"db:seed": "pnpm --filter @nutriphi/backend db:seed",
|
||||
"test": "pnpm --filter @nutriphi/backend test && pnpm --filter @nutriphi/shared test && pnpm --filter @nutriphi/web test",
|
||||
"test:backend": "pnpm --filter @nutriphi/backend test",
|
||||
"db:push": "pnpm --filter @nutriphi/server db:push",
|
||||
"db:studio": "pnpm --filter @nutriphi/server db:studio",
|
||||
"db:seed": "pnpm --filter @nutriphi/server db:seed",
|
||||
"test": "pnpm --filter @nutriphi/server test && pnpm --filter @nutriphi/shared test && pnpm --filter @nutriphi/web test",
|
||||
"test:backend": "pnpm --filter @nutriphi/server test",
|
||||
"test:web": "pnpm --filter @nutriphi/web 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": {
|
||||
"typescript": "^5.9.3"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ AI image generation app using Replicate API with freemium credit system.
|
|||
```
|
||||
apps/picture/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API (port 3006)
|
||||
│ ├── backend/ # Hono/Bun server (port 3006)
|
||||
│ ├── mobile/ # Expo React Native app
|
||||
│ ├── web/ # SvelteKit web app
|
||||
│ └── landing/ # Astro marketing page
|
||||
|
|
@ -21,7 +21,7 @@ apps/picture/
|
|||
pnpm dev:picture:full # Start backend + web + auto DB setup
|
||||
|
||||
# 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/mobile dev # Mobile only
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
```
|
||||
apps/planta/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API server (@planta/backend)
|
||||
│ ├── backend/ # Hono/Bun compute server (@planta/server)
|
||||
│ └── web/ # SvelteKit web application (@planta/web)
|
||||
├── packages/
|
||||
│ └── shared/ # Shared types, utils (@planta/shared)
|
||||
|
|
@ -19,7 +19,7 @@ apps/planta/
|
|||
```bash
|
||||
pnpm planta:dev # Run all planta apps
|
||||
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:full # Start auth + backend + web with DB setup
|
||||
```
|
||||
|
|
@ -45,7 +45,7 @@ pnpm preview # Preview production build
|
|||
## Technology Stack
|
||||
|
||||
- **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
|
||||
- **Storage**: MinIO (S3-compatible)
|
||||
- **Auth**: Mana Core Auth (JWT)
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
"description": "Planta - Plant Documentation & Care App",
|
||||
"scripts": {
|
||||
"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",
|
||||
"db:push": "pnpm --filter @planta/backend db:push",
|
||||
"db:studio": "pnpm --filter @planta/backend db:studio",
|
||||
"db:seed": "pnpm --filter @planta/backend db:seed"
|
||||
"db:push": "pnpm --filter @planta/server db:push",
|
||||
"db:studio": "pnpm --filter @planta/server db:studio",
|
||||
"db:seed": "pnpm --filter @planta/server db:seed"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
```
|
||||
apps/storage/
|
||||
├── 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)
|
||||
│ └── web/ # SvelteKit web application (@storage/web) - Port 5185
|
||||
├── packages/
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
```
|
||||
apps/todo/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API server (@todo/backend)
|
||||
│ ├── backend/ # Hono/Bun compute server (@todo/server)
|
||||
│ ├── web/ # SvelteKit web application (@todo/web)
|
||||
│ └── landing/ # Astro marketing landing page (@todo/landing)
|
||||
├── packages/
|
||||
|
|
@ -75,7 +75,7 @@ pnpm preview # Preview build
|
|||
|
||||
| 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 |
|
||||
| **Landing** | Astro 5.x, Tailwind CSS |
|
||||
| **Auth** | Mana Core Auth (JWT) |
|
||||
|
|
@ -300,7 +300,7 @@ Priority: explicit duration in text > history estimate > default fallback > none
|
|||
|
||||
```bash
|
||||
# Unit tests
|
||||
pnpm --filter @todo/backend test
|
||||
pnpm --filter @todo/server test
|
||||
pnpm --filter @todo/web test
|
||||
|
||||
# E2E tests
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
"description": "Todo App - Task Management for ManaCore Ecosystem",
|
||||
"scripts": {
|
||||
"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:landing": "pnpm --filter @todo/landing dev",
|
||||
"db:push": "pnpm --filter @todo/backend db:push",
|
||||
"db:studio": "pnpm --filter @todo/backend db:studio",
|
||||
"db:seed": "pnpm --filter @todo/backend db:seed"
|
||||
"db:push": "pnpm --filter @todo/server db:push",
|
||||
"db:studio": "pnpm --filter @todo/server db:studio",
|
||||
"db:seed": "pnpm --filter @todo/server db:seed"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ GPS tracking app with AI city guides. Location tracking runs locally via AsyncSt
|
|||
apps/traces/
|
||||
├── package.json # Orchestrator (name: traces)
|
||||
├── apps/
|
||||
│ ├── backend/ # @traces/backend (NestJS, Port 3026)
|
||||
│ ├── backend/ # @traces/server (NestJS, Port 3026)
|
||||
│ │ └── src/
|
||||
│ │ ├── main.ts
|
||||
│ │ ├── app.module.ts
|
||||
|
|
@ -31,7 +31,7 @@ apps/traces/
|
|||
```bash
|
||||
# Development
|
||||
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
|
||||
|
||||
# Database
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
|
@ -246,8 +246,8 @@ pnpm --filter mana-core-auth db:generate
|
|||
pnpm --filter mana-core-auth db:migrate
|
||||
|
||||
# chat-backend
|
||||
pnpm --filter @chat/backend db:push
|
||||
pnpm --filter @chat/backend db:migrate
|
||||
pnpm --filter @chat/server db:push
|
||||
pnpm --filter @chat/server db:migrate
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ The generator reads `.env.development` and creates app-specific `.env` files wit
|
|||
|----------|--------|---------|
|
||||
| Expo (mobile) | `EXPO_PUBLIC_` | `EXPO_PUBLIC_SUPABASE_URL` |
|
||||
| SvelteKit (web) | `PUBLIC_` | `PUBLIC_SUPABASE_URL` |
|
||||
| NestJS (backend) | None | `SUPABASE_URL` |
|
||||
| Hono/Bun (server) | None | `DATABASE_URL` |
|
||||
|
||||
## 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
|
||||
|
||||
### Generated Files (gitignored)
|
||||
- `services/mana-core-auth/.env`
|
||||
- `apps/chat/apps/backend/.env`
|
||||
- `services/mana-auth/.env`
|
||||
- `apps/chat/apps/server/.env`
|
||||
- `apps/chat/apps/mobile/.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/web/.env`
|
||||
- `apps/memoro/apps/mobile/.env`
|
||||
- `apps/memoro/apps/web/.env`
|
||||
- `apps/manadeck/apps/backend/.env`
|
||||
- `apps/manadeck/apps/server/.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
|
||||
|
||||
|
|
@ -98,28 +96,6 @@ The generator reads `.env.development` and creates app-specific `.env` files wit
|
|||
| `CHAT_SUPABASE_URL` | Supabase project URL | - |
|
||||
| `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
|
||||
|
||||
| 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: {
|
||||
// For NestJS, no prefix needed
|
||||
// For Hono/Bun servers, no prefix needed
|
||||
API_KEY: (env) => env.MY_NEW_PROJECT_API_KEY,
|
||||
API_URL: (env) => env.MY_NEW_PROJECT_URL,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,35 +1,94 @@
|
|||
# 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
|
||||
|
||||
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
|
||||
pnpm dev:chat:full # Start chat with auth + database setup
|
||||
pnpm dev:zitare:full # Start zitare with auth + database setup
|
||||
pnpm dev:contacts:full # Start contacts with auth + database setup
|
||||
# ... etc
|
||||
pnpm dev:todo:local # sync + server + web
|
||||
pnpm dev:chat:local # sync + server + web
|
||||
pnpm dev:zitare:local # sync + web (no server needed)
|
||||
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:
|
||||
1. Create the database if it doesn't exist
|
||||
2. Push the latest schema (Drizzle `db:push`)
|
||||
3. Start the auth service (mana-core-auth)
|
||||
4. Start the backend and web app with colored output
|
||||
1. Create the database if it doesn't exist (for apps with Drizzle schemas)
|
||||
2. Push the latest schema (`drizzle-kit push --force`)
|
||||
3. Start the auth service (mana-auth)
|
||||
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 |
|
||||
|---------|----------|--------------|----------|
|
||||
| `pnpm dev:chat:full` | chat | 3002 | 5173 |
|
||||
| `pnpm dev:zitare:full` | zitare | 3007 | 5177 |
|
||||
| `pnpm dev:contacts:full` | contacts | 3015 | 5184 |
|
||||
| `pnpm dev:calendar:full` | calendar | 3014 | 5179 |
|
||||
| `pnpm dev:clock:full` | clock | 3017 | 5187 |
|
||||
| `pnpm dev:todo:full` | todo | 3018 | 5188 |
|
||||
| `pnpm dev:picture:full` | picture | 3006 | 5175 |
|
||||
### Apps with Hono/Bun Servers
|
||||
|
||||
These apps have server-side compute and support both `local` and `full` modes:
|
||||
|
||||
| App | Server Port | Web Port | `local` | `full` |
|
||||
|-----|-------------|----------|---------|--------|
|
||||
| Todo | 3019 | 5188 | Yes | Yes |
|
||||
| Chat | 3002 | 5174 | Yes | Yes |
|
||||
| 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
|
||||
|
||||
|
|
@ -39,41 +98,65 @@ Before running any `dev:*:full` command:
|
|||
# 1. Start Docker infrastructure (PostgreSQL, Redis, MinIO)
|
||||
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
|
||||
```
|
||||
|
||||
For `dev:*:local`, only mana-sync needs to be built. No Docker required unless your server uses a database.
|
||||
|
||||
## Database Setup Commands
|
||||
|
||||
### Individual Service Setup
|
||||
|
||||
```bash
|
||||
pnpm setup:db:auth # Setup mana-core-auth database + schema
|
||||
pnpm setup:db:chat # Setup chat database + schema
|
||||
pnpm setup:db:zitare # Setup zitare database + schema
|
||||
pnpm setup:db:contacts # Setup contacts database + schema
|
||||
pnpm setup:db:calendar # Setup calendar database + schema
|
||||
pnpm setup:db:clock # Setup clock database + schema
|
||||
pnpm setup:db:todo # Setup todo database + schema
|
||||
pnpm setup:db:picture # Setup picture database + schema
|
||||
pnpm setup:db:auth # Setup mana-auth database + schema
|
||||
pnpm setup:db:chat # Setup chat database + schema
|
||||
pnpm setup:db:todo # Setup todo database + schema
|
||||
pnpm setup:db:contacts # Setup contacts database + schema
|
||||
pnpm setup:db:calendar # Setup calendar database + schema
|
||||
pnpm setup:db:picture # Setup picture database + schema
|
||||
pnpm setup:db:uload # Setup uload 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
|
||||
|
||||
```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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
On first `pnpm docker:up`, the PostgreSQL container runs `docker/init-db/01-create-databases.sql` which creates all databases.
|
||||
|
||||
### Setup Script
|
||||
|
||||
|
|
@ -84,6 +167,16 @@ The `scripts/setup-databases.sh` script:
|
|||
|
||||
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
|
||||
|
||||
### 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:
|
||||
|
||||
```bash
|
||||
# Push the latest schema
|
||||
pnpm --filter @chat/backend db:push --force
|
||||
# Push the latest schema from the server package
|
||||
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
|
||||
|
||||
If auth (port 3001) is already running:
|
||||
If auth (port 3001) or sync (port 3050) is already running:
|
||||
|
||||
```bash
|
||||
# Check what's using the port
|
||||
lsof -i :3001
|
||||
lsof -i :3050
|
||||
|
||||
# Kill the process if needed
|
||||
kill <PID>
|
||||
|
|
@ -134,23 +236,6 @@ pnpm docker:up
|
|||
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
|
||||
|
||||
### Step 1: Create Project Structure
|
||||
|
|
@ -160,7 +245,7 @@ Create the standard project structure under `apps/`:
|
|||
```
|
||||
apps/newproject/
|
||||
├── apps/
|
||||
│ ├── backend/ # NestJS API (if needed)
|
||||
│ ├── server/ # Hono/Bun server (if needed for compute)
|
||||
│ ├── mobile/ # Expo React Native app
|
||||
│ ├── web/ # SvelteKit web app
|
||||
│ └── landing/ # Astro marketing page
|
||||
|
|
@ -170,9 +255,24 @@ apps/newproject/
|
|||
└── 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`):
|
||||
```sql
|
||||
|
|
@ -181,84 +281,59 @@ If your backend uses Drizzle ORM:
|
|||
```
|
||||
|
||||
2. **Add to setup script** (`scripts/setup-databases.sh`):
|
||||
|
||||
In the `setup_service()` function, add a new case:
|
||||
```bash
|
||||
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`**:
|
||||
```env
|
||||
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`:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:mobile": "pnpm --filter @newproject/mobile dev",
|
||||
"dev:newproject:backend": "pnpm --filter @newproject/backend dev",
|
||||
"dev:newproject:landing": "pnpm --filter @newproject/landing dev",
|
||||
"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
|
||||
"dev:newproject:server": "cd apps/newproject/apps/server && bun run --watch src/index.ts",
|
||||
"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: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\"",
|
||||
"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:
|
||||
- Project overview
|
||||
- Structure diagram
|
||||
- Available commands
|
||||
- API endpoints (if backend)
|
||||
- Environment variables
|
||||
- Tech stack details
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev:newproject:web": "pnpm --filter @newproject/web dev",
|
||||
"dev:newproject:local": "concurrently -n sync,web -c magenta,cyan \"pnpm dev:sync\" \"pnpm dev:newproject:web\"",
|
||||
"dev:newproject:full": "concurrently -n auth,sync,web -c blue,magenta,cyan \"pnpm dev:auth\" \"pnpm dev:sync\" \"pnpm dev:newproject:web\""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
# Create database and push schema
|
||||
pnpm setup:db:newproject
|
||||
# Quick start (no auth needed)
|
||||
pnpm dev:newproject:local
|
||||
|
||||
# Start with full dev command
|
||||
# Full stack (with auth + DB setup)
|
||||
pnpm dev:newproject:full
|
||||
```
|
||||
|
||||
|
|
@ -266,10 +341,13 @@ pnpm dev:newproject:full
|
|||
|
||||
- [ ] Create project structure under `apps/newproject/`
|
||||
- [ ] Add `pnpm-workspace.yaml` in project root
|
||||
- [ ] Add database to `docker/init-db/01-create-databases.sql`
|
||||
- [ ] Add service to `scripts/setup-databases.sh`
|
||||
- [ ] Add DATABASE_URL to `.env.development`
|
||||
- [ ] Set up local-store with Dexie.js collections
|
||||
- [ ] Create guest seed data
|
||||
- [ ] 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
|
||||
- [ ] Add scripts to root `package.json`
|
||||
- [ ] Create `CLAUDE.md` with project documentation
|
||||
- [ ] Test with `pnpm dev:newproject:full`
|
||||
- [ ] Test with `pnpm dev:newproject:local`
|
||||
|
|
|
|||
|
|
@ -454,16 +454,15 @@ git pull
|
|||
|
||||
### 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 |
|
||||
|------------|-----------|---------------|
|
||||
| `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
|
||||
./scripts/mac-mini/build-app.sh --base
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| 3025-3029 | *(reserved)* | | |
|
||||
|
||||
## 3030-3059: App Backends
|
||||
## 3030-3059: App Compute Servers
|
||||
|
||||
Only apps that need server-side compute (AI, external APIs, file operations).
|
||||
Pure CRUD apps use mana-sync directly.
|
||||
|
||||
| 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 |
|
||||
| 3032 | calendar-backend | NestJS | CalDAV sync, Google Calendar, notifications |
|
||||
| 3033 | contacts-backend | NestJS | Google Contacts, vCard import/export |
|
||||
| 3034 | storage-backend | NestJS | S3 file ops, versioning, shares |
|
||||
| 3035 | picture-backend | NestJS | Replicate AI, generation orchestration |
|
||||
| 3036 | manadeck-backend | NestJS | AI card generation |
|
||||
| 3037 | mukke-backend | NestJS | Audio processing, BPM, ID3 tags |
|
||||
| 3038 | nutriphi-backend | NestJS | Gemini meal analysis |
|
||||
| 3039 | planta-backend | NestJS | Gemini plant analysis |
|
||||
| 3032 | calendar-server | Hono/Bun | CalDAV sync, Google Calendar, notifications |
|
||||
| 3033 | contacts-server | Hono/Bun | Google Contacts, vCard import/export |
|
||||
| 3034 | storage-server | Hono/Bun | S3 file ops, versioning, shares |
|
||||
| 3035 | picture-server | Hono/Bun | Replicate AI, generation orchestration |
|
||||
| 3036 | manadeck-server | Hono/Bun | AI card generation |
|
||||
| 3037 | mukke-server | Hono/Bun | Audio processing, BPM, ID3 tags |
|
||||
| 3038 | nutriphi-server | Hono/Bun | Gemini meal analysis |
|
||||
| 3039 | planta-server | Hono/Bun | Gemini plant analysis |
|
||||
| 3040 | presi-server | Hono/Bun | Share links |
|
||||
| 3041-3059 | *(reserved)* | | |
|
||||
|
||||
|
|
@ -78,28 +78,28 @@ Pure CRUD apps use mana-sync directly.
|
|||
|
||||
## 5000-5059: Web Frontends (SvelteKit)
|
||||
|
||||
| Port | Service | Corresponds to Backend |
|
||||
| Port | Service | Corresponds to Server |
|
||||
|------|---------|----------------------|
|
||||
| 5000 | mana-web | Hub/Dashboard |
|
||||
| 5010 | chat-web | 3030 chat-backend |
|
||||
| 5010 | chat-web | 3030 chat-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)* |
|
||||
| 5014 | contacts-web | 3033 contacts-backend |
|
||||
| 5015 | storage-web | 3034 storage-backend |
|
||||
| 5014 | contacts-web | 3033 contacts-server |
|
||||
| 5015 | storage-web | 3034 storage-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)* |
|
||||
| 5019 | photos-web | *(local-first + mana-media)* |
|
||||
| 5020 | skilltree-web | *(local-first only)* |
|
||||
| 5021 | picture-web | 3035 picture-backend |
|
||||
| 5021 | picture-web | 3035 picture-server |
|
||||
| 5022 | citycorners-web | *(local-first only)* |
|
||||
| 5023 | manadeck-web | 3036 manadeck-backend |
|
||||
| 5024 | mukke-web | 3037 mukke-backend |
|
||||
| 5023 | manadeck-web | 3036 manadeck-server |
|
||||
| 5024 | mukke-web | 3037 mukke-server |
|
||||
| 5025 | inventar-web | *(local-first only)* |
|
||||
| 5026 | context-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)* |
|
||||
| 5030-5049 | *(reserved)* | |
|
||||
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ nutriphi/
|
|||
│ ├── mobile/ # Expo React Native App (@nutriphi/mobile)
|
||||
│ ├── web/ # SvelteKit Web App (@nutriphi/web)
|
||||
│ └── landing/ # Astro Landing Page (@nutriphi/landing)
|
||||
├── backend/ # NestJS API Server (@nutriphi/backend)
|
||||
├── server/ # Hono/Bun server (@nutriphi/server)
|
||||
```
|
||||
|
||||
#### API Endpoints
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
PORT: (env) => env.MOODLIT_BACKEND_PORT || '3012',
|
||||
|
|
@ -524,37 +524,7 @@ const APP_CONFIGS = [
|
|||
},
|
||||
},
|
||||
|
||||
// Finance Backend (NestJS)
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
// Finance: REMOVED
|
||||
|
||||
// Worldream Web (SvelteKit)
|
||||
{
|
||||
|
|
@ -576,29 +546,11 @@ const APP_CONFIGS = [
|
|||
},
|
||||
},
|
||||
|
||||
// TechBase Backend (NestJS)
|
||||
{
|
||||
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: REMOVED
|
||||
|
||||
// TechBase Web (Astro)
|
||||
// Traces Server (Hono/Bun)
|
||||
{
|
||||
path: 'apps/techbase/apps/web/.env',
|
||||
vars: {
|
||||
PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.TECHBASE_BACKEND_PORT || '3021'}`,
|
||||
},
|
||||
},
|
||||
|
||||
// Traces Backend (NestJS)
|
||||
{
|
||||
path: 'apps/traces/apps/backend/.env',
|
||||
path: 'apps/traces/apps/server/.env',
|
||||
vars: {
|
||||
NODE_ENV: () => 'development',
|
||||
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: {
|
||||
NODE_ENV: () => 'development',
|
||||
PORT: (env) => env.MUKKE_BACKEND_PORT || '3010',
|
||||
|
|
|
|||
|
|
@ -111,11 +111,6 @@ build_base_images() {
|
|||
$DOCKER build -f "$PROJECT_ROOT/docker/Dockerfile.sveltekit-base" -t sveltekit-base:local "$PROJECT_ROOT"
|
||||
echo "sveltekit-base:local built."
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -66,13 +66,8 @@ ALL_DATABASES=(
|
|||
"manadeck"
|
||||
"storage"
|
||||
"presi"
|
||||
"mail"
|
||||
"moodlit"
|
||||
"finance"
|
||||
"inventory"
|
||||
"techbase"
|
||||
"voxel_lava"
|
||||
"figgos"
|
||||
"planta"
|
||||
"nutriphi"
|
||||
"photos"
|
||||
|
|
@ -108,7 +103,7 @@ setup_service() {
|
|||
;;
|
||||
chat)
|
||||
create_db_if_not_exists "chat"
|
||||
push_schema "@chat/backend" "chat"
|
||||
push_schema "@chat/server" "chat"
|
||||
;;
|
||||
zitare)
|
||||
create_db_if_not_exists "zitare"
|
||||
|
|
@ -116,11 +111,11 @@ setup_service() {
|
|||
;;
|
||||
contacts)
|
||||
create_db_if_not_exists "contacts"
|
||||
push_schema "@contacts/backend" "contacts"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
calendar)
|
||||
create_db_if_not_exists "calendar"
|
||||
push_schema "@calendar/backend" "calendar"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
clock)
|
||||
create_db_if_not_exists "clock"
|
||||
|
|
@ -128,47 +123,31 @@ setup_service() {
|
|||
;;
|
||||
todo)
|
||||
create_db_if_not_exists "todo"
|
||||
push_schema "@todo/backend" "todo"
|
||||
push_schema "@todo/server" "todo"
|
||||
;;
|
||||
manadeck)
|
||||
create_db_if_not_exists "manadeck"
|
||||
push_schema "@manadeck/backend" "manadeck"
|
||||
;;
|
||||
mail)
|
||||
create_db_if_not_exists "mail"
|
||||
push_schema "@mail/backend" "mail"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
moodlit)
|
||||
create_db_if_not_exists "moodlit"
|
||||
push_schema "@moodlit/backend" "moodlit"
|
||||
push_schema "@moodlit/server" "moodlit"
|
||||
;;
|
||||
picture)
|
||||
create_db_if_not_exists "picture"
|
||||
push_schema "@picture/backend" "picture"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
photos)
|
||||
create_db_if_not_exists "photos"
|
||||
# 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)
|
||||
create_db_if_not_exists "planta"
|
||||
push_schema "@planta/backend" "planta"
|
||||
push_schema "@planta/server" "planta"
|
||||
;;
|
||||
nutriphi)
|
||||
create_db_if_not_exists "nutriphi"
|
||||
push_schema "@nutriphi/backend" "nutriphi"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
presi)
|
||||
create_db_if_not_exists "presi"
|
||||
|
|
@ -176,7 +155,7 @@ setup_service() {
|
|||
;;
|
||||
storage)
|
||||
create_db_if_not_exists "storage"
|
||||
push_schema "@storage/backend" "storage"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
projectdoc)
|
||||
create_db_if_not_exists "projectdoc"
|
||||
|
|
@ -196,7 +175,7 @@ setup_service() {
|
|||
;;
|
||||
questions)
|
||||
create_db_if_not_exists "questions"
|
||||
push_schema "@questions/backend" "questions"
|
||||
# Schema managed by mana-sync (local-first)
|
||||
;;
|
||||
skilltree)
|
||||
create_db_if_not_exists "skilltree"
|
||||
|
|
@ -204,15 +183,15 @@ setup_service() {
|
|||
;;
|
||||
mukke)
|
||||
create_db_if_not_exists "mukke"
|
||||
push_schema "@mukke/backend" "mukke"
|
||||
push_schema "@mukke/server" "mukke"
|
||||
;;
|
||||
traces)
|
||||
create_db_if_not_exists "traces"
|
||||
push_schema "@traces/backend" "traces"
|
||||
push_schema "@traces/server" "traces"
|
||||
;;
|
||||
context)
|
||||
create_db_if_not_exists "context"
|
||||
push_schema "@context/backend" "context"
|
||||
push_schema "@context/server" "context"
|
||||
;;
|
||||
citycorners)
|
||||
create_db_if_not_exists "citycorners"
|
||||
|
|
@ -224,7 +203,7 @@ setup_service() {
|
|||
;;
|
||||
*)
|
||||
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
|
||||
;;
|
||||
esac
|
||||
|
|
@ -248,7 +227,7 @@ echo -e "\n${GREEN}Step 2: Pushing schemas${NC}"
|
|||
echo "--------------------------------------"
|
||||
|
||||
# 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
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -90,12 +90,6 @@ function getWorkspaceDeps(pkgJsonPath) {
|
|||
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)
|
||||
function isStandaloneBuild(dockerfilePath) {
|
||||
const content = readFileSync(dockerfilePath, 'utf8');
|
||||
|
|
@ -133,22 +127,6 @@ function getDockerfileCopyPaths(dockerfilePath) {
|
|||
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
|
||||
function extractImports(filePath) {
|
||||
if (!existsSync(filePath)) return [];
|
||||
|
|
@ -173,13 +151,7 @@ function extractImports(filePath) {
|
|||
|
||||
// Validate a single Dockerfile and return result
|
||||
function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, opts = {}) {
|
||||
const {
|
||||
isNestjsBase = false,
|
||||
nestjsBasePackagePaths = new Set(),
|
||||
checkImports = false,
|
||||
appDir = null,
|
||||
checkPatches = false,
|
||||
} = opts;
|
||||
const { checkImports = false, appDir = null, checkPatches = false } = opts;
|
||||
|
||||
if (!existsSync(pkgJsonPath)) {
|
||||
return {
|
||||
|
|
@ -203,14 +175,6 @@ function validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, op
|
|||
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
|
||||
const found = [...copyPaths].some(
|
||||
(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)
|
||||
if (checkPatches && !isNestjsBase && !hasPatchesCopy) {
|
||||
if (checkPatches && !hasPatchesCopy) {
|
||||
errors.push('MISSING: patches/ directory → add: COPY patches/ ./patches/');
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +245,6 @@ function main() {
|
|||
const servicesDir = join(ROOT, 'services');
|
||||
let hasErrors = false;
|
||||
const results = [];
|
||||
const nestjsBasePackagePaths = getNestjsBasePackages();
|
||||
|
||||
// Find all app directories
|
||||
const appDirs = readdirSync(appsDir, { withFileTypes: true })
|
||||
|
|
@ -317,12 +280,8 @@ function main() {
|
|||
|
||||
const pkgJsonPath = join(appsDir, appName, 'apps', 'backend', 'package.json');
|
||||
const relPath = `apps/${appName}/apps/backend/Dockerfile`;
|
||||
const isNestjsBase = usesNestjsBase(dockerfilePath);
|
||||
|
||||
const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap, {
|
||||
isNestjsBase,
|
||||
nestjsBasePackagePaths,
|
||||
});
|
||||
const result = validateDockerfile(dockerfilePath, pkgJsonPath, relPath, packageMap);
|
||||
if (result.errors.length > 0) hasErrors = true;
|
||||
results.push(result);
|
||||
printResult(result);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue