feat: Setup projet Classeo avec infrastructure Docker et architecture DDD
Configure l'environnement de développement complet avec Docker Compose, structure DDD 4 Bounded Contexts, et pipeline CI/CD GitHub Actions. Corrections compatibilité CI: - Symfony 8 nécessite monolog-bundle ^4.0 (la v3.x ne supporte que jusqu'à Symfony 7) - ESLint v9 nécessite flat config (eslint.config.js) - le format .eslintrc.cjs est obsolète
This commit is contained in:
186
.github/workflows/ci.yml
vendored
Normal file
186
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# =============================================================================
|
||||||
|
# Backend Tests - PHP 8.5, PHPStan, PHPUnit
|
||||||
|
# =============================================================================
|
||||||
|
test-backend:
|
||||||
|
name: Backend Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: backend
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:18.1-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: classeo_test
|
||||||
|
POSTGRES_USER: classeo
|
||||||
|
POSTGRES_PASSWORD: classeo
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.4-alpine
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.5'
|
||||||
|
extensions: intl, pdo_pgsql, amqp, redis, zip
|
||||||
|
coverage: xdebug
|
||||||
|
|
||||||
|
- name: Get Composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache Composer dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: ${{ runner.os }}-composer-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
|
- name: Run PHP CS Fixer (check)
|
||||||
|
run: composer cs-check
|
||||||
|
|
||||||
|
- name: Run PHPStan
|
||||||
|
run: composer phpstan
|
||||||
|
|
||||||
|
- name: Run PHPUnit
|
||||||
|
run: composer test
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://classeo:classeo@localhost:5432/classeo_test?serverVersion=18
|
||||||
|
REDIS_URL: redis://localhost:6379
|
||||||
|
|
||||||
|
- name: Run BC Isolation Check
|
||||||
|
working-directory: .
|
||||||
|
run: ./scripts/check-bc-isolation.sh
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Frontend Tests - Vitest, Playwright
|
||||||
|
# =============================================================================
|
||||||
|
test-frontend:
|
||||||
|
name: Frontend Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: frontend
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache pnpm dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: ${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Run TypeScript check
|
||||||
|
run: pnpm run check
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: pnpm run test
|
||||||
|
|
||||||
|
- name: Install Playwright browsers
|
||||||
|
run: pnpm exec playwright install --with-deps
|
||||||
|
|
||||||
|
- name: Run E2E tests
|
||||||
|
run: pnpm run test:e2e
|
||||||
|
|
||||||
|
- name: Upload Playwright report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: frontend/playwright-report/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Naming Conventions Check
|
||||||
|
# =============================================================================
|
||||||
|
check-naming:
|
||||||
|
name: Naming Conventions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run Naming Check
|
||||||
|
run: ./scripts/check-naming.sh
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Build Check
|
||||||
|
# =============================================================================
|
||||||
|
build:
|
||||||
|
name: Build Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test-backend, test-frontend]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build backend image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./backend
|
||||||
|
push: false
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build frontend image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./frontend
|
||||||
|
push: false
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Environment files
|
||||||
|
# =============================================================================
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
*.env.local
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Logs
|
||||||
|
# =============================================================================
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
145
CONTRIBUTING.md
Normal file
145
CONTRIBUTING.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Guide de Contribution - Classeo
|
||||||
|
|
||||||
|
## Pre-requis
|
||||||
|
|
||||||
|
- Docker Desktop 24+
|
||||||
|
- Git
|
||||||
|
|
||||||
|
## Setup Developpeur
|
||||||
|
|
||||||
|
### 1. Cloner et lancer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/ClasseoEdu/classeo.git
|
||||||
|
cd classeo
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verifier le setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tous les services doivent etre "healthy"
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# Backend repond (port 18000 pour eviter conflit avec services locaux)
|
||||||
|
curl http://localhost:18000/api
|
||||||
|
|
||||||
|
# Frontend repond (port 5174 pour eviter conflit avec services locaux)
|
||||||
|
curl http://localhost:5174
|
||||||
|
```
|
||||||
|
|
||||||
|
## Regles de Code
|
||||||
|
|
||||||
|
> **Important** : Lire le fichier `CLAUDE.md` a la racine du projet pour les conventions
|
||||||
|
> specifiques (style d'imports PHP, format des messages de commit, etc.).
|
||||||
|
|
||||||
|
### PHP Backend
|
||||||
|
|
||||||
|
1. **`declare(strict_types=1);`** sur la premiere ligne de chaque fichier
|
||||||
|
2. **PHPStan level 9** - Zero erreur toleree
|
||||||
|
3. **Domain = PHP pur** - Aucune dependance Symfony/Doctrine dans `src/*/Domain/`
|
||||||
|
4. **Value Objects immutables** - `final readonly class`
|
||||||
|
5. **No null returns** - Utiliser exceptions ou Null Object
|
||||||
|
|
||||||
|
### TypeScript Frontend
|
||||||
|
|
||||||
|
1. **Strict mode** active
|
||||||
|
2. **Svelte 5 Runes uniquement** - `$state`, `$derived`, `$effect`
|
||||||
|
3. **Jamais** `writable()`, `on:click`, `export let` (Svelte 4)
|
||||||
|
4. **Composants PascalCase** - `MyComponent.svelte`
|
||||||
|
|
||||||
|
### Conventions Nommage
|
||||||
|
|
||||||
|
| Element | Convention | Exemple |
|
||||||
|
|---------|-----------|---------|
|
||||||
|
| Classes PHP | PascalCase | `NoteRepository` |
|
||||||
|
| Methodes | camelCase | `findByStudent()` |
|
||||||
|
| Events | FR nom + EN verbe passe | `NoteRecorded` |
|
||||||
|
| Value Objects | `final readonly class` | `NoteId` |
|
||||||
|
| Composants Svelte | PascalCase.svelte | `GradeCard.svelte` |
|
||||||
|
|
||||||
|
## Workflow Git
|
||||||
|
|
||||||
|
### Branches
|
||||||
|
|
||||||
|
- `main` - Production
|
||||||
|
- `develop` - Integration
|
||||||
|
- `feature/XXX` - Nouvelles fonctionnalites
|
||||||
|
- `fix/XXX` - Corrections de bugs
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
Format : `type(scope): description`
|
||||||
|
|
||||||
|
Types :
|
||||||
|
- `feat` - Nouvelle fonctionnalite
|
||||||
|
- `fix` - Correction de bug
|
||||||
|
- `refactor` - Refactoring
|
||||||
|
- `docs` - Documentation
|
||||||
|
- `test` - Ajout de tests
|
||||||
|
- `chore` - Maintenance
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
```
|
||||||
|
feat(auth): add JWT authentication
|
||||||
|
fix(notes): correct average calculation
|
||||||
|
refactor(admin): extract user service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### Avant de commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend
|
||||||
|
docker compose exec php composer phpstan
|
||||||
|
docker compose exec php composer test
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
docker compose exec frontend pnpm run lint
|
||||||
|
docker compose exec frontend pnpm run check
|
||||||
|
docker compose exec frontend pnpm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD
|
||||||
|
|
||||||
|
GitHub Actions execute automatiquement :
|
||||||
|
- PHPStan level 9
|
||||||
|
- PHPUnit tests
|
||||||
|
- ESLint
|
||||||
|
- TypeScript check
|
||||||
|
- Vitest
|
||||||
|
- Playwright E2E
|
||||||
|
- BC isolation check
|
||||||
|
- Naming conventions check
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Bounded Contexts
|
||||||
|
|
||||||
|
Ne pas creer de dependances directes entre BC. Utiliser :
|
||||||
|
- **Contracts** pour les interfaces partagees
|
||||||
|
- **Domain Events** pour la communication async
|
||||||
|
|
||||||
|
### Domain Layer
|
||||||
|
|
||||||
|
```php
|
||||||
|
// ✅ CORRECT - Pure PHP
|
||||||
|
namespace App\Scolarite\Domain\Model;
|
||||||
|
|
||||||
|
final readonly class NoteId extends EntityId {}
|
||||||
|
|
||||||
|
// ❌ INCORRECT - Framework dependency
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
class Note {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Infrastructure Layer
|
||||||
|
|
||||||
|
Les mappings Doctrine vont dans `Infrastructure/Persistence/Mapping/`.
|
||||||
|
|
||||||
|
## Questions ?
|
||||||
|
|
||||||
|
Ouvrir une issue sur GitHub ou contacter l'equipe sur le canal #classeo-dev.
|
||||||
99
Makefile
Normal file
99
Makefile
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
.PHONY: help up down build logs ps test lint phpstan cs-fix frontend-lint frontend-test e2e clean
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "Classeo - Commandes disponibles"
|
||||||
|
@echo ""
|
||||||
|
@echo "Docker:"
|
||||||
|
@echo " make up - Lancer tous les services"
|
||||||
|
@echo " make down - Arreter tous les services"
|
||||||
|
@echo " make build - Reconstruire les images"
|
||||||
|
@echo " make logs - Voir les logs (Ctrl+C pour quitter)"
|
||||||
|
@echo " make ps - Statut des services"
|
||||||
|
@echo " make clean - Supprimer volumes et images"
|
||||||
|
@echo ""
|
||||||
|
@echo "Backend:"
|
||||||
|
@echo " make phpstan - Analyse statique PHPStan"
|
||||||
|
@echo " make cs-fix - Correction code style PHP"
|
||||||
|
@echo " make test-php - Tests PHPUnit"
|
||||||
|
@echo ""
|
||||||
|
@echo "Frontend:"
|
||||||
|
@echo " make lint - ESLint frontend"
|
||||||
|
@echo " make test-js - Tests Vitest"
|
||||||
|
@echo " make e2e - Tests Playwright"
|
||||||
|
@echo ""
|
||||||
|
@echo "All:"
|
||||||
|
@echo " make test - Tous les tests"
|
||||||
|
@echo " make check - Tous les linters"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Docker
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
up:
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
down:
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
build:
|
||||||
|
docker compose build --no-cache
|
||||||
|
|
||||||
|
logs:
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
ps:
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
clean:
|
||||||
|
docker compose down -v --rmi local
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Backend
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
phpstan:
|
||||||
|
docker compose exec php composer phpstan
|
||||||
|
|
||||||
|
cs-fix:
|
||||||
|
docker compose exec php composer cs-fix
|
||||||
|
|
||||||
|
cs-check:
|
||||||
|
docker compose exec php composer cs-check
|
||||||
|
|
||||||
|
test-php:
|
||||||
|
docker compose exec php composer test
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Frontend
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
lint:
|
||||||
|
docker compose exec frontend pnpm run lint
|
||||||
|
|
||||||
|
check-types:
|
||||||
|
docker compose exec frontend pnpm run check
|
||||||
|
|
||||||
|
test-js:
|
||||||
|
docker compose exec frontend pnpm run test
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
docker compose exec frontend pnpm run test:e2e
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# All
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
test: test-php test-js
|
||||||
|
|
||||||
|
check: phpstan cs-check lint check-types
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Scripts
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
check-bc:
|
||||||
|
./scripts/check-bc-isolation.sh
|
||||||
|
|
||||||
|
check-naming:
|
||||||
|
./scripts/check-naming.sh
|
||||||
140
README.md
Normal file
140
README.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# Classeo
|
||||||
|
|
||||||
|
Application de gestion scolaire moderne - Backend Symfony 8 + Frontend SvelteKit 2.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequis
|
||||||
|
|
||||||
|
- Docker Desktop 24+ avec Docker Compose 2.20+
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Lancement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cloner le repo
|
||||||
|
git clone https://github.com/ClasseoEdu/classeo.git
|
||||||
|
cd classeo
|
||||||
|
|
||||||
|
# Lancer tous les services
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Verifier le statut
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### URLs
|
||||||
|
|
||||||
|
| Service | URL | Description |
|
||||||
|
|---------|-----|-------------|
|
||||||
|
| Frontend | http://localhost:5174 | Application SvelteKit |
|
||||||
|
| Backend API | http://localhost:18000/api | API REST (API Platform) |
|
||||||
|
| API Docs | http://localhost:18000/api/docs | Documentation OpenAPI |
|
||||||
|
| RabbitMQ | http://localhost:15672 | Admin (guest/guest) |
|
||||||
|
| Meilisearch | http://localhost:7700 | Dashboard recherche |
|
||||||
|
| Mailpit | http://localhost:8025 | Emails de test |
|
||||||
|
| Mercure | http://localhost:3000/.well-known/mercure | SSE Hub |
|
||||||
|
|
||||||
|
## Stack Technique
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- **PHP 8.5** avec property hooks et asymmetric visibility
|
||||||
|
- **Symfony 8.0** - Framework DDD-friendly
|
||||||
|
- **API Platform 4.x** - API REST auto-generee
|
||||||
|
- **Doctrine ORM 3.x** - Persistence avec mappings separes
|
||||||
|
- **PHPStan level 9** - Analyse statique stricte
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- **SvelteKit 2.x** - SSR, routing, PWA
|
||||||
|
- **Svelte 5** - Runes (`$state`, `$derived`, `$effect`)
|
||||||
|
- **TypeScript strict** - Typage fort
|
||||||
|
- **TanStack Query 5** - Server state management
|
||||||
|
- **Tailwind CSS 3** - Utility-first CSS
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
- **PostgreSQL 18.1** - Base de donnees
|
||||||
|
- **Redis 7.4** - Cache + Sessions
|
||||||
|
- **RabbitMQ 4.2** - Message queue
|
||||||
|
- **Mercure** - Real-time SSE
|
||||||
|
- **Meilisearch 1.12** - Full-text search
|
||||||
|
- **Mailpit** - Email testing
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Bounded Contexts
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/src/
|
||||||
|
├── Administration/ # Gestion etablissement, utilisateurs
|
||||||
|
├── Scolarite/ # Notes, classes, emploi du temps
|
||||||
|
├── VieScolaire/ # Absences, retards, sanctions
|
||||||
|
├── Communication/ # Messages, notifications
|
||||||
|
└── Shared/ # Kernel partage (EntityId, DomainEvent, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structure DDD
|
||||||
|
|
||||||
|
Chaque Bounded Context suit la meme structure :
|
||||||
|
|
||||||
|
```
|
||||||
|
{BC}/
|
||||||
|
├── Domain/ # Pure PHP - ZERO dependance framework
|
||||||
|
│ ├── Model/ # Aggregates, Entities, Value Objects
|
||||||
|
│ ├── Event/ # Domain Events
|
||||||
|
│ ├── Repository/ # Interfaces repository
|
||||||
|
│ └── Service/ # Domain Services
|
||||||
|
├── Application/ # Use cases
|
||||||
|
│ ├── Command/ # Write operations
|
||||||
|
│ ├── Query/ # Read operations
|
||||||
|
│ └── EventHandler/ # Domain event handlers
|
||||||
|
└── Infrastructure/ # Implementations framework
|
||||||
|
├── Persistence/ # Doctrine repositories
|
||||||
|
├── Api/ # API Platform resources
|
||||||
|
└── Messaging/ # RabbitMQ handlers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developpement
|
||||||
|
|
||||||
|
### Commandes utiles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend
|
||||||
|
docker compose exec php composer phpstan # Analyse statique
|
||||||
|
docker compose exec php composer test # Tests PHPUnit
|
||||||
|
docker compose exec php composer cs-fix # Correction code style
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
docker compose exec frontend pnpm run lint # ESLint
|
||||||
|
docker compose exec frontend pnpm run check # TypeScript check
|
||||||
|
docker compose exec frontend pnpm run test # Vitest
|
||||||
|
docker compose exec frontend pnpm run test:e2e # Playwright
|
||||||
|
```
|
||||||
|
|
||||||
|
### Makefile (raccourcis)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make up # docker compose up -d
|
||||||
|
make down # docker compose down
|
||||||
|
make logs # docker compose logs -f
|
||||||
|
make test # Run all tests
|
||||||
|
make lint # Run all linters
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- **PHPUnit** - Tests unitaires et integration backend
|
||||||
|
- **Vitest** - Tests unitaires frontend
|
||||||
|
- **Playwright** - Tests E2E
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Architecture Decision Records](./docs/adr/)
|
||||||
|
- [Contributing Guide](./CONTRIBUTING.md)
|
||||||
|
- [API Documentation](http://localhost:8000/api/docs)
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
Proprietary - ClasseoEdu
|
||||||
54
backend/.env
Normal file
54
backend/.env
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# In all environments, the following files are loaded if they exist,
|
||||||
|
# the latter taking precedence over the former:
|
||||||
|
#
|
||||||
|
# * .env contains default values for the environment variables needed by the app
|
||||||
|
# * .env.local uncommitted file with local overrides
|
||||||
|
# * .env.$APP_ENV committed environment-specific defaults
|
||||||
|
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||||
|
#
|
||||||
|
# Real environment variables win over .env files.
|
||||||
|
#
|
||||||
|
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||||
|
# https://symfony.com/doc/current/configuration/secrets.html
|
||||||
|
#
|
||||||
|
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_ENV=dev
|
||||||
|
APP_SECRET=change_me_in_production_12345678
|
||||||
|
TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||||
|
TRUSTED_HOSTS='^(localhost|php|127\.0\.0\.1)$'
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> doctrine/doctrine-bundle ###
|
||||||
|
DATABASE_URL="postgresql://classeo:classeo@db:5432/classeo_master?serverVersion=18&charset=utf8"
|
||||||
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
###> symfony/messenger ###
|
||||||
|
MESSENGER_TRANSPORT_DSN=amqp://guest:guest@rabbitmq:5672/%2f/messages
|
||||||
|
###< symfony/messenger ###
|
||||||
|
|
||||||
|
###> lexik/jwt-authentication-bundle ###
|
||||||
|
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
||||||
|
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
||||||
|
JWT_PASSPHRASE=classeo_jwt_passphrase_change_me
|
||||||
|
###< lexik/jwt-authentication-bundle ###
|
||||||
|
|
||||||
|
###> redis ###
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
###< redis ###
|
||||||
|
|
||||||
|
###> mercure ###
|
||||||
|
MERCURE_URL=http://mercure/.well-known/mercure
|
||||||
|
MERCURE_PUBLIC_URL=http://localhost:3000/.well-known/mercure
|
||||||
|
MERCURE_JWT_SECRET=mercure_publisher_secret_change_me_in_production
|
||||||
|
###< mercure ###
|
||||||
|
|
||||||
|
###> meilisearch ###
|
||||||
|
MEILISEARCH_URL=http://meilisearch:7700
|
||||||
|
MEILISEARCH_API_KEY=masterKey
|
||||||
|
###< meilisearch ###
|
||||||
|
|
||||||
|
###> symfony/mailer ###
|
||||||
|
MAILER_DSN=smtp://mailpit:1025
|
||||||
|
###< symfony/mailer ###
|
||||||
72
backend/.gitignore
vendored
Normal file
72
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Symfony
|
||||||
|
# =============================================================================
|
||||||
|
/var/
|
||||||
|
/vendor/
|
||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
|
||||||
|
# Fichiers auto-générés par Symfony
|
||||||
|
/config/bundles.php
|
||||||
|
/config/preload.php
|
||||||
|
/config/reference.php
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Doctrine Fixtures (auto-généré)
|
||||||
|
# =============================================================================
|
||||||
|
/src/DataFixtures/
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PHPUnit
|
||||||
|
# =============================================================================
|
||||||
|
/phpunit.xml
|
||||||
|
.phpunit.cache/
|
||||||
|
.phpunit.result.cache
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PHPStan
|
||||||
|
# =============================================================================
|
||||||
|
phpstan.neon.dist
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PHP CS Fixer
|
||||||
|
# =============================================================================
|
||||||
|
.php-cs-fixer.cache
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# JWT Keys
|
||||||
|
# =============================================================================
|
||||||
|
/config/jwt/*.pem
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Composer
|
||||||
|
# =============================================================================
|
||||||
|
composer.phar
|
||||||
|
composer.lock
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
/var/
|
||||||
|
/vendor/
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> friendsofphp/php-cs-fixer ###
|
||||||
|
/.php-cs-fixer.php
|
||||||
|
/.php-cs-fixer.cache
|
||||||
|
###< friendsofphp/php-cs-fixer ###
|
||||||
|
|
||||||
|
###> lexik/jwt-authentication-bundle ###
|
||||||
|
/config/jwt/*.pem
|
||||||
|
###< lexik/jwt-authentication-bundle ###
|
||||||
|
|
||||||
|
###> phpunit/phpunit ###
|
||||||
|
/phpunit.xml
|
||||||
|
/.phpunit.cache/
|
||||||
|
###< phpunit/phpunit ###
|
||||||
60
backend/.php-cs-fixer.php
Normal file
60
backend/.php-cs-fixer.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$finder = (new PhpCsFixer\Finder())
|
||||||
|
->in(__DIR__)
|
||||||
|
->exclude('var')
|
||||||
|
->exclude('vendor')
|
||||||
|
// Fichiers auto-générés par Symfony/Doctrine
|
||||||
|
->notPath('config/bundles.php')
|
||||||
|
->notPath('config/preload.php')
|
||||||
|
->notPath('config/reference.php')
|
||||||
|
->notPath('src/DataFixtures/AppFixtures.php')
|
||||||
|
// Exclusions spécifiques
|
||||||
|
->notPath('src/Shared/Domain/AggregateRoot.php')
|
||||||
|
->notPath('src/Shared/Domain/EntityId.php')
|
||||||
|
;
|
||||||
|
|
||||||
|
return (new PhpCsFixer\Config())
|
||||||
|
->setRules([
|
||||||
|
'@Symfony' => true,
|
||||||
|
'@Symfony:risky' => true,
|
||||||
|
'declare_strict_types' => true,
|
||||||
|
'strict_param' => true,
|
||||||
|
'array_syntax' => ['syntax' => 'short'],
|
||||||
|
'ordered_imports' => ['sort_algorithm' => 'alpha'],
|
||||||
|
'no_unused_imports' => true,
|
||||||
|
'not_operator_with_successor_space' => true,
|
||||||
|
'trailing_comma_in_multiline' => true,
|
||||||
|
'phpdoc_order' => true,
|
||||||
|
'phpdoc_separation' => true,
|
||||||
|
'phpdoc_no_empty_return' => true,
|
||||||
|
'native_function_invocation' => [
|
||||||
|
'include' => ['@compiler_optimized'],
|
||||||
|
'scope' => 'namespaced',
|
||||||
|
'strict' => true,
|
||||||
|
],
|
||||||
|
'native_constant_invocation' => true,
|
||||||
|
'global_namespace_import' => [
|
||||||
|
'import_classes' => true,
|
||||||
|
'import_constants' => true,
|
||||||
|
'import_functions' => true,
|
||||||
|
],
|
||||||
|
'final_class' => true,
|
||||||
|
'class_definition' => [
|
||||||
|
'single_line' => true,
|
||||||
|
],
|
||||||
|
'concat_space' => [
|
||||||
|
'spacing' => 'one',
|
||||||
|
],
|
||||||
|
'single_line_throw' => false,
|
||||||
|
// NO Yoda conditions
|
||||||
|
'yoda_style' => false,
|
||||||
|
'blank_line_before_statement' => [
|
||||||
|
'statements' => ['return', 'throw', 'try'],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->setRiskyAllowed(true)
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
||||||
107
backend/Dockerfile
Normal file
107
backend/Dockerfile
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PHP 8.5 + FrankenPHP - Backend Classeo
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
FROM dunglas/frankenphp:1-php8.5-alpine AS base
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
acl \
|
||||||
|
fcgi \
|
||||||
|
file \
|
||||||
|
gettext \
|
||||||
|
git \
|
||||||
|
icu-dev \
|
||||||
|
libzip-dev \
|
||||||
|
postgresql-dev \
|
||||||
|
rabbitmq-c-dev \
|
||||||
|
linux-headers \
|
||||||
|
$PHPIZE_DEPS
|
||||||
|
|
||||||
|
# Install PHP extensions (opcache is pre-installed in FrankenPHP)
|
||||||
|
RUN docker-php-ext-install intl pdo_pgsql zip sockets
|
||||||
|
|
||||||
|
# Install AMQP extension for RabbitMQ
|
||||||
|
RUN pecl install amqp && docker-php-ext-enable amqp
|
||||||
|
|
||||||
|
# Install Redis extension
|
||||||
|
RUN pecl install redis && docker-php-ext-enable redis
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Configure PHP for production
|
||||||
|
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||||
|
|
||||||
|
# Custom PHP configuration
|
||||||
|
RUN echo "opcache.enable=1" >> "$PHP_INI_DIR/conf.d/opcache.ini" \
|
||||||
|
&& echo "opcache.memory_consumption=256" >> "$PHP_INI_DIR/conf.d/opcache.ini" \
|
||||||
|
&& echo "opcache.interned_strings_buffer=16" >> "$PHP_INI_DIR/conf.d/opcache.ini" \
|
||||||
|
&& echo "opcache.max_accelerated_files=20000" >> "$PHP_INI_DIR/conf.d/opcache.ini" \
|
||||||
|
&& echo "opcache.validate_timestamps=0" >> "$PHP_INI_DIR/conf.d/opcache.ini" \
|
||||||
|
&& echo "realpath_cache_size=4096K" >> "$PHP_INI_DIR/conf.d/opcache.ini" \
|
||||||
|
&& echo "realpath_cache_ttl=600" >> "$PHP_INI_DIR/conf.d/opcache.ini"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Development stage
|
||||||
|
# =============================================================================
|
||||||
|
FROM base AS dev
|
||||||
|
|
||||||
|
# Enable opcache revalidation for dev (zz- prefix loads last alphabetically)
|
||||||
|
RUN echo "opcache.validate_timestamps=1" >> "$PHP_INI_DIR/conf.d/zz-opcache-dev.ini"
|
||||||
|
|
||||||
|
# Enable Xdebug for development
|
||||||
|
RUN pecl install xdebug && docker-php-ext-enable xdebug
|
||||||
|
RUN echo "xdebug.mode=develop,debug,coverage" >> "$PHP_INI_DIR/conf.d/xdebug.ini" \
|
||||||
|
&& echo "xdebug.client_host=host.docker.internal" >> "$PHP_INI_DIR/conf.d/xdebug.ini" \
|
||||||
|
&& echo "xdebug.start_with_request=trigger" >> "$PHP_INI_DIR/conf.d/xdebug.ini"
|
||||||
|
|
||||||
|
# Caddy config for FrankenPHP
|
||||||
|
ENV SERVER_NAME=:8000
|
||||||
|
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||||
|
|
||||||
|
# Create entrypoint script for dev (installs deps if needed)
|
||||||
|
RUN echo '#!/bin/sh' > /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo 'set -e' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo 'if [ ! -f /app/vendor/autoload.php ]; then' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo ' echo "Installing Composer dependencies..."' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo ' composer install --prefer-dist --no-progress --no-interaction' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo 'mkdir -p var/cache var/log && chmod -R 777 var' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
echo 'exec "$@"' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||||
|
chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
|
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Production stage
|
||||||
|
# =============================================================================
|
||||||
|
FROM base AS prod
|
||||||
|
|
||||||
|
ENV APP_ENV=prod
|
||||||
|
ENV SERVER_NAME=:8000
|
||||||
|
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
# Install dependencies (prod only, optimized)
|
||||||
|
RUN composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader
|
||||||
|
|
||||||
|
# Warmup cache
|
||||||
|
RUN php bin/console cache:warmup
|
||||||
|
|
||||||
|
# Ensure var directory exists with proper permissions
|
||||||
|
RUN mkdir -p var/cache var/log && chmod -R 755 var
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||||
19
backend/bin/console
Executable file
19
backend/bin/console
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
|
||||||
|
if (!is_file(dirname(__DIR__) . '/vendor/autoload_runtime.php')) {
|
||||||
|
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once dirname(__DIR__) . '/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return static function (array $context): Application {
|
||||||
|
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
|
||||||
|
return new Application($kernel);
|
||||||
|
};
|
||||||
109
backend/composer.json
Normal file
109
backend/composer.json
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"name": "classeo/backend",
|
||||||
|
"description": "Classeo - Backend API (Symfony 8 + API Platform)",
|
||||||
|
"type": "project",
|
||||||
|
"license": "proprietary",
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.5",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"ext-intl": "*",
|
||||||
|
"api-platform/core": "^4.0",
|
||||||
|
"doctrine/dbal": "^4.0",
|
||||||
|
"doctrine/doctrine-bundle": "^2.13 || ^3.0@dev",
|
||||||
|
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||||
|
"doctrine/orm": "^3.3",
|
||||||
|
"lexik/jwt-authentication-bundle": "^3.2",
|
||||||
|
"ramsey/uuid": "^4.7",
|
||||||
|
"symfony/amqp-messenger": "^8.0",
|
||||||
|
"symfony/asset": "^8.0",
|
||||||
|
"symfony/console": "^8.0",
|
||||||
|
"symfony/doctrine-messenger": "^8.0",
|
||||||
|
"symfony/dotenv": "^8.0",
|
||||||
|
"symfony/flex": "^2",
|
||||||
|
"symfony/framework-bundle": "^8.0",
|
||||||
|
"symfony/messenger": "^8.0",
|
||||||
|
"symfony/monolog-bundle": "^4.0",
|
||||||
|
"symfony/property-access": "^8.0",
|
||||||
|
"symfony/property-info": "^8.0",
|
||||||
|
"symfony/runtime": "^8.0",
|
||||||
|
"symfony/security-bundle": "^8.0",
|
||||||
|
"symfony/serializer": "^8.0",
|
||||||
|
"symfony/twig-bundle": "^8.0",
|
||||||
|
"symfony/uid": "^8.0",
|
||||||
|
"symfony/validator": "^8.0",
|
||||||
|
"symfony/yaml": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/doctrine-fixtures-bundle": "^4.0",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"phpstan/phpstan-doctrine": "^2.0",
|
||||||
|
"phpstan/phpstan-symfony": "^2.0",
|
||||||
|
"phpunit/phpunit": "^11.0",
|
||||||
|
"symfony/browser-kit": "^8.0",
|
||||||
|
"symfony/css-selector": "^8.0",
|
||||||
|
"symfony/debug-bundle": "^8.0",
|
||||||
|
"symfony/maker-bundle": "^1.62",
|
||||||
|
"symfony/phpunit-bridge": "^8.0",
|
||||||
|
"symfony/stopwatch": "^8.0",
|
||||||
|
"symfony/web-profiler-bundle": "^8.0",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.65"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"php-http/discovery": true,
|
||||||
|
"symfony/flex": true,
|
||||||
|
"symfony/runtime": true
|
||||||
|
},
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-ctype": "*",
|
||||||
|
"symfony/polyfill-iconv": "*",
|
||||||
|
"symfony/polyfill-php72": "*",
|
||||||
|
"symfony/polyfill-php73": "*",
|
||||||
|
"symfony/polyfill-php74": "*",
|
||||||
|
"symfony/polyfill-php80": "*",
|
||||||
|
"symfony/polyfill-php81": "*",
|
||||||
|
"symfony/polyfill-php82": "*",
|
||||||
|
"symfony/polyfill-php83": "*",
|
||||||
|
"symfony/polyfill-php84": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"auto-scripts": {
|
||||||
|
"cache:clear": "symfony-cmd",
|
||||||
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
|
},
|
||||||
|
"post-install-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
],
|
||||||
|
"test": "phpunit",
|
||||||
|
"phpstan": "phpstan analyse --memory-limit=512M",
|
||||||
|
"cs-fix": "php-cs-fixer fix",
|
||||||
|
"cs-check": "php-cs-fixer fix --dry-run --diff"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/symfony": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"symfony": {
|
||||||
|
"allow-contrib": false,
|
||||||
|
"require": "8.0.*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
backend/config/packages/api_platform.yaml
Normal file
31
backend/config/packages/api_platform.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
api_platform:
|
||||||
|
title: 'Classeo API'
|
||||||
|
description: 'API for Classeo - School Management System'
|
||||||
|
version: '1.0.0'
|
||||||
|
|
||||||
|
# Enable OpenAPI documentation
|
||||||
|
formats:
|
||||||
|
jsonld: ['application/ld+json']
|
||||||
|
json: ['application/json']
|
||||||
|
html: ['text/html']
|
||||||
|
|
||||||
|
docs_formats:
|
||||||
|
jsonld: ['application/ld+json']
|
||||||
|
jsonopenapi: ['application/vnd.openapi+json']
|
||||||
|
html: ['text/html']
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
defaults:
|
||||||
|
stateless: true
|
||||||
|
cache_headers:
|
||||||
|
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||||
|
extra_properties:
|
||||||
|
standard_put: true
|
||||||
|
rfc_7807_compliant_errors: true
|
||||||
|
pagination_items_per_page: 30
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
collection:
|
||||||
|
pagination:
|
||||||
|
enabled: true
|
||||||
|
items_per_page_parameter_name: 'itemsPerPage'
|
||||||
13
backend/config/packages/cache.yaml
Normal file
13
backend/config/packages/cache.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||||
|
prefix_seed: classeo/backend
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
pools:
|
||||||
|
doctrine.system_cache_pool:
|
||||||
|
adapter: cache.adapter.system
|
||||||
|
doctrine.result_cache_pool:
|
||||||
|
adapter: cache.adapter.system
|
||||||
52
backend/config/packages/doctrine.yaml
Normal file
52
backend/config/packages/doctrine.yaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
doctrine:
|
||||||
|
dbal:
|
||||||
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
|
profiling_collect_backtrace: '%kernel.debug%'
|
||||||
|
|
||||||
|
orm:
|
||||||
|
validate_xml_mapping: true
|
||||||
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
|
auto_mapping: true
|
||||||
|
mappings:
|
||||||
|
# Infrastructure mappings - keep entities separate from Domain
|
||||||
|
Administration:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/Administration/Infrastructure/Persistence/Mapping'
|
||||||
|
prefix: 'App\Administration\Infrastructure\Persistence\Mapping'
|
||||||
|
alias: Administration
|
||||||
|
Scolarite:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/Scolarite/Infrastructure/Persistence/Mapping'
|
||||||
|
prefix: 'App\Scolarite\Infrastructure\Persistence\Mapping'
|
||||||
|
alias: Scolarite
|
||||||
|
VieScolaire:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/VieScolaire/Infrastructure/Persistence/Mapping'
|
||||||
|
prefix: 'App\VieScolaire\Infrastructure\Persistence\Mapping'
|
||||||
|
alias: VieScolaire
|
||||||
|
Communication:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/Communication/Infrastructure/Persistence/Mapping'
|
||||||
|
prefix: 'App\Communication\Infrastructure\Persistence\Mapping'
|
||||||
|
alias: Communication
|
||||||
|
controller_resolver:
|
||||||
|
auto_mapping: false
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
doctrine:
|
||||||
|
dbal:
|
||||||
|
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
doctrine:
|
||||||
|
orm:
|
||||||
|
query_cache_driver:
|
||||||
|
type: pool
|
||||||
|
pool: doctrine.system_cache_pool
|
||||||
|
result_cache_driver:
|
||||||
|
type: pool
|
||||||
|
pool: doctrine.result_cache_pool
|
||||||
8
backend/config/packages/doctrine_migrations.yaml
Normal file
8
backend/config/packages/doctrine_migrations.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
doctrine_migrations:
|
||||||
|
migrations_paths:
|
||||||
|
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||||
|
enable_profiler: false
|
||||||
|
organize_migrations: none
|
||||||
|
all_or_nothing: true
|
||||||
|
transactional: true
|
||||||
|
check_database_platform: true
|
||||||
25
backend/config/packages/framework.yaml
Normal file
25
backend/config/packages/framework.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
|
framework:
|
||||||
|
secret: '%env(APP_SECRET)%'
|
||||||
|
csrf_protection: true
|
||||||
|
handle_all_throwables: true
|
||||||
|
http_method_override: false
|
||||||
|
trusted_proxies: '%env(TRUSTED_PROXIES)%'
|
||||||
|
trusted_hosts: '%env(TRUSTED_HOSTS)%'
|
||||||
|
|
||||||
|
# Enables session support
|
||||||
|
session:
|
||||||
|
handler_id: null
|
||||||
|
cookie_secure: auto
|
||||||
|
cookie_samesite: lax
|
||||||
|
storage_factory_id: session.storage.factory.native
|
||||||
|
|
||||||
|
# Enable php_attributes routing
|
||||||
|
php_errors:
|
||||||
|
log: true
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
test: true
|
||||||
|
session:
|
||||||
|
storage_factory_id: session.storage.factory.mock_file
|
||||||
17
backend/config/packages/lexik_jwt_authentication.yaml
Normal file
17
backend/config/packages/lexik_jwt_authentication.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
lexik_jwt_authentication:
|
||||||
|
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
|
||||||
|
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
|
||||||
|
pass_phrase: '%env(JWT_PASSPHRASE)%'
|
||||||
|
token_ttl: 3600
|
||||||
|
user_id_claim: username
|
||||||
|
clock_skew: 0
|
||||||
|
|
||||||
|
# Automatically extracts the token from cookies
|
||||||
|
token_extractors:
|
||||||
|
authorization_header:
|
||||||
|
enabled: true
|
||||||
|
prefix: Bearer
|
||||||
|
name: Authorization
|
||||||
|
cookie:
|
||||||
|
enabled: true
|
||||||
|
name: BEARER
|
||||||
44
backend/config/packages/messenger.yaml
Normal file
44
backend/config/packages/messenger.yaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
framework:
|
||||||
|
messenger:
|
||||||
|
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
|
||||||
|
failure_transport: failed
|
||||||
|
|
||||||
|
# Three buses: Command, Query, Event (CQRS + Event-driven)
|
||||||
|
default_bus: command.bus
|
||||||
|
|
||||||
|
buses:
|
||||||
|
command.bus:
|
||||||
|
default_middleware: true
|
||||||
|
middleware:
|
||||||
|
- doctrine_transaction
|
||||||
|
|
||||||
|
query.bus:
|
||||||
|
default_middleware: true
|
||||||
|
|
||||||
|
event.bus:
|
||||||
|
default_middleware:
|
||||||
|
allow_no_handlers: true
|
||||||
|
|
||||||
|
transports:
|
||||||
|
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||||
|
async:
|
||||||
|
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||||
|
options:
|
||||||
|
exchange:
|
||||||
|
name: classeo_messages
|
||||||
|
type: topic
|
||||||
|
queues:
|
||||||
|
messages:
|
||||||
|
binding_keys: ['#']
|
||||||
|
retry_strategy:
|
||||||
|
max_retries: 3
|
||||||
|
delay: 1000
|
||||||
|
multiplier: 2
|
||||||
|
max_delay: 60000
|
||||||
|
|
||||||
|
failed:
|
||||||
|
dsn: 'doctrine://default?queue_name=failed'
|
||||||
|
|
||||||
|
routing:
|
||||||
|
# Route your messages to the transports
|
||||||
|
# 'App\Message\YourMessage': async
|
||||||
57
backend/config/packages/monolog.yaml
Normal file
57
backend/config/packages/monolog.yaml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
monolog:
|
||||||
|
channels:
|
||||||
|
- deprecation
|
||||||
|
- security
|
||||||
|
- audit
|
||||||
|
|
||||||
|
when@dev:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
channels: ["!event"]
|
||||||
|
formatter: monolog.formatter.json
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine", "!console"]
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!event"]
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
when@prod:
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
buffer_size: 50
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: php://stderr
|
||||||
|
level: debug
|
||||||
|
formatter: monolog.formatter.json
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine"]
|
||||||
|
deprecation:
|
||||||
|
type: stream
|
||||||
|
channels: [deprecation]
|
||||||
|
path: php://stderr
|
||||||
|
formatter: monolog.formatter.json
|
||||||
41
backend/config/packages/security.yaml
Normal file
41
backend/config/packages/security.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
security:
|
||||||
|
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||||
|
password_hashers:
|
||||||
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||||
|
|
||||||
|
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||||
|
providers:
|
||||||
|
# used to reload user from session & other features (e.g. switch_user)
|
||||||
|
# Configure user provider when User entity is created
|
||||||
|
users_in_memory:
|
||||||
|
memory:
|
||||||
|
users:
|
||||||
|
admin: { password: 'admin', roles: ['ROLE_ADMIN'] }
|
||||||
|
|
||||||
|
firewalls:
|
||||||
|
dev:
|
||||||
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
security: false
|
||||||
|
api:
|
||||||
|
pattern: ^/api
|
||||||
|
stateless: true
|
||||||
|
jwt: ~
|
||||||
|
main:
|
||||||
|
lazy: true
|
||||||
|
provider: users_in_memory
|
||||||
|
|
||||||
|
# Easy way to control access for large sections of your site
|
||||||
|
# Note: Only the *first* access control that matches will be used
|
||||||
|
access_control:
|
||||||
|
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
||||||
|
- { path: ^/api/login, roles: PUBLIC_ACCESS }
|
||||||
|
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
security:
|
||||||
|
password_hashers:
|
||||||
|
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||||
|
algorithm: auto
|
||||||
|
cost: 4 # Lowest possible value for bcrypt
|
||||||
|
time_cost: 3 # Lowest possible value for argon
|
||||||
|
memory_cost: 10 # Lowest possible value for argon
|
||||||
6
backend/config/packages/twig.yaml
Normal file
6
backend/config/packages/twig.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
twig:
|
||||||
|
file_name_pattern: '*.twig'
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
twig:
|
||||||
|
strict_variables: true
|
||||||
8
backend/config/packages/validator.yaml
Normal file
8
backend/config/packages/validator.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
email_validation_mode: html5
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
not_compromised_password: false
|
||||||
5
backend/config/routes.yaml
Normal file
5
backend/config/routes.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
controllers:
|
||||||
|
resource:
|
||||||
|
path: ../src/
|
||||||
|
namespace: App\
|
||||||
|
type: attribute
|
||||||
4
backend/config/routes/api_platform.yaml
Normal file
4
backend/config/routes/api_platform.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
api_platform:
|
||||||
|
resource: .
|
||||||
|
type: api_platform
|
||||||
|
prefix: /api
|
||||||
27
backend/config/services.yaml
Normal file
27
backend/config/services.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# This file is the entry point to configure your own services.
|
||||||
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
|
||||||
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
services:
|
||||||
|
# default configuration for services in this file
|
||||||
|
_defaults:
|
||||||
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
|
||||||
|
# makes classes in src/ available to be used as services
|
||||||
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
App\:
|
||||||
|
resource: '../src/'
|
||||||
|
exclude:
|
||||||
|
- '../src/DependencyInjection/'
|
||||||
|
- '../src/Entity/'
|
||||||
|
- '../src/Kernel.php'
|
||||||
|
# Exclude Domain layers - they should be pure PHP with no framework deps
|
||||||
|
- '../src/*/Domain/'
|
||||||
|
|
||||||
|
# Domain services need to be registered explicitly to avoid framework coupling
|
||||||
|
# Example: App\Administration\Application\Command\:
|
||||||
|
# resource: '../src/Administration/Application/Command/'
|
||||||
12
backend/phpstan.neon
Normal file
12
backend/phpstan.neon
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
parameters:
|
||||||
|
level: 9
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
excludePaths:
|
||||||
|
- src/Kernel.php
|
||||||
|
treatPhpDocTypesAsCertain: false
|
||||||
|
reportUnmatchedIgnoredErrors: false
|
||||||
|
|
||||||
|
includes:
|
||||||
|
- vendor/phpstan/phpstan-doctrine/extension.neon
|
||||||
|
- vendor/phpstan/phpstan-symfony/extension.neon
|
||||||
11
backend/public/index.php
Normal file
11
backend/public/index.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
|
||||||
|
require_once dirname(__DIR__) . '/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
return static function (array $context): Kernel {
|
||||||
|
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
|
};
|
||||||
0
backend/src/Administration/Domain/Event/.gitkeep
Normal file
0
backend/src/Administration/Domain/Event/.gitkeep
Normal file
0
backend/src/Administration/Domain/Model/.gitkeep
Normal file
0
backend/src/Administration/Domain/Model/.gitkeep
Normal file
0
backend/src/Administration/Domain/Policy/.gitkeep
Normal file
0
backend/src/Administration/Domain/Policy/.gitkeep
Normal file
0
backend/src/Administration/Domain/Service/.gitkeep
Normal file
0
backend/src/Administration/Domain/Service/.gitkeep
Normal file
0
backend/src/Communication/Application/Port/.gitkeep
Normal file
0
backend/src/Communication/Application/Port/.gitkeep
Normal file
0
backend/src/Communication/Domain/Event/.gitkeep
Normal file
0
backend/src/Communication/Domain/Event/.gitkeep
Normal file
0
backend/src/Communication/Domain/Exception/.gitkeep
Normal file
0
backend/src/Communication/Domain/Exception/.gitkeep
Normal file
0
backend/src/Communication/Domain/Model/.gitkeep
Normal file
0
backend/src/Communication/Domain/Model/.gitkeep
Normal file
0
backend/src/Communication/Domain/Policy/.gitkeep
Normal file
0
backend/src/Communication/Domain/Policy/.gitkeep
Normal file
0
backend/src/Communication/Domain/Service/.gitkeep
Normal file
0
backend/src/Communication/Domain/Service/.gitkeep
Normal file
13
backend/src/Kernel.php
Normal file
13
backend/src/Kernel.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
|
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||||
|
|
||||||
|
final class Kernel extends BaseKernel
|
||||||
|
{
|
||||||
|
use MicroKernelTrait;
|
||||||
|
}
|
||||||
0
backend/src/Scolarite/Application/Command/.gitkeep
Normal file
0
backend/src/Scolarite/Application/Command/.gitkeep
Normal file
0
backend/src/Scolarite/Application/Port/.gitkeep
Normal file
0
backend/src/Scolarite/Application/Port/.gitkeep
Normal file
0
backend/src/Scolarite/Application/Query/.gitkeep
Normal file
0
backend/src/Scolarite/Application/Query/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Event/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Event/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Exception/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Exception/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Model/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Model/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Policy/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Policy/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Repository/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Repository/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Service/.gitkeep
Normal file
0
backend/src/Scolarite/Domain/Service/.gitkeep
Normal file
0
backend/src/Scolarite/Infrastructure/Api/.gitkeep
Normal file
0
backend/src/Scolarite/Infrastructure/Api/.gitkeep
Normal file
0
backend/src/Shared/Contracts/.gitkeep
Normal file
0
backend/src/Shared/Contracts/.gitkeep
Normal file
25
backend/src/Shared/Domain/AggregateRoot.php
Normal file
25
backend/src/Shared/Domain/AggregateRoot.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Shared\Domain;
|
||||||
|
|
||||||
|
abstract class AggregateRoot
|
||||||
|
{
|
||||||
|
/** @var DomainEvent[] */
|
||||||
|
private array $domainEvents = [];
|
||||||
|
|
||||||
|
protected function recordEvent(DomainEvent $event): void
|
||||||
|
{
|
||||||
|
$this->domainEvents[] = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return DomainEvent[] */
|
||||||
|
public function pullDomainEvents(): array
|
||||||
|
{
|
||||||
|
$events = $this->domainEvents;
|
||||||
|
$this->domainEvents = [];
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
backend/src/Shared/Domain/Clock.php
Normal file
12
backend/src/Shared/Domain/Clock.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Shared\Domain;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
interface Clock
|
||||||
|
{
|
||||||
|
public function now(): DateTimeImmutable;
|
||||||
|
}
|
||||||
35
backend/src/Shared/Domain/CorrelationId.php
Normal file
35
backend/src/Shared/Domain/CorrelationId.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Shared\Domain;
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
final readonly class CorrelationId
|
||||||
|
{
|
||||||
|
private function __construct(
|
||||||
|
private string $value,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generate(): self
|
||||||
|
{
|
||||||
|
return new self(Uuid::uuid4()->toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromString(string $value): self
|
||||||
|
{
|
||||||
|
return new self($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function value(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
backend/src/Shared/Domain/DomainEvent.php
Normal file
15
backend/src/Shared/Domain/DomainEvent.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Shared\Domain;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
|
||||||
|
interface DomainEvent
|
||||||
|
{
|
||||||
|
public function occurredOn(): DateTimeImmutable;
|
||||||
|
|
||||||
|
public function aggregateId(): UuidInterface;
|
||||||
|
}
|
||||||
39
backend/src/Shared/Domain/EntityId.php
Normal file
39
backend/src/Shared/Domain/EntityId.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Shared\Domain;
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-consistent-constructor
|
||||||
|
*/
|
||||||
|
abstract readonly class EntityId
|
||||||
|
{
|
||||||
|
protected function __construct(
|
||||||
|
public UuidInterface $value,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generate(): static
|
||||||
|
{
|
||||||
|
return new static(Uuid::uuid4());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromString(string $value): static
|
||||||
|
{
|
||||||
|
return new static(Uuid::fromString($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function equals(EntityId $other): bool
|
||||||
|
{
|
||||||
|
return $this->value->equals($other->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->value->toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
backend/src/Shared/Infrastructure/Clock/SystemClock.php
Normal file
16
backend/src/Shared/Infrastructure/Clock/SystemClock.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Shared\Infrastructure\Clock;
|
||||||
|
|
||||||
|
use App\Shared\Domain\Clock;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
final readonly class SystemClock implements Clock
|
||||||
|
{
|
||||||
|
public function now(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return new DateTimeImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
0
backend/src/Shared/Infrastructure/Tenant/.gitkeep
Normal file
0
backend/src/Shared/Infrastructure/Tenant/.gitkeep
Normal file
0
backend/src/VieScolaire/Application/Port/.gitkeep
Normal file
0
backend/src/VieScolaire/Application/Port/.gitkeep
Normal file
0
backend/src/VieScolaire/Application/Query/.gitkeep
Normal file
0
backend/src/VieScolaire/Application/Query/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Event/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Event/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Exception/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Exception/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Model/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Model/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Policy/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Policy/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Repository/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Repository/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Service/.gitkeep
Normal file
0
backend/src/VieScolaire/Domain/Service/.gitkeep
Normal file
0
backend/src/VieScolaire/Infrastructure/Api/.gitkeep
Normal file
0
backend/src/VieScolaire/Infrastructure/Api/.gitkeep
Normal file
87
backend/tests/Unit/Shared/Domain/AggregateRootTest.php
Normal file
87
backend/tests/Unit/Shared/Domain/AggregateRootTest.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Unit\Shared\Domain;
|
||||||
|
|
||||||
|
use App\Shared\Domain\AggregateRoot;
|
||||||
|
use App\Shared\Domain\DomainEvent;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
|
||||||
|
final class AggregateRootTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testPullDomainEventsReturnsRecordedEvents(): void
|
||||||
|
{
|
||||||
|
$aggregate = new TestAggregate();
|
||||||
|
$event1 = new TestDomainEvent('test1');
|
||||||
|
$event2 = new TestDomainEvent('test2');
|
||||||
|
|
||||||
|
$aggregate->doSomething($event1);
|
||||||
|
$aggregate->doSomething($event2);
|
||||||
|
|
||||||
|
$events = $aggregate->pullDomainEvents();
|
||||||
|
|
||||||
|
$this->assertCount(2, $events);
|
||||||
|
$this->assertSame($event1, $events[0]);
|
||||||
|
$this->assertSame($event2, $events[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPullDomainEventsClearsEventsAfterPulling(): void
|
||||||
|
{
|
||||||
|
$aggregate = new TestAggregate();
|
||||||
|
$event = new TestDomainEvent('test');
|
||||||
|
|
||||||
|
$aggregate->doSomething($event);
|
||||||
|
$aggregate->pullDomainEvents();
|
||||||
|
|
||||||
|
$secondPull = $aggregate->pullDomainEvents();
|
||||||
|
|
||||||
|
$this->assertCount(0, $secondPull);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRecordEventAddsEventToInternalList(): void
|
||||||
|
{
|
||||||
|
$aggregate = new TestAggregate();
|
||||||
|
$event = new TestDomainEvent('test');
|
||||||
|
|
||||||
|
$aggregate->doSomething($event);
|
||||||
|
$events = $aggregate->pullDomainEvents();
|
||||||
|
|
||||||
|
$this->assertCount(1, $events);
|
||||||
|
$this->assertInstanceOf(TestDomainEvent::class, $events[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test implementations
|
||||||
|
final class TestAggregate extends AggregateRoot
|
||||||
|
{
|
||||||
|
public function doSomething(DomainEvent $event): void
|
||||||
|
{
|
||||||
|
$this->recordEvent($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final readonly class TestDomainEvent implements DomainEvent
|
||||||
|
{
|
||||||
|
private DateTimeImmutable $occurredOn;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $data,
|
||||||
|
private ?UuidInterface $testAggregateId = null,
|
||||||
|
) {
|
||||||
|
$this->occurredOn = new DateTimeImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function occurredOn(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->occurredOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function aggregateId(): UuidInterface
|
||||||
|
{
|
||||||
|
return $this->testAggregateId ?? Uuid::uuid4();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
backend/tests/Unit/Shared/Domain/CorrelationIdTest.php
Normal file
51
backend/tests/Unit/Shared/Domain/CorrelationIdTest.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Unit\Shared\Domain;
|
||||||
|
|
||||||
|
use App\Shared\Domain\CorrelationId;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
final class CorrelationIdTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGenerateCreatesValidUuid(): void
|
||||||
|
{
|
||||||
|
$correlationId = CorrelationId::generate();
|
||||||
|
|
||||||
|
$this->assertTrue(Uuid::isValid($correlationId->value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromStringCreatesCorrelationIdFromValidUuid(): void
|
||||||
|
{
|
||||||
|
$uuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||||
|
$correlationId = CorrelationId::fromString($uuid);
|
||||||
|
|
||||||
|
$this->assertSame($uuid, $correlationId->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValueReturnsUuidString(): void
|
||||||
|
{
|
||||||
|
$uuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||||
|
$correlationId = CorrelationId::fromString($uuid);
|
||||||
|
|
||||||
|
$this->assertSame($uuid, $correlationId->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToStringReturnsUuidString(): void
|
||||||
|
{
|
||||||
|
$uuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||||
|
$correlationId = CorrelationId::fromString($uuid);
|
||||||
|
|
||||||
|
$this->assertSame($uuid, (string) $correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateCreatesDifferentIdsEachTime(): void
|
||||||
|
{
|
||||||
|
$id1 = CorrelationId::generate();
|
||||||
|
$id2 = CorrelationId::generate();
|
||||||
|
|
||||||
|
$this->assertNotSame($id1->value(), $id2->value());
|
||||||
|
}
|
||||||
|
}
|
||||||
58
backend/tests/Unit/Shared/Domain/EntityIdTest.php
Normal file
58
backend/tests/Unit/Shared/Domain/EntityIdTest.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Unit\Shared\Domain;
|
||||||
|
|
||||||
|
use App\Shared\Domain\EntityId;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
final class EntityIdTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGenerateCreatesValidUuid(): void
|
||||||
|
{
|
||||||
|
$id = TestEntityId::generate();
|
||||||
|
|
||||||
|
$this->assertInstanceOf(TestEntityId::class, $id);
|
||||||
|
$this->assertTrue(Uuid::isValid((string) $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromStringCreatesEntityIdFromValidUuid(): void
|
||||||
|
{
|
||||||
|
$uuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||||
|
$id = TestEntityId::fromString($uuid);
|
||||||
|
|
||||||
|
$this->assertSame($uuid, (string) $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEqualsReturnsTrueForSameValue(): void
|
||||||
|
{
|
||||||
|
$uuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||||
|
$id1 = TestEntityId::fromString($uuid);
|
||||||
|
$id2 = TestEntityId::fromString($uuid);
|
||||||
|
|
||||||
|
$this->assertTrue($id1->equals($id2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEqualsReturnsFalseForDifferentValue(): void
|
||||||
|
{
|
||||||
|
$id1 = TestEntityId::generate();
|
||||||
|
$id2 = TestEntityId::generate();
|
||||||
|
|
||||||
|
$this->assertFalse($id1->equals($id2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToStringReturnsUuidString(): void
|
||||||
|
{
|
||||||
|
$uuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||||
|
$id = TestEntityId::fromString($uuid);
|
||||||
|
|
||||||
|
$this->assertSame($uuid, (string) $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concrete implementation
|
||||||
|
final readonly class TestEntityId extends EntityId
|
||||||
|
{
|
||||||
|
}
|
||||||
13
backend/tests/bootstrap.php
Normal file
13
backend/tests/bootstrap.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
|
||||||
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
if (file_exists(dirname(__DIR__) . '/config/bootstrap.php')) {
|
||||||
|
require dirname(__DIR__) . '/config/bootstrap.php';
|
||||||
|
} elseif (method_exists(Dotenv::class, 'bootEnv')) {
|
||||||
|
(new Dotenv())->bootEnv(dirname(__DIR__) . '/.env');
|
||||||
|
}
|
||||||
199
compose.yaml
Normal file
199
compose.yaml
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
services:
|
||||||
|
# =============================================================================
|
||||||
|
# BACKEND API - PHP 8.5 + FrankenPHP
|
||||||
|
# =============================================================================
|
||||||
|
php:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: dev
|
||||||
|
container_name: classeo_php
|
||||||
|
environment:
|
||||||
|
APP_ENV: dev
|
||||||
|
APP_DEBUG: 1
|
||||||
|
DATABASE_URL: postgresql://classeo:classeo@db:5432/classeo_master?serverVersion=18&charset=utf8
|
||||||
|
REDIS_URL: redis://redis:6379
|
||||||
|
MESSENGER_TRANSPORT_DSN: amqp://guest:guest@rabbitmq:5672/%2f/messages
|
||||||
|
MERCURE_URL: http://mercure/.well-known/mercure
|
||||||
|
MERCURE_PUBLIC_URL: http://localhost:3000/.well-known/mercure
|
||||||
|
MERCURE_JWT_SECRET: mercure_publisher_secret_change_me_in_production
|
||||||
|
MEILISEARCH_URL: http://meilisearch:7700
|
||||||
|
MEILISEARCH_API_KEY: masterKey
|
||||||
|
MAILER_DSN: smtp://mailpit:1025
|
||||||
|
ports:
|
||||||
|
- "18000:8000" # Port externe 18000 pour eviter conflit
|
||||||
|
volumes:
|
||||||
|
- ./backend:/app:cached
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
rabbitmq:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# FRONTEND - SvelteKit + Node.js
|
||||||
|
# =============================================================================
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: dev
|
||||||
|
container_name: classeo_frontend
|
||||||
|
environment:
|
||||||
|
PUBLIC_API_URL: http://localhost:18000/api
|
||||||
|
PUBLIC_MERCURE_URL: http://localhost:3000/.well-known/mercure
|
||||||
|
ports:
|
||||||
|
- "5174:5173" # Port externe 5174 pour eviter conflit
|
||||||
|
volumes:
|
||||||
|
- ./frontend:/app:cached
|
||||||
|
- frontend_node_modules:/app/node_modules
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5173/"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DATABASE - PostgreSQL 18.1
|
||||||
|
# =============================================================================
|
||||||
|
db:
|
||||||
|
image: postgres:18.1-alpine
|
||||||
|
container_name: classeo_db
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: classeo_master
|
||||||
|
POSTGRES_USER: classeo
|
||||||
|
POSTGRES_PASSWORD: classeo
|
||||||
|
ports:
|
||||||
|
- "5433:5432" # Port externe 5433 pour eviter conflit avec PostgreSQL local
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U classeo -d classeo_master"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CACHE & SESSIONS - Redis 7.4
|
||||||
|
# =============================================================================
|
||||||
|
redis:
|
||||||
|
image: redis:7.4-alpine
|
||||||
|
container_name: classeo_redis
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
ports:
|
||||||
|
- "6380:6379" # Port externe 6380 pour eviter conflit avec Redis local
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 5s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MESSAGE QUEUE - RabbitMQ 4.2
|
||||||
|
# =============================================================================
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:4.2-management-alpine
|
||||||
|
container_name: classeo_rabbitmq
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: guest
|
||||||
|
RABBITMQ_DEFAULT_PASS: guest
|
||||||
|
ports:
|
||||||
|
- "5672:5672"
|
||||||
|
- "15672:15672"
|
||||||
|
volumes:
|
||||||
|
- rabbitmq_data:/var/lib/rabbitmq
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REAL-TIME SSE - Mercure
|
||||||
|
# =============================================================================
|
||||||
|
mercure:
|
||||||
|
image: dunglas/mercure:latest
|
||||||
|
container_name: classeo_mercure
|
||||||
|
environment:
|
||||||
|
MERCURE_PUBLISHER_JWT_KEY: "mercure_publisher_secret_change_me_in_production"
|
||||||
|
MERCURE_SUBSCRIBER_JWT_KEY: "mercure_subscriber_secret_change_me_in_production"
|
||||||
|
SERVER_NAME: ":80"
|
||||||
|
MERCURE_EXTRA_DIRECTIVES: |
|
||||||
|
cors_origins http://localhost:5174
|
||||||
|
anonymous
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost/.well-known/mercure"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# FULL-TEXT SEARCH - Meilisearch 1.12
|
||||||
|
# =============================================================================
|
||||||
|
meilisearch:
|
||||||
|
image: getmeili/meilisearch:v1.12
|
||||||
|
container_name: classeo_meilisearch
|
||||||
|
environment:
|
||||||
|
MEILI_MASTER_KEY: "masterKey"
|
||||||
|
MEILI_ENV: "development"
|
||||||
|
ports:
|
||||||
|
- "7700:7700"
|
||||||
|
volumes:
|
||||||
|
- meilisearch_data:/meili_data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:7700/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EMAIL TESTING - Mailpit
|
||||||
|
# =============================================================================
|
||||||
|
mailpit:
|
||||||
|
image: axllent/mailpit:latest
|
||||||
|
container_name: classeo_mailpit
|
||||||
|
ports:
|
||||||
|
- "1025:1025"
|
||||||
|
- "8025:8025"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 5s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VOLUMES PERSISTANTS
|
||||||
|
# =============================================================================
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
rabbitmq_data:
|
||||||
|
meilisearch_data:
|
||||||
|
frontend_node_modules:
|
||||||
40
docs/adr/index.md
Normal file
40
docs/adr/index.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Architecture Decision Records (ADR)
|
||||||
|
|
||||||
|
Ce dossier contient les decisions architecturales du projet Classeo.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
Chaque ADR suit le template :
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# ADR-XXX: Titre
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Proposed | Accepted | Deprecated | Superseded by [ADR-YYY]
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Description du probleme ou de la situation.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
La decision prise et pourquoi.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
Impact positif et negatif de cette decision.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
| # | Titre | Status | Date |
|
||||||
|
|---|-------|--------|------|
|
||||||
|
| 001 | [Architecture DDD avec Bounded Contexts](./001-ddd-bounded-contexts.md) | Accepted | 2026-01 |
|
||||||
|
| 002 | [Svelte 5 Runes Only](./002-svelte5-runes-only.md) | Accepted | 2026-01 |
|
||||||
|
| 003 | [PHP 8.5 Property Hooks](./003-php85-property-hooks.md) | Accepted | 2026-01 |
|
||||||
|
|
||||||
|
## Comment proposer une nouvelle ADR
|
||||||
|
|
||||||
|
1. Copier le template `template.md`
|
||||||
|
2. Nommer `XXX-titre-court.md`
|
||||||
|
3. Remplir les sections
|
||||||
|
4. Soumettre en Pull Request
|
||||||
|
5. Discuter en equipe
|
||||||
|
6. Merger = Accepted
|
||||||
37
frontend/.gitignore
vendored
Normal file
37
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Dependencies
|
||||||
|
# =============================================================================
|
||||||
|
node_modules/
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Build output
|
||||||
|
# =============================================================================
|
||||||
|
/.svelte-kit/
|
||||||
|
/build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Environment files
|
||||||
|
# =============================================================================
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Testing
|
||||||
|
# =============================================================================
|
||||||
|
/coverage/
|
||||||
|
/playwright-report/
|
||||||
|
/test-results/
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PWA
|
||||||
|
# =============================================================================
|
||||||
|
dev-dist/
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Misc
|
||||||
|
# =============================================================================
|
||||||
|
*.local
|
||||||
|
*.tsbuildinfo
|
||||||
15
frontend/.prettierrc
Normal file
15
frontend/.prettierrc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user