Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
531b385
add initial db setup
vtotalova Nov 25, 2025
6871c30
remove redundant fields in db
vtotalova Dec 2, 2025
5e76c47
Merge branch 'main' into feature/db-setup
vtotalova Dec 2, 2025
377fab2
fix CI and code quality issues
vtotalova Dec 2, 2025
f114fa8
remove manually migration, update db setup
vtotalova Dec 3, 2025
f8c2983
Merge branch 'main' into feature/db-setup
vtotalova Dec 3, 2025
2b86a16
check resolved
vtotalova Dec 3, 2025
515d35f
Merge branch 'feature/db-setup' of github.com:ls1intum/memo into feat…
vtotalova Dec 3, 2025
68bf56e
feat: enhance database schema and migration system
vtotalova Dec 3, 2025
5c538fe
style: fix prettier formatting
vtotalova Dec 3, 2025
0d2d8dd
docs: clarify Prisma Studio usage and DATABASE_URL
vtotalova Dec 9, 2025
1b6cb4f
docs: clarify database setup and .env file usage
vtotalova Dec 9, 2025
626561c
remove unnecessary code
vtotalova Dec 9, 2025
9b19b68
fix npm Ci issues
vtotalova Dec 9, 2025
1bffd0d
update doc
vtotalova Dec 9, 2025
c7fe36a
fix CI issued
vtotalova Dec 9, 2025
a7f4c7f
fix inconsistency between prisma clinet and db setup file
vtotalova Dec 10, 2025
abb04f0
Update prisma/migrations/20251203125815_init/migration.sql
vtotalova Dec 10, 2025
791f7f3
Update prisma/schema.prisma
vtotalova Dec 10, 2025
555c4b7
set up domain core
vtotalova Dec 10, 2025
b3f6bc6
Merge branch 'feature/db-setup' into feature/setup-domain-core-db-com…
vtotalova Dec 10, 2025
0fefd69
fix code formatting issues
vtotalova Dec 10, 2025
1e768ae
fix: export RelationshipType from domain core and update imports
vtotalova Dec 10, 2025
1df11b0
Merge branch 'main' into feature/setup-domain-core-db-communication
vtotalova Dec 10, 2025
ab4d9f2
move prisma build stage upper
vtotalova Dec 10, 2025
3e271ef
Merge branch 'feature/setup-domain-core-db-communication' of github.c…
vtotalova Dec 10, 2025
89eae3f
cosmetic minor changes
vtotalova Dec 15, 2025
c0da752
fix test code issues
vtotalova Dec 15, 2025
e5ad44e
restructure domain core + code issues fixed
vtotalova Dec 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions DOMAIN_CORE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# 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 (domain_core/services/)
↓
Repository Interface (domain_core/repositories/dc_interface.ts)
↓
Repository Implementation (domain_core/repositories/dc_repo.ts) ← ONLY layer that touches DB
↓
Prisma Client (domain_core/infrastructure/prisma.ts)
↓
PostgreSQL Database
```

## Folder Structure

```
domain_core/
β”œβ”€β”€ infrastructure/ # External service clients & utilities
β”‚ β”œβ”€β”€ prisma.ts # Prisma client singleton
β”‚ β”œβ”€β”€ run-migrations.ts # Database migration runner
β”‚ └── utils.ts # Utility functions (cn, etc.)
β”œβ”€β”€ model/ # Domain entities & input types
β”‚ └── domain_model.ts # Pure domain interfaces
β”œβ”€β”€ repositories/ # Data access layer
β”‚ β”œβ”€β”€ dc_interface.ts # Repository contracts
β”‚ └── dc_repo.ts # Prisma implementations
└── services/ # Business logic layer
β”œβ”€β”€ user.ts
β”œβ”€β”€ competency.ts
β”œβ”€β”€ learning_resource.ts
β”œβ”€β”€ competency_relationship.ts
└── competency_resource_link.ts
```

## How It Works

### 1. Shared Domain Model (`domain_core/model/domain_model.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 (`domain_core/repositories/dc_interface.ts`)

Contract definition. No implementation.

```typescript
export interface CompetencyRepository {
create(data: CreateCompetencyInput): Promise<Competency>;
findById(id: string): Promise<Competency | null>;
findAll(): Promise<Competency[]>;
}
```

### 3. Repository Implementation (`domain_core/repositories/dc_repo.ts`)

Prisma implementation. **ONLY this layer touches the database.**

```typescript
export class PrismaCompetencyRepository implements CompetencyRepository {
async create(data: CreateCompetencyInput): Promise<Competency> {
return await prisma.competency.create({ data });
}
}

export const competencyRepository = new PrismaCompetencyRepository();
```

### 4. Service Layer (`domain_core/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 '@/domain_core/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 <div>Error loading competencies</div>;

