diff --git a/DATABASE.md b/DATABASE.md index eb1231a..3cba45d 100644 --- a/DATABASE.md +++ b/DATABASE.md @@ -60,6 +60,7 @@ npm run db:migrate | `db:studio` | Open Prisma Studio GUI | | `db:reset` | Reset database (⚠️ destroys data) | | `db:generate` | Regenerate Prisma Client | +| `db:seed` | Seed demo user + competencies | ## Common Tasks @@ -88,6 +89,15 @@ npx prisma migrate status npm run db:reset ``` +**Seed demo content (user + 5 competencies):** + +```bash +npm run db:seed +``` + +Creates a demo user (`demo@memo.local`) and 5 sample competencies. Idempotent, safe to run multiple +times. + ## Schema See [prisma/schema.prisma](prisma/schema.prisma) for the complete schema. diff --git a/DOMAIN_CORE.md b/DOMAIN_CORE.md new file mode 100644 index 0000000..a172607 --- /dev/null +++ b/DOMAIN_CORE.md @@ -0,0 +1,213 @@ +# Domain Core Architecture + +This project uses a layered architecture. The "domain core" includes the service and repository +layers, and **only the repository implementation layer communicates with the database**. + +## Architecture Flow + +``` +Client Side + ↓ +Server Actions (app/actions/) + ↓ +Service Layer (lib/services/) + ↓ +Repository Interface (lib/repositories/dc_interface.ts) + ↓ +Repository Implementation (lib/repositories/dc_repo.ts) ← ONLY layer that touches DB + ↓ +Prisma Client (lib/prisma.ts) + ↓ +PostgreSQL Database +``` + +## How It Works + +### 1. Shared Domain Model (`lib/domain/domain_core.ts`) + +Pure entities and input types. No database dependencies. + +```typescript +export interface Competency { + id: string; + title: string; + description: string | null; + createdAt: Date; +} + +export interface CreateCompetencyInput { + title: string; + description?: string; +} +``` + +### 2. Repository Interface (`lib/repositories/dc_interface.ts`) + +Contract definition. No implementation. + +```typescript +export interface CompetencyRepository { + create(data: CreateCompetencyInput): Promise; + findById(id: string): Promise; + findAll(): Promise; +} +``` + +### 3. Repository Implementation (`lib/repositories/dc_repo.ts`) + +Prisma implementation. **ONLY this layer touches the database.** + +```typescript +export class PrismaCompetencyRepository implements CompetencyRepository { + async create(data: CreateCompetencyInput): Promise { + return await prisma.competency.create({ data }); + } +} + +export const competencyRepository = new PrismaCompetencyRepository(); +``` + +### 4. Service Layer (`lib/services/`) + +Business logic and validation. + +```typescript +export class CompetencyService { + constructor(private readonly repository: CompetencyRepository) {} + + async createCompetency(data: CreateCompetencyInput) { + return await this.repository.create(data); + } +} + +export const competencyService = new CompetencyService(competencyRepository); +``` + +### 5. Server Actions (`app/actions/`) + +Exposes operations to Client Side. + +```typescript +'use server'; + +import { competencyService } from '@/lib/services/competency'; + +export async function createCompetencyAction(formData: FormData) { + try { + const title = formData.get('title') as string; + const competency = await competencyService.createCompetency({ title }); + return { success: true, competency }; + } catch (error) { + return { success: false, error: error.message }; + } +} +``` + +## Usage from Client Side + +### Server Components (Default) + +```tsx +import { getAllCompetenciesAction } from '@/app/actions/competencies'; + +export default async function CompetenciesPage() { + const { success, competencies } = await getAllCompetenciesAction(); + + if (!success) return
Error loading competencies
; + + return ( +
+ {competencies?.map(comp => ( +
+

{comp.title}

+

{comp.description}

+
+ ))} +
+ ); +} +``` + +### Client Components + +```tsx +'use client'; + +import { createCompetencyAction } from '@/app/actions/competencies'; +import { useState } from 'react'; + +export default function CreateCompetencyForm() { + const [message, setMessage] = useState(''); + + async function handleSubmit(formData: FormData) { + const result = await createCompetencyAction(formData); + setMessage(result.success ? 'Created!' : result.error); + } + + return ( +
+ +