return (
<div>
{competencies?.map(comp => (
<div key={comp.id}>
<h2>{comp.title}</h2>
<p>{comp.description}</p>
</div>
))}
</div>
);
}
```

### 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 (
<form action={handleSubmit}>
<input name="title" placeholder="Title" required />
<textarea name="description" placeholder="Description" />
<button type="submit">Create</button>
{message && <p>{message}</p>}
</form>
);
}
```

## Connecting to an API

To expose data through an API endpoint:

```typescript
// app/api/competencies/route.ts
import { competencyService } from '@/domain_core/services/competency';
import { NextResponse } from 'next/server';

export async function GET() {
try {
const competencies = await competencyService.getAllCompetencies();
return NextResponse.json({ success: true, competencies });
} catch (error) {
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}

export async function POST(request: Request) {
try {
const body = await request.json();
const competency = await competencyService.createCompetency(body);
return NextResponse.json({ success: true, competency });
} catch (error) {
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}
```

## Rules

### βœ… DO

- Use Server Actions from Client Side
- Add business logic in Service layer
- Use repository interfaces in services
- Call services from API routes for external APIs

### ❌ DON'T

- Never import `prisma` in Client Side/services/API routes
- Never skip layers (call repository directly from actions/routes)
- Never put business logic in Server Actions or API routes
- Never put database queries outside repositories

## Existing Entities

Full CRUD implementations available:

- **User** - [actions/users.ts](app/actions/users.ts)
- **Competency** - [actions/competencies.ts](app/actions/competencies.ts)
- **LearningResource** - [actions/learning_resources.ts](app/actions/learning_resources.ts)
- **CompetencyRelationship** -
[actions/competency_relationships.ts](app/actions/competency_relationships.ts)
- **CompetencyResourceLink** -
[actions/competency_resource_links.ts](app/actions/competency_resource_links.ts)
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ FROM base AS builder
COPY package.json package-lock.json ./
RUN npm install --legacy-peer-deps
COPY . .
RUN npx prisma generate
RUN npm run build

# Production stage
Expand Down
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,26 @@ The project includes a convenient management script for all Docker operations:
```
memo/
β”œβ”€β”€ app/ # Next.js app directory
β”‚ β”œβ”€β”€ actions/ # Server Actions (API layer)
β”‚ β”œβ”€β”€ dashboard/ # Dashboard pages
β”‚ β”œβ”€β”€ session/ # Session management pages
β”‚ └── onboarding/ # Onboarding flow
β”œβ”€β”€ components/ # React components
β”œβ”€β”€ lib/ # Utility libraries
β”œβ”€β”€ docker/ # Environment-specific configs
β”œβ”€β”€ domain_core/ # Domain-Driven Design layers
β”‚ β”œβ”€β”€ infrastructure/ # External clients (Prisma, utilities)
β”‚ β”œβ”€β”€ model/ # Domain entities & types
β”‚ β”œβ”€β”€ repositories/ # Data access layer
β”‚ └── services/ # Business logic layer
β”œβ”€β”€ prisma/ # Database schema & migrations
β”œβ”€β”€ docker/ # Environment-specific configs
β”‚ β”œβ”€β”€ development/ # Local development
β”‚ β”œβ”€β”€ staging/ # Testing environment
β”‚ └── production/ # Production environment
β”œβ”€β”€ scripts/ # Database initialization
β”œβ”€β”€ scripts/ # Database initialization
β”œβ”€β”€ .github/workflows/ # CI/CD workflows
β”œβ”€β”€ docker-manage.sh # Docker management script
β”œβ”€β”€ Dockerfile # Multi-stage Docker build
└── README.md # This file
β”œβ”€β”€ docker-manage.sh # Docker management script
β”œβ”€β”€ Dockerfile # Multi-stage Docker build
└── README.md # This file
```

## πŸ”§ Development Configuration
Expand Down
85 changes: 85 additions & 0 deletions app/actions/competencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use server';

import { competencyService } from '@/domain_core/services/competency';

export async function createCompetencyAction(formData: FormData) {
try {
const title = formData.get('title');
const description = formData.get('description') as string | undefined;

if (!title || typeof title !== 'string' || title.trim() === '') {
return { success: false, error: 'Title is required' };
}

const competency = await competencyService.createCompetency({
title,
description,
});

return { success: true, competency };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Failed to create competency',
};
}
}

export async function getCompetencyAction(id: string) {
try {
const competency = await competencyService.getCompetencyById(id);
return { success: true, competency };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Competency not found',
};
}
}

export async function getAllCompetenciesAction() {
try {
const competencies = await competencyService.getAllCompetencies();
return { success: true, competencies };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Failed to fetch competencies',
};
}
}

export async function updateCompetencyAction(
id: string,
title?: string,
description?: string
) {
try {
const competency = await competencyService.updateCompetency(id, {
title,
description,
});
return { success: true, competency };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Failed to update competency',
};
}
}

export async function deleteCompetencyAction(id: string) {
try {
await competencyService.deleteCompetency(id);
return { success: true };
} catch (error) {
return {
success: false,
error:
error instanceof Error ? error.message : 'Failed to delete competency',
};
}
}
Loading