chore: initial commit — formation-hub conception phase
Conception complete (Phase 0) pour formation-hub Acadenice : - 19 docs Merise Agile + UML + GitOps + plans (tests/deploy/ops/api) cf docs/00-readme.md pour l'index complet - Stack Docker compose (Docmost + Baserow + Postgres + Redis + MinIO local FS) compose.yml + compose.staging.yml + compose.prod.yml - CI/CD GitHub Actions skeleton (ci, deploy-staging, deploy-prod) - Bridge service skeleton (Hono + TS + Biome + Vitest + zod + pino) - Templates GitHub : PR + 3 issue types + CODEOWNERS + dependabot.yml - Scripts ops : healthcheck, backup quotidien, smoke-test post-deploy - LICENSE AGPL-3.0 + SECURITY.md + CONTRIBUTING.md + CHANGELOG.md - Diagramme drawIO archi infra (XML importable dans diagrams.net) Decisions structurelles enregistrees : - Scope CFA + Agence avec entite PERSONNE pivot multi-roles (ADR-001) - Stack composite Docmost AGPL + Baserow MIT + bridge custom (ADR-001) - Path B : UX quasi-unified via Tiptap node-views custom (ADR-002) - Monorepo trunk-based development (ADR-003) - Postgres separe Docmost/Baserow (ADR-004) - Bridge stack Node 22 + Hono (ADR-005) - Repo neuf prefere a fork Docmost - Prod-like des le jour 1 (pas MVP)
This commit is contained in:
commit
668576cdc4
55 changed files with 7986 additions and 0 deletions
27
.editorconfig
Normal file
27
.editorconfig
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.{sh,bash}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{ts,tsx,js,jsx,json}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
14
.env.example
Normal file
14
.env.example
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# formation-hub — variables d'environnement (local dev)
|
||||||
|
# Copier vers .env et remplir avec des valeurs reelles.
|
||||||
|
|
||||||
|
# Docmost
|
||||||
|
DOCMOST_URL=http://localhost:3000
|
||||||
|
DOCMOST_APP_SECRET=changeme-please-rotate-32-chars-minimum
|
||||||
|
DOCMOST_DB_PASSWORD=changeme
|
||||||
|
|
||||||
|
# Baserow
|
||||||
|
BASEROW_URL=http://localhost:8080
|
||||||
|
|
||||||
|
# Bridge (Phase 2 — laisser vide pour l'instant)
|
||||||
|
BASEROW_API_TOKEN=
|
||||||
|
DOCMOST_API_TOKEN=
|
||||||
25
.github/CODEOWNERS
vendored
Normal file
25
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# CODEOWNERS — Acadenice formation-hub
|
||||||
|
# Regles d'auto-assignment de reviewer sur les PRs.
|
||||||
|
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||||
|
|
||||||
|
# Default owner (fallback)
|
||||||
|
* @Imugiii
|
||||||
|
|
||||||
|
# Infra & ops
|
||||||
|
/.github/workflows/ @Imugiii
|
||||||
|
/compose*.yml @Imugiii
|
||||||
|
/Makefile @Imugiii
|
||||||
|
/scripts/ @Imugiii
|
||||||
|
|
||||||
|
# Code custom (bridge service)
|
||||||
|
/bridge/ @Imugiii
|
||||||
|
|
||||||
|
# Schemas Baserow
|
||||||
|
/baserow/ @Imugiii
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
/docs/ @Imugiii
|
||||||
|
|
||||||
|
# Security-sensitive
|
||||||
|
/SECURITY.md @Imugiii
|
||||||
|
/.env.example @Imugiii
|
||||||
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Signaler un bug
|
||||||
|
title: "[BUG] "
|
||||||
|
labels: bug
|
||||||
|
assignees: Imugiii
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- Quoi -->
|
||||||
|
|
||||||
|
## Etapes pour reproduire
|
||||||
|
|
||||||
|
1. ...
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
## Comportement attendu
|
||||||
|
|
||||||
|
<!-- ... -->
|
||||||
|
|
||||||
|
## Comportement observe
|
||||||
|
|
||||||
|
<!-- ... -->
|
||||||
|
|
||||||
|
## Environnement
|
||||||
|
|
||||||
|
- Env (local / staging / prod) :
|
||||||
|
- Version (commit SHA ou tag) :
|
||||||
|
- Browser / device :
|
||||||
|
- OS :
|
||||||
|
|
||||||
|
## Logs / screenshots
|
||||||
|
|
||||||
|
<!-- Coller les logs pertinents (sans secrets) ou screenshots -->
|
||||||
|
|
||||||
|
```
|
||||||
|
<logs ici>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Severite
|
||||||
|
|
||||||
|
- [ ] CRITICAL (service down / data loss)
|
||||||
|
- [ ] HIGH (degradation majeure)
|
||||||
|
- [ ] MEDIUM (bug fonctionnel avec workaround)
|
||||||
|
- [ ] LOW (cosmetic, edge case)
|
||||||
|
|
||||||
|
## Notes additionnelles
|
||||||
|
|
||||||
|
<!-- Cas reproductible ? Frequence ? Premiere occurrence ? -->
|
||||||
41
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Proposer une nouvelle fonctionnalite
|
||||||
|
title: "[FEAT] "
|
||||||
|
labels: enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
## Probleme metier
|
||||||
|
|
||||||
|
<!-- Quel probleme cette feature resout ? -->
|
||||||
|
|
||||||
|
## Solution proposee
|
||||||
|
|
||||||
|
<!-- Comment on resout ? -->
|
||||||
|
|
||||||
|
## Alternatives considerees
|
||||||
|
|
||||||
|
<!-- Autres options envisagees et pourquoi elles ont ete ecartees -->
|
||||||
|
|
||||||
|
## Impact estime
|
||||||
|
|
||||||
|
- Effort dev : <S / M / L / XL>
|
||||||
|
- Valeur metier : <faible / moyenne / haute / critique>
|
||||||
|
- Phase visee : <Phase 1 / 2 / 3 / 4>
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
```gherkin
|
||||||
|
Scenario: ...
|
||||||
|
Given ...
|
||||||
|
When ...
|
||||||
|
Then ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## UC concerne(s)
|
||||||
|
|
||||||
|
<!-- Reference a doc 11-uml-use-cases.md, ex: UC-13, UCA-07 -->
|
||||||
|
|
||||||
|
## Notes additionnelles
|
||||||
|
|
||||||
|
<!-- Mockups, screenshots, references -->
|
||||||
37
.github/ISSUE_TEMPLATE/security.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE/security.md
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
name: Security report (PUBLIC issue NON RECOMMANDE)
|
||||||
|
about: Pour signaler une vulnerabilite, voir SECURITY.md
|
||||||
|
title: "[SEC] "
|
||||||
|
labels: security
|
||||||
|
assignees: Imugiii
|
||||||
|
---
|
||||||
|
|
||||||
|
## STOP
|
||||||
|
|
||||||
|
**Si tu signales une vulnerabilite reelle, NE PAS ouvrir une issue publique.**
|
||||||
|
|
||||||
|
Contacte : **security@acadenice.fr**
|
||||||
|
|
||||||
|
Voir `SECURITY.md` pour le process complet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Si c'est une suggestion non-sensible (hardening, best practice)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
<!-- Quoi -->
|
||||||
|
|
||||||
|
### Risk assessment
|
||||||
|
|
||||||
|
- CVSS score estime :
|
||||||
|
- Vector :
|
||||||
|
- Impact si exploite :
|
||||||
|
|
||||||
|
### Recommandation
|
||||||
|
|
||||||
|
<!-- Comment fixer -->
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
<!-- CVE, CWE, OWASP, etc. -->
|
||||||
39
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
39
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- Que fait cette PR et pourquoi ? -->
|
||||||
|
|
||||||
|
## Type de changement
|
||||||
|
|
||||||
|
- [ ] feat — nouvelle fonctionnalite
|
||||||
|
- [ ] fix — bug fix
|
||||||
|
- [ ] docs — documentation seulement
|
||||||
|
- [ ] refactor — refactor sans changement comportement
|
||||||
|
- [ ] test — ajout/modif tests
|
||||||
|
- [ ] chore — maintenance, deps, tooling
|
||||||
|
- [ ] ops — infra, CI/CD
|
||||||
|
- [ ] sec — security
|
||||||
|
|
||||||
|
## Issue liee
|
||||||
|
|
||||||
|
Closes #<num>
|
||||||
|
|
||||||
|
## Tests realises
|
||||||
|
|
||||||
|
- [ ] Tests unitaires ajoutes/modifies
|
||||||
|
- [ ] Tests integration ajoutes/modifies
|
||||||
|
- [ ] Test manuel local
|
||||||
|
- [ ] Test sur staging (si applicable)
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] CI vert (lint + type-check + tests + security)
|
||||||
|
- [ ] Pas de secret commit (verifier diff)
|
||||||
|
- [ ] Doc mise a jour si necessaire (`docs/`)
|
||||||
|
- [ ] CHANGELOG.md mis a jour si user-facing
|
||||||
|
- [ ] Migration data si schema change
|
||||||
|
- [ ] Compatible avec versions Docmost / Baserow pinned
|
||||||
|
- [ ] Coverage minimum respecte (80% domain, 70% global)
|
||||||
|
|
||||||
|
## Notes pour le reviewer
|
||||||
|
|
||||||
|
<!-- Points d'attention, decisions architecturales, alternatives considerees -->
|
||||||
60
.github/dependabot.yml
vendored
Normal file
60
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Bridge service (npm)
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/bridge"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "06:00"
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
versioning-strategy: "increase"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "bridge"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore"
|
||||||
|
include: "scope"
|
||||||
|
groups:
|
||||||
|
production-dependencies:
|
||||||
|
dependency-type: "production"
|
||||||
|
development-dependencies:
|
||||||
|
dependency-type: "development"
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "github-actions"
|
||||||
|
commit-message:
|
||||||
|
prefix: "ops"
|
||||||
|
|
||||||
|
# Docker compose (base images Postgres, Redis, etc.)
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "docker"
|
||||||
|
commit-message:
|
||||||
|
prefix: "ops"
|
||||||
|
|
||||||
|
# Docker Bridge Dockerfile
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/bridge"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "docker"
|
||||||
|
- "bridge"
|
||||||
|
commit-message:
|
||||||
|
prefix: "ops"
|
||||||
145
.github/workflows/ci.yml
vendored
Normal file
145
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-bridge:
|
||||||
|
name: Lint bridge (Biome)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: bridge/package-lock.json
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npx biome ci .
|
||||||
|
|
||||||
|
typecheck-bridge:
|
||||||
|
name: Type-check bridge
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint-bridge
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: bridge/package-lock.json
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run typecheck
|
||||||
|
|
||||||
|
test-bridge-unit:
|
||||||
|
name: Tests unit bridge
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: typecheck-bridge
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: bridge/package-lock.json
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run test:unit -- --coverage
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-unit
|
||||||
|
path: bridge/coverage/
|
||||||
|
|
||||||
|
test-bridge-integration:
|
||||||
|
name: Tests integration bridge
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: typecheck-bridge
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD: test
|
||||||
|
POSTGRES_DB: testdb
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U test"
|
||||||
|
--health-interval 5s
|
||||||
|
--health-timeout 3s
|
||||||
|
--health-retries 10
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 5s
|
||||||
|
--health-timeout 3s
|
||||||
|
--health-retries 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: bridge/package-lock.json
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run test:integration
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
|
||||||
|
REDIS_URL: redis://localhost:6379
|
||||||
|
|
||||||
|
security-scan:
|
||||||
|
name: Security scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Secret scanning (TruffleHog)
|
||||||
|
uses: trufflesecurity/trufflehog@main
|
||||||
|
with:
|
||||||
|
path: ./
|
||||||
|
extra_args: --only-verified
|
||||||
|
- name: SAST (Semgrep)
|
||||||
|
uses: semgrep/semgrep-action@v1
|
||||||
|
with:
|
||||||
|
config: >-
|
||||||
|
p/javascript
|
||||||
|
p/typescript
|
||||||
|
p/security-audit
|
||||||
|
p/secrets
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Dep audit (npm audit)
|
||||||
|
run: cd bridge && npm audit --audit-level=high
|
||||||
|
continue-on-error: false
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
name: Docker build + healthcheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test-bridge-unit, test-bridge-integration, security-scan]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build images
|
||||||
|
run: docker compose build
|
||||||
|
- name: Up stack
|
||||||
|
run: |
|
||||||
|
cp .env.example .env
|
||||||
|
docker compose up -d
|
||||||
|
- name: Wait for services
|
||||||
|
run: sleep 30
|
||||||
|
- name: Healthcheck
|
||||||
|
run: ./scripts/healthcheck.sh
|
||||||
|
- name: Logs on failure
|
||||||
|
if: failure()
|
||||||
|
run: docker compose logs
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
run: docker compose down -v
|
||||||
81
.github/workflows/deploy-prod.yml
vendored
Normal file
81
.github/workflows/deploy-prod.yml
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
name: Deploy Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
ref:
|
||||||
|
description: "Tag a deployer (ex: v1.2.3)"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-prod
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to production
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: production # required reviewers configures GitHub UI
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.ref || github.ref_name }}
|
||||||
|
|
||||||
|
- name: Validate compose configs
|
||||||
|
run: docker compose -f compose.yml -f compose.prod.yml config > /dev/null
|
||||||
|
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.PROD_HOST }}
|
||||||
|
username: ${{ secrets.PROD_USER }}
|
||||||
|
key: ${{ secrets.PROD_SSH_KEY }}
|
||||||
|
script_stop: true
|
||||||
|
script: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd /opt/formation-hub
|
||||||
|
git fetch --tags
|
||||||
|
git checkout ${{ github.event.inputs.ref || github.ref_name }}
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml pull
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml up -d
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
|
||||||
|
- name: Smoke test
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -fsS --max-time 10 ${{ secrets.PROD_URL }}/api/health || exit 1
|
||||||
|
|
||||||
|
- name: Watch logs (5 min)
|
||||||
|
run: |
|
||||||
|
# Optionnel : monitor logs apres deploy
|
||||||
|
echo "Post-deploy watch — verifier monitoring/alerts pendant 30 min"
|
||||||
|
|
||||||
|
- name: Notify on success
|
||||||
|
if: success()
|
||||||
|
uses: slackapi/slack-github-action@v1
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": "PROD deployed: ${{ github.event.inputs.ref || github.ref_name }} — sha ${{ github.sha }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Notify on failure
|
||||||
|
if: failure()
|
||||||
|
uses: slackapi/slack-github-action@v1
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": "PROD deploy FAILED — ${{ github.event.inputs.ref || github.ref_name }}. Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
continue-on-error: true
|
||||||
57
.github/workflows/deploy-staging.yml
vendored
Normal file
57
.github/workflows/deploy-staging.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
name: Deploy Staging
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-staging
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to staging
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: staging
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate compose configs
|
||||||
|
run: docker compose -f compose.yml -f compose.staging.yml config > /dev/null
|
||||||
|
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.STAGING_HOST }}
|
||||||
|
username: ${{ secrets.STAGING_USER }}
|
||||||
|
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||||
|
script_stop: true
|
||||||
|
script: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd /opt/formation-hub
|
||||||
|
git fetch --all
|
||||||
|
git checkout ${{ github.sha }}
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml pull
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml up -d
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
|
||||||
|
- name: Smoke test
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -fsS --max-time 10 ${{ secrets.STAGING_URL }}/api/health || exit 1
|
||||||
|
|
||||||
|
- name: Notify on failure
|
||||||
|
if: failure()
|
||||||
|
uses: slackapi/slack-github-action@v1
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": "Deploy staging FAILED — sha ${{ github.sha }}\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
continue-on-error: true
|
||||||
50
.gitignore
vendored
Normal file
50
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.env.staging
|
||||||
|
.env.prod
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# Backups
|
||||||
|
backups/
|
||||||
|
*.sql.gz
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.next/
|
||||||
|
.turbo/
|
||||||
|
.vitest/
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
*.pid
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Editors
|
||||||
|
.idea/
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json.example
|
||||||
|
|
||||||
|
# Python (si seed scripts)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.cache/
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
32
CHANGELOG.md
Normal file
32
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
Toutes les modifications notables de ce projet sont documentees ici.
|
||||||
|
|
||||||
|
Format base sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
|
||||||
|
Versionning suit [Semantic Versioning](https://semver.org/lang/fr/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Conception complete Phase 0 : 19 documents Merise Agile + UML + GitOps + plans tests/deploy/ops/api
|
||||||
|
- Stack Docker compose locale (Docmost + Baserow + Postgres + Redis)
|
||||||
|
- Makefile commandes ops (up, down, logs, backup, restore)
|
||||||
|
- Skeleton CI/CD GitHub Actions (ci, deploy-staging, deploy-prod)
|
||||||
|
- Templates PR + 3 issue types
|
||||||
|
- Documentation architecturale poussee sur Outline (collection [AGENCE] R&D Notion-Like)
|
||||||
|
- Diagramme drawIO archi infra (XML importable)
|
||||||
|
|
||||||
|
### Decided
|
||||||
|
|
||||||
|
- Scope etendu CFA + Agence approuve (entite PERSONNE pivot avec roles multiples)
|
||||||
|
- Stack composite Docmost (AGPL) + Baserow (MIT) + bridge custom Node TS
|
||||||
|
- Path B retenu : UX quasi-unified via Tiptap node-views custom (Phase 2)
|
||||||
|
- Repo neuf prefere a fork Docmost (decouplage propre)
|
||||||
|
- Prod-like des le jour 1 (pas MVP)
|
||||||
|
|
||||||
|
## [0.1.0] - TBD
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- (premier release apres Phase 1 vanilla deploy staging)
|
||||||
123
CONTRIBUTING.md
Normal file
123
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Merci de contribuer a `formation-hub`. Ce doc resume les conventions et le workflow.
|
||||||
|
|
||||||
|
## Code of conduct
|
||||||
|
|
||||||
|
Etre respectueux. Critiquer le code, pas les personnes. Pas de tolerance pour le harcelement.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### 1. Setup local
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone git@github.com:AcadeNice/wiki.git formation-hub
|
||||||
|
cd formation-hub
|
||||||
|
cp .env.example .env
|
||||||
|
# editer .env avec tes secrets (cf SECURITY.md)
|
||||||
|
make up
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Branches
|
||||||
|
|
||||||
|
- Branche par defaut : `main` (protegee)
|
||||||
|
- Travail sur branches features : `<type>/<description-kebab>`
|
||||||
|
- Types : `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `ops`, `sec`
|
||||||
|
- Duree de vie max d'une branche : **3 jours** (rebase ou drop si plus vieux)
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
- `feat/saisie-heures-mobile`
|
||||||
|
- `fix/baserow-rollup-cache`
|
||||||
|
- `docs/update-merise-mcd`
|
||||||
|
|
||||||
|
### 3. Commits
|
||||||
|
|
||||||
|
Format : `<type>(<scope>): <description>`
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
- `feat(bridge): add formateur mention tiptap node`
|
||||||
|
- `fix(baserow): correct rollup cache invalidation on annulation`
|
||||||
|
- `ops(ci): add SAST scan with semgrep`
|
||||||
|
- `sec(deps): bump postgres to 16.4 for CVE-2026-XXXX`
|
||||||
|
|
||||||
|
Regles :
|
||||||
|
- **Pas d'emoji** dans les commits (regle Acadenice)
|
||||||
|
- Description en anglais, concise, imperative
|
||||||
|
- Explique le WHY si non-evident dans le body
|
||||||
|
|
||||||
|
### 4. Pull request
|
||||||
|
|
||||||
|
- 1 PR = 1 sujet (pas de melange feat + fix)
|
||||||
|
- Squash merge vers main (un commit propre par PR)
|
||||||
|
- Required reviews : minimum 1 approval
|
||||||
|
- CI obligatoire (lint + type-check + test + security)
|
||||||
|
|
||||||
|
Template PR genere automatiquement (voir `.github/PULL_REQUEST_TEMPLATE.md`).
|
||||||
|
|
||||||
|
### 5. Tests obligatoires
|
||||||
|
|
||||||
|
Pour toute modification de `bridge/src/`, ajouter ou mettre a jour les tests :
|
||||||
|
- Unit tests : `bridge/tests/unit/`
|
||||||
|
- Integration tests : `bridge/tests/integration/`
|
||||||
|
|
||||||
|
Coverage minimum :
|
||||||
|
- 80% sur `bridge/src/domain/` et `bridge/src/lib/`
|
||||||
|
- 70% global
|
||||||
|
|
||||||
|
Run local :
|
||||||
|
```bash
|
||||||
|
cd bridge
|
||||||
|
npm test
|
||||||
|
npm run test:coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Lint et format
|
||||||
|
|
||||||
|
Outil : **Biome** (lint + format en un)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd bridge
|
||||||
|
npx biome check --write . # auto-fix
|
||||||
|
npx biome ci . # verification CI
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Docs
|
||||||
|
|
||||||
|
Si ta modif change le comportement metier ou l'API :
|
||||||
|
- Mettre a jour le doc concerne dans `docs/`
|
||||||
|
- Push aussi sur Outline (cf `docs/00-readme...` pour la convention)
|
||||||
|
- Mentionner dans `CHANGELOG.md` section `[Unreleased]`
|
||||||
|
|
||||||
|
## Quality gates (CI)
|
||||||
|
|
||||||
|
Checks bloquants pour merge :
|
||||||
|
|
||||||
|
- [ ] Lint Biome vert
|
||||||
|
- [ ] Type-check TypeScript vert
|
||||||
|
- [ ] Tests unitaires verts
|
||||||
|
- [ ] Tests integration verts
|
||||||
|
- [ ] Coverage minimum atteint
|
||||||
|
- [ ] Secret scanning (TruffleHog) zero hit
|
||||||
|
- [ ] SAST (Semgrep) zero `error`
|
||||||
|
- [ ] Dependency check (npm audit) zero `high`/`critical`
|
||||||
|
- [ ] Docker build OK
|
||||||
|
- [ ] Review humaine 1+ approval
|
||||||
|
|
||||||
|
## Conventions code
|
||||||
|
|
||||||
|
- TypeScript strict mode obligatoire
|
||||||
|
- Pas de `any` sans justification dans un commentaire
|
||||||
|
- Naming : camelCase pour vars/fonctions, PascalCase pour classes/types
|
||||||
|
- Imports : tries (Biome auto)
|
||||||
|
- Pas de console.log en prod (utiliser le logger Pino)
|
||||||
|
- Pas d'emoji dans le code, commits, ou specs (Mantra Acadenice IA-23)
|
||||||
|
- Code auto-documente, commentaires uniquement pour le POURQUOI (Mantra IA-24)
|
||||||
|
|
||||||
|
## Methodologie
|
||||||
|
|
||||||
|
`formation-hub` suit Merise Agile + 64 mantras BYAN. Voir `docs/04-cahier-des-charges-techniques.md` et `docs/03-decision-record.md` pour les decisions architecturales.
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
- Ouvre une issue sur GitHub avec le label `question`
|
||||||
|
- Ou ping `@corentin` ou `@yan` directement
|
||||||
661
LICENSE
Normal file
661
LICENSE
Normal file
|
|
@ -0,0 +1,661 @@
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
82
Makefile
Normal file
82
Makefile
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
.PHONY: help up down restart logs ps clean reset shell-docmost shell-baserow backup backup-docmost backup-baserow status
|
||||||
|
|
||||||
|
DATE := $(shell date +%Y%m%d-%H%M%S)
|
||||||
|
BACKUP_DIR := ./backups
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "formation-hub — commandes disponibles"
|
||||||
|
@echo ""
|
||||||
|
@echo "Stack lifecycle:"
|
||||||
|
@echo " make up Demarre la stack en background"
|
||||||
|
@echo " make down Stoppe la stack (conserve les volumes)"
|
||||||
|
@echo " make restart Redemarre tous les services"
|
||||||
|
@echo " make logs Suit les logs (Ctrl+C pour quitter)"
|
||||||
|
@echo " make ps Liste les services et leur etat"
|
||||||
|
@echo " make status Healthcheck rapide des endpoints HTTP"
|
||||||
|
@echo ""
|
||||||
|
@echo "Acces shell:"
|
||||||
|
@echo " make shell-docmost Shell dans le container Docmost"
|
||||||
|
@echo " make shell-baserow Shell dans le container Baserow"
|
||||||
|
@echo ""
|
||||||
|
@echo "Sauvegardes:"
|
||||||
|
@echo " make backup Backup complet (Docmost + Baserow)"
|
||||||
|
@echo " make backup-docmost Backup uniquement Docmost (pg_dump + files)"
|
||||||
|
@echo " make backup-baserow Backup uniquement Baserow (data dir)"
|
||||||
|
@echo ""
|
||||||
|
@echo "DESTRUCTIF:"
|
||||||
|
@echo " make clean Stoppe ET supprime les volumes (ATTENTION)"
|
||||||
|
@echo " make reset clean + up (reset complet)"
|
||||||
|
|
||||||
|
up:
|
||||||
|
@test -f .env || (echo "ERREUR: .env manquant. Copier .env.example vers .env et editer." && exit 1)
|
||||||
|
docker compose up -d
|
||||||
|
@echo ""
|
||||||
|
@echo "Stack demarree :"
|
||||||
|
@echo " Docmost : $${DOCMOST_URL:-http://localhost:3000}"
|
||||||
|
@echo " Baserow : $${BASEROW_URL:-http://localhost:8080}"
|
||||||
|
|
||||||
|
down:
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
restart:
|
||||||
|
docker compose restart
|
||||||
|
|
||||||
|
logs:
|
||||||
|
docker compose logs -f --tail=100
|
||||||
|
|
||||||
|
ps:
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
status:
|
||||||
|
@echo "Docmost :" && curl -sf -o /dev/null -w " HTTP %{http_code} en %{time_total}s\n" http://localhost:3000 || echo " KO"
|
||||||
|
@echo "Baserow :" && curl -sf -o /dev/null -w " HTTP %{http_code} en %{time_total}s\n" http://localhost:8080 || echo " KO"
|
||||||
|
|
||||||
|
shell-docmost:
|
||||||
|
docker compose exec docmost sh
|
||||||
|
|
||||||
|
shell-baserow:
|
||||||
|
docker compose exec baserow bash
|
||||||
|
|
||||||
|
backup: backup-docmost backup-baserow
|
||||||
|
@echo "Backup complet termine dans $(BACKUP_DIR)/"
|
||||||
|
|
||||||
|
backup-docmost:
|
||||||
|
@mkdir -p $(BACKUP_DIR)
|
||||||
|
@echo "Backup Docmost (pg_dump + files)..."
|
||||||
|
docker compose exec -T docmost-db pg_dump -U docmost docmost | gzip > $(BACKUP_DIR)/docmost-db-$(DATE).sql.gz
|
||||||
|
docker compose exec -T docmost tar czf - /app/data/storage > $(BACKUP_DIR)/docmost-files-$(DATE).tar.gz
|
||||||
|
@echo " -> $(BACKUP_DIR)/docmost-db-$(DATE).sql.gz"
|
||||||
|
@echo " -> $(BACKUP_DIR)/docmost-files-$(DATE).tar.gz"
|
||||||
|
|
||||||
|
backup-baserow:
|
||||||
|
@mkdir -p $(BACKUP_DIR)
|
||||||
|
@echo "Backup Baserow (data dir)..."
|
||||||
|
docker compose exec -T baserow tar czf - /baserow/data > $(BACKUP_DIR)/baserow-$(DATE).tar.gz
|
||||||
|
@echo " -> $(BACKUP_DIR)/baserow-$(DATE).tar.gz"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "ATTENTION: cette commande supprime TOUS les volumes (donnees perdues)."
|
||||||
|
@read -p "Tapez 'oui' pour confirmer: " confirm; [ "$$confirm" = "oui" ] || exit 1
|
||||||
|
docker compose down -v
|
||||||
|
|
||||||
|
reset: clean up
|
||||||
79
README.md
Normal file
79
README.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# formation-hub
|
||||||
|
|
||||||
|
Notion-like self-host pour **Acadenice** (CFA + Agence dev + Operations) : wiki collaboratif + bases de donnees structurees (suivi heures formation + projets clients agence + capacite par personne).
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Composant | Role | License |
|
||||||
|
|-----------|------|---------|
|
||||||
|
| **Docmost** | Wiki collaboratif, spaces, share links, **diagrammes natifs** (Mermaid + Draw.io + Excalidraw depuis v0.3.0) | AGPL-3.0 |
|
||||||
|
| **Baserow** | Bases de donnees typees (relations, rollups, formules, multi-vues) | MIT (core) |
|
||||||
|
| **bridge** (Phase 2) | Service Node TS qui expose Baserow comme nodes Tiptap dans Docmost | MIT |
|
||||||
|
|
||||||
|
## Diagrammes
|
||||||
|
|
||||||
|
Docmost embarque nativement trois moteurs de diagrammes — zero config, zero dev :
|
||||||
|
|
||||||
|
- **Mermaid** : diagrammes en syntaxe markdown (flowchart, sequence, ER, gantt, classe, state, journey...). Versionnable comme du code.
|
||||||
|
- **Draw.io** : editeur visuel complet pour archi technique, BPMN, infra. Stocke en SVG attachment.
|
||||||
|
- **Excalidraw** : whiteboard hand-drawn pour brainstorming, schemas pedagogiques, sketches. Stocke en SVG attachment.
|
||||||
|
|
||||||
|
Le MCD du projet (`docs/06-merise-mcd.md`) utilise un diagramme **Mermaid ER**. Ouvre-le dans Docmost ou Outline pour le rendu visuel.
|
||||||
|
|
||||||
|
## Etat actuel (au 2026-05-07)
|
||||||
|
|
||||||
|
Phase 0 — Conception :
|
||||||
|
- [x] Discovery + scope etendu CFA + Agence approuve
|
||||||
|
- [x] ADR + CDC technique
|
||||||
|
- [x] Data dictionary, MCD, MLD, MCT, MOT, state diagrams, use cases, class diagram, activity diagrams
|
||||||
|
- [x] Repo structure & GitOps (CI/CD, SecOps, environnements)
|
||||||
|
- [x] Stack Docker compose locale (vanilla, sans bridge)
|
||||||
|
- [ ] MPD Baserow (table-par-table)
|
||||||
|
- [ ] Plan de tests
|
||||||
|
- [ ] Plan de deployment + CI/CD prets
|
||||||
|
- [ ] Plan d'operations
|
||||||
|
|
||||||
|
Phase 1+ : voir `docs/04-cahier-des-charges-techniques.md` section roadmap.
|
||||||
|
|
||||||
|
## Demarrage local
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# editer .env avec des secrets reels
|
||||||
|
make up
|
||||||
|
```
|
||||||
|
|
||||||
|
- Docmost : http://localhost:3000
|
||||||
|
- Baserow : http://localhost:8080
|
||||||
|
|
||||||
|
Premier lancement : creer un compte admin Docmost et Baserow via l'UI.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Numerotation **logique** : pourquoi → quoi → comment (concept) → comment (logique) → comment (physique) → comment (ops).
|
||||||
|
|
||||||
|
| # | Doc | Theme |
|
||||||
|
|---|-----|-------|
|
||||||
|
| 01 | `docs/01-discovery-recap.md` | Pourquoi (vision/contexte) |
|
||||||
|
| 02 | `docs/02-scope-etendu-cfa-agence.md` | Quoi (perimetre approuve) |
|
||||||
|
| 03 | `docs/03-decision-record.md` | Choix structurels (ADR) |
|
||||||
|
| 04 | `docs/04-cahier-des-charges-techniques.md` | CDC technique (stack, NFR, roadmap) |
|
||||||
|
| 05 | `docs/05-data-dictionary.md` | Donnees — vocabulaire |
|
||||||
|
| 06 | `docs/06-merise-mcd.md` | Donnees — concept (ER, cardinalites) |
|
||||||
|
| 07 | `docs/07-merise-mld.md` | Donnees — logique (schema relationnel) |
|
||||||
|
| 08 | `docs/08-merise-mct.md` | Traitements — concept |
|
||||||
|
| 09 | `docs/09-merise-mot.md` | Traitements — organisation (qui/quand/outil) |
|
||||||
|
| 10 | `docs/10-state-diagrams.md` | Comportement — cycle de vie |
|
||||||
|
| 11 | `docs/11-uml-use-cases.md` | Comportement — interactions |
|
||||||
|
| 12 | `docs/12-uml-class-diagram.md` | Comportement — code OO |
|
||||||
|
| 13 | `docs/13-uml-activity-diagrams.md` | Comportement — workflows complets |
|
||||||
|
| 14 | `docs/14-repo-structure-gitops.md` | Code — arborescence + CI/CD + SecOps |
|
||||||
|
| 15 | `docs/15-baserow-mpd.md` | Implementation — Baserow concret (table par table, formules, vues) |
|
||||||
|
| 16 | `docs/16-plan-tests.md` | Qualite — pyramide tests, outils, coverage, acceptance |
|
||||||
|
| 17 | `docs/17-plan-deployment.md` | Ops — provisionnement, CI/CD detaille, releases, migrations, rollback |
|
||||||
|
| 18 | `docs/18-plan-operations.md` | Ops — monitoring, alerting, backups DR, runbooks, capacity |
|
||||||
|
| 19 | `docs/19-bridge-api-design.md` | Bridge API — endpoints, auth, webhooks, cache, integration Tiptap |
|
||||||
|
|
||||||
|
## Methodologie
|
||||||
|
|
||||||
|
Merise Agile + 64 mantras BYAN. Data Dictionary First, MCD/MCT cross-validation, Ockham razor sur le scope, zero emoji dans le code et les commits.
|
||||||
57
SECURITY.md
Normal file
57
SECURITY.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Acadenice prend la securite tres au serieux. Si tu decouvres une vulnerabilite dans `formation-hub`, **ne pas l'ouvrir en issue publique**.
|
||||||
|
|
||||||
|
Contacte directement : **security@acadenice.fr**
|
||||||
|
|
||||||
|
Inclure :
|
||||||
|
- Description de la vulnerabilite
|
||||||
|
- Etapes pour reproduire
|
||||||
|
- Impact estime
|
||||||
|
- Version / commit SHA concerne
|
||||||
|
- Ta contact info pour la reponse coordonnee
|
||||||
|
|
||||||
|
## Reponse
|
||||||
|
|
||||||
|
| Etape | Delai cible |
|
||||||
|
|-------|-------------|
|
||||||
|
| Accuse de reception | < 48h ouvrees |
|
||||||
|
| Triage initial | < 5j ouvres |
|
||||||
|
| Patch developpe | depend severite (CVSS) |
|
||||||
|
| Disclosure publique | apres patch deploye en prod, embargo coordonne |
|
||||||
|
|
||||||
|
CVE assignee si vulnerabilite serieuse, et publication sur GitHub Security Advisories.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
| In scope | Out of scope |
|
||||||
|
|----------|--------------|
|
||||||
|
| Code custom du bridge service (`bridge/`) | Vulnerabilites Docmost upstream (reporter chez eux) |
|
||||||
|
| Configurations infra/CI (`compose*.yml`, `.github/`) | Vulnerabilites Baserow upstream (reporter chez eux) |
|
||||||
|
| Scripts ops (`scripts/`) | Vulnerabilites Postgres/Redis/Traefik (reporter chez les vendors) |
|
||||||
|
| Schemas et formules Baserow customs (`baserow/`) | Vulnerabilites browsers / OS |
|
||||||
|
|
||||||
|
## Classification severites (CVSS-like)
|
||||||
|
|
||||||
|
- **CRITICAL** : RCE, data leak massive, auth bypass — patch < 24h
|
||||||
|
- **HIGH** : escalade privileges, data leak partiel — patch < 7j
|
||||||
|
- **MEDIUM** : DoS, info disclosure non-sensible — patch < 30j
|
||||||
|
- **LOW** : best-practice deviation, low-impact — next release
|
||||||
|
|
||||||
|
## Bonnes pratiques
|
||||||
|
|
||||||
|
Avant de signaler, verifie :
|
||||||
|
- Ton .env n'est pas commit
|
||||||
|
- Ton API token n'est pas expose en clair quelque part
|
||||||
|
- Tu as la derniere version de Docker / Docmost / Baserow
|
||||||
|
|
||||||
|
## Hall of fame
|
||||||
|
|
||||||
|
Liste des reporters (avec leur permission) :
|
||||||
|
- (vide pour l'instant)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Cette politique est applicable au repo `AcadeNice/wiki`. Voir `LICENSE` pour les conditions de redistribution.
|
||||||
31
bridge/.env.example
Normal file
31
bridge/.env.example
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Bridge service — variables d'environnement
|
||||||
|
# Copier vers .env et remplir avec valeurs reelles.
|
||||||
|
|
||||||
|
# Server
|
||||||
|
NODE_ENV=development
|
||||||
|
PORT=4000
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
# Baserow API
|
||||||
|
BASEROW_API_URL=http://baserow:80/api
|
||||||
|
BASEROW_API_TOKEN=
|
||||||
|
|
||||||
|
# Docmost API
|
||||||
|
DOCMOST_API_URL=http://docmost:3000/api
|
||||||
|
DOCMOST_API_TOKEN=
|
||||||
|
|
||||||
|
# Redis (cache + idempotence webhooks)
|
||||||
|
REDIS_URL=redis://docmost-redis:6379
|
||||||
|
|
||||||
|
# Webhooks Baserow signature secret (HMAC-SHA256)
|
||||||
|
BASEROW_WEBHOOK_SECRET=
|
||||||
|
|
||||||
|
# Auth tokens bridge (CSV des tokens valides + scopes — Phase 2 simple)
|
||||||
|
# Format: token1:scope1,scope2;token2:scope3
|
||||||
|
# Phase 3 : migration vers DB dediee
|
||||||
|
BRIDGE_API_TOKENS=
|
||||||
|
|
||||||
|
# Rate limiting (par token + endpoint)
|
||||||
|
RATE_LIMIT_READ_PER_MIN=600
|
||||||
|
RATE_LIMIT_WRITE_PER_MIN=60
|
||||||
|
RATE_LIMIT_WEBHOOK_PER_MIN=1000
|
||||||
9
bridge/.gitignore
vendored
Normal file
9
bridge/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.vitest/
|
||||||
40
bridge/Dockerfile
Normal file
40
bridge/Dockerfile
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Bridge service — multi-stage build
|
||||||
|
# Image finale : node 22 alpine, ~80 Mo
|
||||||
|
|
||||||
|
ARG NODE_VERSION=22-alpine
|
||||||
|
|
||||||
|
# --- Stage 1 : deps ---
|
||||||
|
FROM node:${NODE_VERSION} AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --only=production && npm cache clean --force
|
||||||
|
|
||||||
|
# --- Stage 2 : build ---
|
||||||
|
FROM node:${NODE_VERSION} AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
COPY src ./src
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Stage 3 : runtime ---
|
||||||
|
FROM node:${NODE_VERSION} AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Non-root user
|
||||||
|
RUN addgroup -g 1001 -S bridge && adduser -S bridge -u 1001
|
||||||
|
USER bridge
|
||||||
|
|
||||||
|
COPY --from=deps --chown=bridge:bridge /app/node_modules ./node_modules
|
||||||
|
COPY --from=build --chown=bridge:bridge /app/dist ./dist
|
||||||
|
COPY --chown=bridge:bridge package.json ./
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=4000
|
||||||
|
EXPOSE 4000
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD wget --quiet --tries=1 --spider http://localhost:4000/api/health || exit 1
|
||||||
|
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
43
bridge/biome.json
Normal file
43
bridge/biome.json
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": true,
|
||||||
|
"ignore": ["dist", "node_modules", "coverage"]
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedImports": "error",
|
||||||
|
"noUnusedVariables": "error"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"useConst": "error",
|
||||||
|
"useTemplate": "warn"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "warn",
|
||||||
|
"noConsoleLog": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 100,
|
||||||
|
"lineEnding": "lf"
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"trailingCommas": "all",
|
||||||
|
"semicolons": "always",
|
||||||
|
"arrowParentheses": "always"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
bridge/package.json
Normal file
43
bridge/package.json
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "@acadenice/bridge",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Bridge service Acadenice formation-hub — expose Baserow comme nodes Tiptap dans Docmost",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/index.ts",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "biome ci .",
|
||||||
|
"lint:fix": "biome check --write .",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:unit": "vitest run tests/unit",
|
||||||
|
"test:integration": "vitest run tests/integration",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/node-server": "^1.13.0",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"hono": "^4.6.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
|
"ofetch": "^1.4.0",
|
||||||
|
"pino": "^9.5.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.9.0",
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@vitest/coverage-v8": "^2.1.0",
|
||||||
|
"testcontainers": "^10.13.0",
|
||||||
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.6.0",
|
||||||
|
"vitest": "^2.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
bridge/src/index.ts
Normal file
32
bridge/src/index.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { serve } from '@hono/node-server';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { logger as honoLogger } from 'hono/logger';
|
||||||
|
import { loadConfig } from './lib/config.js';
|
||||||
|
import { logger } from './lib/logger.js';
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
app.use('*', honoLogger());
|
||||||
|
|
||||||
|
app.get('/api/health', (c) => {
|
||||||
|
return c.json({ status: 'ok', service: 'bridge', version: '0.1.0' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/ready', async (c) => {
|
||||||
|
return c.json({ status: 'ok', dependencies: { baserow: 'TODO', redis: 'TODO' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.notFound((c) => c.json({ error: { code: 'NOT_FOUND', message: 'Route not found' } }, 404));
|
||||||
|
|
||||||
|
app.onError((err, c) => {
|
||||||
|
logger.error({ err }, 'Unhandled error');
|
||||||
|
return c.json(
|
||||||
|
{ error: { code: 'INTERNAL', message: 'Internal server error' } },
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
serve({ fetch: app.fetch, port: config.port }, (info) => {
|
||||||
|
logger.info({ port: info.port, env: config.nodeEnv }, 'Bridge service started');
|
||||||
|
});
|
||||||
41
bridge/src/lib/config.ts
Normal file
41
bridge/src/lib/config.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { config as loadDotenv } from 'dotenv';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
loadDotenv();
|
||||||
|
|
||||||
|
const ConfigSchema = z.object({
|
||||||
|
nodeEnv: z.enum(['development', 'test', 'staging', 'production']).default('development'),
|
||||||
|
port: z.coerce.number().int().positive().default(4000),
|
||||||
|
logLevel: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'),
|
||||||
|
baserowApiUrl: z.string().url(),
|
||||||
|
baserowApiToken: z.string().min(1),
|
||||||
|
docmostApiUrl: z.string().url().optional(),
|
||||||
|
docmostApiToken: z.string().optional(),
|
||||||
|
redisUrl: z.string().url(),
|
||||||
|
baserowWebhookSecret: z.string().min(16, 'webhook secret must be >= 16 chars'),
|
||||||
|
bridgeApiTokens: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Config = z.infer<typeof ConfigSchema>;
|
||||||
|
|
||||||
|
export function loadConfig(): Config {
|
||||||
|
const parsed = ConfigSchema.safeParse({
|
||||||
|
nodeEnv: process.env.NODE_ENV,
|
||||||
|
port: process.env.PORT,
|
||||||
|
logLevel: process.env.LOG_LEVEL,
|
||||||
|
baserowApiUrl: process.env.BASEROW_API_URL,
|
||||||
|
baserowApiToken: process.env.BASEROW_API_TOKEN,
|
||||||
|
docmostApiUrl: process.env.DOCMOST_API_URL,
|
||||||
|
docmostApiToken: process.env.DOCMOST_API_TOKEN,
|
||||||
|
redisUrl: process.env.REDIS_URL,
|
||||||
|
baserowWebhookSecret: process.env.BASEROW_WEBHOOK_SECRET,
|
||||||
|
bridgeApiTokens: process.env.BRIDGE_API_TOKENS,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
const issues = parsed.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\n');
|
||||||
|
throw new Error(`Invalid configuration:\n${issues}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
23
bridge/src/lib/logger.ts
Normal file
23
bridge/src/lib/logger.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import pino from 'pino';
|
||||||
|
|
||||||
|
export const logger = pino({
|
||||||
|
level: process.env.LOG_LEVEL ?? 'info',
|
||||||
|
transport:
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? {
|
||||||
|
target: 'pino-pretty',
|
||||||
|
options: { colorize: true, translateTime: 'HH:MM:ss', ignore: 'pid,hostname' },
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
redact: {
|
||||||
|
paths: [
|
||||||
|
'*.password',
|
||||||
|
'*.token',
|
||||||
|
'*.secret',
|
||||||
|
'*.authorization',
|
||||||
|
'req.headers.authorization',
|
||||||
|
'req.headers["x-baserow-signature"]',
|
||||||
|
],
|
||||||
|
censor: '[REDACTED]',
|
||||||
|
},
|
||||||
|
});
|
||||||
0
bridge/tests/.gitkeep
Normal file
0
bridge/tests/.gitkeep
Normal file
25
bridge/tsconfig.json
Normal file
25
bridge/tsconfig.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "tests"]
|
||||||
|
}
|
||||||
65
compose.prod.yml
Normal file
65
compose.prod.yml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# compose.prod.yml — overrides pour env production
|
||||||
|
# Usage : docker compose -f compose.yml -f compose.prod.yml up -d
|
||||||
|
|
||||||
|
services:
|
||||||
|
docmost:
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
APP_URL: ${DOCMOST_URL:?DOCMOST_URL requis sur prod}
|
||||||
|
LOG_LEVEL: warn
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.docmost-prod.rule=Host(`wiki.acadenice.fr`)"
|
||||||
|
- "traefik.http.routers.docmost-prod.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.docmost-prod.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.docmost-prod.loadbalancer.server.port=3000"
|
||||||
|
ports: !reset []
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
reservations:
|
||||||
|
memory: 512M
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
baserow:
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
BASEROW_PUBLIC_URL: ${BASEROW_URL:?BASEROW_URL requis sur prod}
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.baserow-prod.rule=Host(`baserow.acadenice.fr`)"
|
||||||
|
- "traefik.http.routers.baserow-prod.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.baserow-prod.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.baserow-prod.loadbalancer.server.port=80"
|
||||||
|
ports: !reset []
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 3G
|
||||||
|
reservations:
|
||||||
|
memory: 1G
|
||||||
|
|
||||||
|
docmost-db:
|
||||||
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
|
||||||
|
docmost-redis:
|
||||||
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 256M
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
external: true
|
||||||
|
name: traefik
|
||||||
44
compose.staging.yml
Normal file
44
compose.staging.yml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# compose.staging.yml — overrides pour env staging
|
||||||
|
# Usage : docker compose -f compose.yml -f compose.staging.yml up -d
|
||||||
|
|
||||||
|
services:
|
||||||
|
docmost:
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
APP_URL: ${DOCMOST_URL:?DOCMOST_URL requis sur staging}
|
||||||
|
LOG_LEVEL: info
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.docmost-staging.rule=Host(`wiki.staging.acadenice.fr`)"
|
||||||
|
- "traefik.http.routers.docmost-staging.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.docmost-staging.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.docmost-staging.loadbalancer.server.port=3000"
|
||||||
|
ports: !reset []
|
||||||
|
|
||||||
|
baserow:
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
BASEROW_PUBLIC_URL: ${BASEROW_URL:?BASEROW_URL requis sur staging}
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.baserow-staging.rule=Host(`baserow.staging.acadenice.fr`)"
|
||||||
|
- "traefik.http.routers.baserow-staging.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.baserow-staging.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.baserow-staging.loadbalancer.server.port=80"
|
||||||
|
ports: !reset []
|
||||||
|
|
||||||
|
docmost-db:
|
||||||
|
restart: always
|
||||||
|
# Sur staging, on garde un volume persiste mais on accepte un dump regulier en backup
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
|
||||||
|
docmost-redis:
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
external: true
|
||||||
|
name: traefik # network commun avec Traefik (a adapter selon setup)
|
||||||
76
compose.yml
Normal file
76
compose.yml
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
name: formation-hub
|
||||||
|
|
||||||
|
services:
|
||||||
|
docmost-db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: docmost
|
||||||
|
POSTGRES_USER: docmost
|
||||||
|
POSTGRES_PASSWORD: ${DOCMOST_DB_PASSWORD:?DOCMOST_DB_PASSWORD requis}
|
||||||
|
volumes:
|
||||||
|
- docmost-db:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U docmost"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
docmost-redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- docmost-redis:/data
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
|
||||||
|
docmost:
|
||||||
|
image: docmost/docmost:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
docmost-db:
|
||||||
|
condition: service_healthy
|
||||||
|
docmost-redis:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
APP_URL: ${DOCMOST_URL:-http://localhost:3000}
|
||||||
|
APP_SECRET: ${DOCMOST_APP_SECRET:?DOCMOST_APP_SECRET requis (32+ chars)}
|
||||||
|
DATABASE_URL: postgresql://docmost:${DOCMOST_DB_PASSWORD}@docmost-db:5432/docmost
|
||||||
|
REDIS_URL: redis://docmost-redis:6379
|
||||||
|
STORAGE_DRIVER: local
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- docmost-files:/app/data/storage
|
||||||
|
|
||||||
|
baserow:
|
||||||
|
image: baserow/baserow:1.30.1
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
BASEROW_PUBLIC_URL: ${BASEROW_URL:-http://localhost:8080}
|
||||||
|
BASEROW_BACKEND_DEBUG: "false"
|
||||||
|
BASEROW_EMAIL_SMTP: ""
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- baserow-data:/baserow/data
|
||||||
|
|
||||||
|
# bridge:
|
||||||
|
# build: ./bridge
|
||||||
|
# restart: unless-stopped
|
||||||
|
# depends_on:
|
||||||
|
# - baserow
|
||||||
|
# - docmost-redis
|
||||||
|
# environment:
|
||||||
|
# BASEROW_API_URL: http://baserow:80/api
|
||||||
|
# BASEROW_API_TOKEN: ${BASEROW_API_TOKEN}
|
||||||
|
# DOCMOST_API_URL: http://docmost:3000/api
|
||||||
|
# DOCMOST_API_TOKEN: ${DOCMOST_API_TOKEN}
|
||||||
|
# REDIS_URL: redis://docmost-redis:6379
|
||||||
|
# ports:
|
||||||
|
# - "4000:4000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
docmost-db:
|
||||||
|
docmost-redis:
|
||||||
|
docmost-files:
|
||||||
|
baserow-data:
|
||||||
86
docs/01-discovery-recap.md
Normal file
86
docs/01-discovery-recap.md
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Discovery — Recap
|
||||||
|
|
||||||
|
> Synthese de la phase de recherche, projets evalues, blocages identifies, decision finale.
|
||||||
|
> Date : 2026-05-07
|
||||||
|
|
||||||
|
## Contexte metier
|
||||||
|
|
||||||
|
- Centre de formation, ~20 employes (admin + formateurs)
|
||||||
|
- Acces clients ponctuel par lien partage
|
||||||
|
- Etudiants avec espaces personnels libres
|
||||||
|
- Cible : 90-100 utilisateurs total, ~30 simultanes peak
|
||||||
|
|
||||||
|
## Besoin fonctionnel
|
||||||
|
|
||||||
|
1. Wiki collaboratif (SOPs, supports formation, doc interne)
|
||||||
|
2. Bases structurees liees pour le suivi des heures de formation :
|
||||||
|
- Formations (programmes complets)
|
||||||
|
- Blocs (blocs de competences)
|
||||||
|
- Modules (lecons individuelles)
|
||||||
|
- Formateurs (avec capacite annuelle)
|
||||||
|
3. Calculs automatiques d'heures attribuees / restantes par formation, par bloc, par module, par formateur
|
||||||
|
4. Bidirec backlinks dans le wiki
|
||||||
|
5. Editeur dual-mode (WYSIWYG + raw markdown a la Alexandrie Hub)
|
||||||
|
6. Self-host obligatoire, illimite users, AGPL/MIT acceptable
|
||||||
|
|
||||||
|
## Projets OSS evalues
|
||||||
|
|
||||||
|
### Elimines
|
||||||
|
|
||||||
|
| Projet | Raison |
|
||||||
|
|--------|--------|
|
||||||
|
| Notion (cloud) | Pas self-host, prix au seat |
|
||||||
|
| AFFiNE | Self-host limite a 10 seats, Team License $10/seat/mois = ~2200€/an pour 20 users |
|
||||||
|
| AppFlowy | Self-host limite a 1 user + 3 guests free |
|
||||||
|
| Outline (getoutline) | Pas de bidirec backlinks reel + license BSL (restrictions commerciales) |
|
||||||
|
| SiYuan | Excellent dual-mode + bidirec mais conçu single-user, refactor team trop lourd |
|
||||||
|
| TriliumNext | Single-user origine |
|
||||||
|
| Logseq | Outliner Roam-like, stack Clojure rare, DB version en beta |
|
||||||
|
| Anytype | License "Any Source Available" non-OSI, fork commercial = zone grise |
|
||||||
|
| HedgeDoc / BookStack | Pas de bidirec, pas de DBs |
|
||||||
|
| Alexandrie Hub | Dev solo, bus factor de 1, pas viable pour boite. Sert de **reference UX** dual-mode. |
|
||||||
|
|
||||||
|
Sources verifiees :
|
||||||
|
- [AFFiNE 10-seat limit](https://docs.affine.pro/self-host-affine/features/basic-user-quota)
|
||||||
|
- [AppFlowy 1-user limit](https://github.com/AppFlowy-IO/AppFlowy-Cloud/issues/1570)
|
||||||
|
- [Docmost AGPL pricing](https://docmost.com/pricing)
|
||||||
|
- [Outline backlinks doc](https://docs.getoutline.com/s/guide/doc/backlinks-f9YSmlNSkr)
|
||||||
|
|
||||||
|
### Retenus
|
||||||
|
|
||||||
|
| Projet | Role | Pourquoi |
|
||||||
|
|--------|------|----------|
|
||||||
|
| **Docmost** | Wiki collaboratif | AGPL, users illimites self-host, team workspaces + spaces + share links natifs, stack TS/NestJS/React/Tiptap mainstream, ultra-actif (release fin avril 2026) |
|
||||||
|
| **Baserow** | DBs structurees | MIT core, users illimites self-host, multi-vues (table/kanban/calendar/timeline/gallery), formules, rollups, relations, real-time collab |
|
||||||
|
|
||||||
|
## Path retenu — Path B
|
||||||
|
|
||||||
|
Le user a confirme le 2026-05-07 : **on ne reinvente pas la roue**, on utilise Docmost + Baserow tels quels et on construit un **bridge custom** (Node TS) qui :
|
||||||
|
|
||||||
|
- Expose l'API Baserow comme nodes Tiptap inline dans Docmost
|
||||||
|
- Fournit des routes de rendering `/formateur/:id` et similaires comme pages Docmost-style
|
||||||
|
- Cache Redis pour eviter de spam Baserow API
|
||||||
|
|
||||||
|
L'utilisateur final ne doit pas voir l'UI Baserow. Cliquer sur un formateur depuis le wiki = arriver sur une page Docmost qui contient les proprietes (capacite, heures restantes) en haut + zone wiki rich content en bas.
|
||||||
|
|
||||||
|
## Profil dev
|
||||||
|
|
||||||
|
- AdminSys + DevOps solo (Docker + Traefik + scripts ops)
|
||||||
|
- Apprend React + Tiptap au besoin (courbe d'apprentissage acceptee)
|
||||||
|
- Possibilite freelance ponctuel pour Tiptap node-views (~2 jours pair-programming)
|
||||||
|
|
||||||
|
## Manques connus de Docmost
|
||||||
|
|
||||||
|
| Manque | Source | Cout dev estime |
|
||||||
|
|--------|--------|-----------------|
|
||||||
|
| Bidirec backlinks | [issue #1122](https://github.com/docmost/docmost/issues/1122) | 2-4 semaines |
|
||||||
|
| Dual-mode editor (WYSIWYG ↔ raw MD) | aucune doc | 2-3 semaines |
|
||||||
|
| Guest sharing fin (Notion-style) | [discussion #1586](https://github.com/docmost/docmost/discussions/1586) | 1-2 semaines |
|
||||||
|
| DBs Notion-style integrees | non roadmap | **delegue a Baserow + bridge** |
|
||||||
|
|
||||||
|
## Ordre d'attaque
|
||||||
|
|
||||||
|
1. Phase 1 — Stack vanilla locale, schema Baserow, MCD/MCT documentes
|
||||||
|
2. Phase 1 bis — Deploiement staging avec Traefik, CI/CD GitHub Actions
|
||||||
|
3. Phase 2 — Bridge Node TS, premier Tiptap node-view custom, route `/formateur/:id`
|
||||||
|
4. Phase 3 — Bidirec backlinks Docmost, dual-mode editor (selon douleur reelle)
|
||||||
207
docs/02-scope-etendu-cfa-agence.md
Normal file
207
docs/02-scope-etendu-cfa-agence.md
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
# Scope etendu — CFA + Agence + Internes
|
||||||
|
|
||||||
|
> **APPROVED le 2026-05-07 par Corentin** — Option B retenue (CFA + Agence via PERSONNE pivot).
|
||||||
|
> **Etudiants : non modelises en Baserow**, juste users Docmost avec spaces libres (heritent des templates et integrations qu'on construit).
|
||||||
|
> Refacto des docs Merise effectue dans la foulee.
|
||||||
|
> Date : 2026-05-07.
|
||||||
|
> Source : Document Fondateur Acadenice (KxvipVcxNV) lu sur le wiki.
|
||||||
|
|
||||||
|
## 1. Constat
|
||||||
|
|
||||||
|
Mon modele initial couvre uniquement le **CFA** (formations / blocs / modules / formateurs / heures). C'est partiel.
|
||||||
|
|
||||||
|
Acadenice est en realite **trois activites coordonnees** :
|
||||||
|
|
||||||
|
| Activite | Description | Entites cles |
|
||||||
|
|----------|-------------|--------------|
|
||||||
|
| **CFA** (Centre de Formation des Apprentis) | Formation : dev, graphisme, marketing, IoT, cybersec. Max 15 etudiants/classe. | FORMATION, BLOC, MODULE, FORMATEUR, ETUDIANT, INSCRIPTION |
|
||||||
|
| **AGENCE dev** | Developpement de projets pour clients reels. Les formateurs y bossent en parallele de leurs cours. | CLIENT, PROJET, TACHE, SITE_WEB, SERVEUR, INTERVENTION |
|
||||||
|
| **OPERATIONS internes** | RH, comm, batiment, evenements, vision strategique. | SALARIE, EVENEMENT, COMMUNICATION |
|
||||||
|
|
||||||
|
**Le lien clef** : un FORMATEUR est souvent aussi DEVELOPPEUR sur projets agence. Sa capacite annuelle se split entre les deux activites.
|
||||||
|
|
||||||
|
C'est ce qui fait l'ADN d'Acadenice (cf doc fondateur) — pas un detail qu'on peut ignorer.
|
||||||
|
|
||||||
|
## 2. Question structurante
|
||||||
|
|
||||||
|
**Le projet "formation-hub" doit-il modeliser :**
|
||||||
|
|
||||||
|
- **Option A — CFA only** (mon modele actuel) : on reste sur formation/heures formateurs. L'Agence et les Operations ont leurs propres outils (Linear/Notion/etc) ou ne sont pas modelises.
|
||||||
|
- **Option B — CFA + Agence unifies** : on ajoute le suivi projets clients. La capacite Formateur-Dev est tracee unifiee. Vue 360 d'une personne (cours + projets).
|
||||||
|
- **Option C — Toute l'organisation** : CFA + Agence + Internes. Outil ERP-leger complet.
|
||||||
|
|
||||||
|
Mon vote : **Option B**. Justification :
|
||||||
|
- Le lien CFA-Agence est central a la vision Acadenice (cf doc fondateur, section "Le lien agence-formation"). Le modeliser = capter la vraie valeur.
|
||||||
|
- L'Option C ajoute beaucoup de scope (RH, batiment, comm) qui ne tirent pas le projet vers ses objectifs immediats.
|
||||||
|
- L'Option A laisse de la valeur sur la table — la capacite Formateur-Dev est un differentiateur metier reel.
|
||||||
|
|
||||||
|
## 3. Extension du modele propose (Option B)
|
||||||
|
|
||||||
|
### 3.1 Nouvelle entite pivot : PERSONNE
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Personne {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+string prenom
|
||||||
|
+Email email
|
||||||
|
+decimal capacite_annuelle_totale
|
||||||
|
+decimal split_formation_pct
|
||||||
|
+decimal split_agence_pct
|
||||||
|
+Statut statut
|
||||||
|
}
|
||||||
|
class RoleFormateur {
|
||||||
|
+decimal heures_attribuees_formation
|
||||||
|
+decimal heures_restantes_formation
|
||||||
|
}
|
||||||
|
class RoleDeveloppeur {
|
||||||
|
+decimal heures_attribuees_agence
|
||||||
|
+decimal heures_restantes_agence
|
||||||
|
}
|
||||||
|
class RoleAdmin {
|
||||||
|
+permissions[]
|
||||||
|
}
|
||||||
|
class RoleEtudiant {
|
||||||
|
+Date date_inscription
|
||||||
|
+Formation formation_courante
|
||||||
|
}
|
||||||
|
|
||||||
|
Personne "1" --> "0..*" RoleFormateur
|
||||||
|
Personne "1" --> "0..*" RoleDeveloppeur
|
||||||
|
Personne "1" --> "0..*" RoleAdmin
|
||||||
|
Personne "1" --> "0..*" RoleEtudiant
|
||||||
|
```
|
||||||
|
|
||||||
|
Une PERSONNE peut cumuler plusieurs roles. La capacite annuelle est splittee entre formation et agence selon un pourcentage configurable.
|
||||||
|
|
||||||
|
### 3.2 Nouvelles entites Agence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
CLIENT ||--o{ PROJET : "1,N a"
|
||||||
|
PROJET ||--o{ TACHE : "1,N comporte"
|
||||||
|
TACHE ||--o{ INTERVENTION : "0,N realisee via"
|
||||||
|
PERSONNE ||--o{ INTERVENTION : "0,N realise"
|
||||||
|
PROJET }o--o{ SITE_WEB : "0,N livre"
|
||||||
|
PROJET }o--o{ SERVEUR : "0,N deploie"
|
||||||
|
|
||||||
|
CLIENT {
|
||||||
|
int client_id PK
|
||||||
|
string client_nom UNIQUE
|
||||||
|
string client_contact_email
|
||||||
|
string client_telephone
|
||||||
|
text client_notes
|
||||||
|
enum client_statut "prospect|actif|inactif|archive"
|
||||||
|
}
|
||||||
|
PROJET {
|
||||||
|
int projet_id PK
|
||||||
|
int projet_client_id FK
|
||||||
|
string projet_nom
|
||||||
|
decimal projet_charge_heures
|
||||||
|
decimal projet_heures_realisees "rollup"
|
||||||
|
date projet_date_debut
|
||||||
|
date projet_date_fin_prevue
|
||||||
|
enum projet_statut "devis|en_cours|livre|cloture|abandonne"
|
||||||
|
}
|
||||||
|
TACHE {
|
||||||
|
int tache_id PK
|
||||||
|
int tache_projet_id FK
|
||||||
|
string tache_titre
|
||||||
|
decimal tache_charge_heures
|
||||||
|
decimal tache_heures_realisees "rollup"
|
||||||
|
enum tache_statut "todo|in_progress|done|abandoned"
|
||||||
|
}
|
||||||
|
INTERVENTION {
|
||||||
|
int intervention_id PK
|
||||||
|
int intervention_tache_id FK
|
||||||
|
int intervention_personne_id FK
|
||||||
|
decimal intervention_heures
|
||||||
|
date intervention_date
|
||||||
|
text intervention_notes
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Modele global combine (CFA + Agence + Personne pivot)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
PERSONNE ||--o{ ATTRIBUTION : "FORMATEUR : enseigne"
|
||||||
|
PERSONNE ||--o{ INTERVENTION : "DEVELOPPEUR : realise"
|
||||||
|
PERSONNE ||--o{ INSCRIPTION : "ETUDIANT : suit"
|
||||||
|
|
||||||
|
FORMATION ||--o{ BLOC : "contient"
|
||||||
|
BLOC ||--o{ MODULE : "comprend"
|
||||||
|
MODULE ||--o{ ATTRIBUTION : "attribue"
|
||||||
|
|
||||||
|
CLIENT ||--o{ PROJET : "a"
|
||||||
|
PROJET ||--o{ TACHE : "comporte"
|
||||||
|
TACHE ||--o{ INTERVENTION : "realisee"
|
||||||
|
|
||||||
|
FORMATION ||--o{ INSCRIPTION : "regroupe etudiants"
|
||||||
|
|
||||||
|
PROJET }o--o{ FORMATION : "lien optionnel : projet pedagogique"
|
||||||
|
```
|
||||||
|
|
||||||
|
Le **lien optionnel PROJET ↔ FORMATION** capte le cas Acadenice ou des etudiants travaillent sur de vrais projets clients comme exercice pedagogique.
|
||||||
|
|
||||||
|
### 3.4 Capacite cumulee Personne
|
||||||
|
|
||||||
|
Pour une PERSONNE qui a plusieurs roles :
|
||||||
|
|
||||||
|
```
|
||||||
|
Personne.heures_attribuees_total =
|
||||||
|
SUM(ATTRIBUTION.heures_attribuees) WHERE attribution_personne_id = personne_id
|
||||||
|
+ SUM(INTERVENTION.heures) WHERE intervention_personne_id = personne_id
|
||||||
|
|
||||||
|
Personne.heures_restantes =
|
||||||
|
Personne.capacite_annuelle_totale - Personne.heures_attribuees_total
|
||||||
|
```
|
||||||
|
|
||||||
|
L'admin voit en un coup d'oeil sur la fiche d'un formateur-dev :
|
||||||
|
- Cours attribues : 400h
|
||||||
|
- Projets attribues : 600h
|
||||||
|
- Capacite totale : 1500h
|
||||||
|
- Restant : 500h
|
||||||
|
|
||||||
|
## 4. Impact sur les docs existants
|
||||||
|
|
||||||
|
| Doc | Impact | Effort refacto |
|
||||||
|
|-----|--------|---------------|
|
||||||
|
| 01 - Discovery | Mise a jour scope | 15 min |
|
||||||
|
| 02 - Decision Records | Ajouter ADR-006 sur l'extension scope | 15 min |
|
||||||
|
| 03 - Data Dictionary | Ajouter PERSONNE, CLIENT, PROJET, TACHE, INTERVENTION, ETUDIANT, INSCRIPTION | 1h |
|
||||||
|
| 04 - MCD | Etendre ER diagram, ajouter cardinalites | 30 min |
|
||||||
|
| 05 - MLD | Ajouter tables + FK | 30 min |
|
||||||
|
| 06 - UML Use Cases | Ajouter acteurs Developpeur, Client, et leurs UC | 30 min |
|
||||||
|
| 07 - State Diagrams | Ajouter cycle CLIENT, PROJET, TACHE | 30 min |
|
||||||
|
| 08 - MCT | Ajouter operations Agence | 1h |
|
||||||
|
| 09 - MOT | Ajouter ligne pour les ops Agence | 30 min |
|
||||||
|
| 10 - Class Diagram | Ajouter classes Personne, Client, Projet, Tache, Intervention | 30 min |
|
||||||
|
| 11 - Activity Diagrams | Detailler AD-04 et AD-05 | 30 min |
|
||||||
|
|
||||||
|
**Effort refacto total : ~6-7h** si Option B validee.
|
||||||
|
|
||||||
|
## 5. Impact sur l'implementation
|
||||||
|
|
||||||
|
Stack reste identique (Docmost + Baserow + bridge). Mais :
|
||||||
|
|
||||||
|
- Plus de tables Baserow (12 au lieu de 5)
|
||||||
|
- Bridge service plus important (gere la vue unifiee Personne)
|
||||||
|
- UI : tableaux de bord par personne (cours + projets en parallele)
|
||||||
|
|
||||||
|
Capacite estime ajout dev sur Phase 2 : **+2-3 semaines** par rapport au scope CFA-only.
|
||||||
|
|
||||||
|
## 6. Question pour validation
|
||||||
|
|
||||||
|
**Tu confirmes Option B ?**
|
||||||
|
|
||||||
|
- [ ] Option A — CFA only (mon modele actuel reste)
|
||||||
|
- [ ] **Option B — CFA + Agence (extension via PERSONNE pivot)** — ma recommandation
|
||||||
|
- [ ] Option C — Tout (CFA + Agence + Operations)
|
||||||
|
- [ ] Autre — tu precises
|
||||||
|
|
||||||
|
Si **Option B** : je refacto les docs (effort ~6-7h sur 1 session) et on continue sur le scope etendu.
|
||||||
|
Si **Option A** : on reste sur le CFA, et tu modelises l'Agence dans un autre projet plus tard.
|
||||||
|
|
||||||
|
Une autre validation a faire : **les "Etudiants" doivent-ils avoir leur propre entite dans Baserow** (avec inscription a une formation, suivi pedagogique, etc.) **ou rester juste des spaces Docmost** sans modelisation structuree ? C'est independant de A/B/C — choix metier orthogonal.
|
||||||
141
docs/03-decision-record.md
Normal file
141
docs/03-decision-record.md
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
# ADR — Architecture Decision Records
|
||||||
|
|
||||||
|
Format Architecture Decision Record. Une entree par decision structurelle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-001 — Stack composite : Docmost (wiki) + Baserow (DBs)
|
||||||
|
|
||||||
|
**Statut** : Accepte le 2026-05-07
|
||||||
|
**Decideurs** : Le user (AdminSys/DevOps) + BYAN (consulting)
|
||||||
|
|
||||||
|
### Contexte
|
||||||
|
|
||||||
|
Aucun projet OSS unique ne couvre :
|
||||||
|
- Users illimites self-host (out : AFFiNE 10-seat, AppFlowy 1-user)
|
||||||
|
- Bidirec + DBs multi-vues + team workspaces simultanement
|
||||||
|
- Budget recurrent zero (out : AFFiNE Team License)
|
||||||
|
|
||||||
|
### Decision
|
||||||
|
|
||||||
|
Combiner deux outils OSS matures :
|
||||||
|
- **Docmost** pour le wiki collaboratif (AGPL, users illimites, team workspaces, share links natifs)
|
||||||
|
- **Baserow** pour les DBs structurees (MIT core, users illimites, vues multiples, rollups, relations)
|
||||||
|
|
||||||
|
Et construire un **bridge service** custom Node TS pour unifier l'UX de surface (l'UI Baserow reste cachee cote utilisateur final).
|
||||||
|
|
||||||
|
### Alternatives ecartees
|
||||||
|
|
||||||
|
1. **AFFiNE Team License paid** : ~2200€/an recurrent, lock-in vendor.
|
||||||
|
2. **Custom DB engine sur Docmost** : 4-6 mois dev (~30-60k€), reinvention de la roue.
|
||||||
|
3. **Notion (cloud)** : pas self-host.
|
||||||
|
4. **Outline + add bidirec custom** : license BSL bloque usage commercial.
|
||||||
|
5. **AppFlowy paid** : disqualifie pour stack Flutter (recrutement difficile, pas web-first).
|
||||||
|
|
||||||
|
### Consequences
|
||||||
|
|
||||||
|
**Positives**
|
||||||
|
- Zero recurrent licensing cost (juste l'infra ~30€/mois VPS)
|
||||||
|
- Stack mainstream TS/NestJS/React/Tiptap pour Docmost et Python/Django pour Baserow
|
||||||
|
- Decouplage permet de remplacer Docmost ou Baserow sans tout refaire
|
||||||
|
- Baserow couvre les besoins DBs du cas (multi-vues, relations, rollups, formules) parmi les Airtable-likes OSS evalues
|
||||||
|
|
||||||
|
**Negatives**
|
||||||
|
- Deux services a maintenir au lieu d'un
|
||||||
|
- Bridge service = code custom a ecrire et maintenir
|
||||||
|
- Performance : appels HTTP entre Docmost et Baserow (mitige par cache Redis)
|
||||||
|
- Risque scope creep sur le bridge (tentation de reproduire 100% de Notion)
|
||||||
|
|
||||||
|
**A surveiller**
|
||||||
|
- Si Docmost ajoute des DBs natives (pas dans roadmap actuelle), reevaluer la valeur de Baserow
|
||||||
|
- Si AFFiNE OSS leve sa limite 10-seat, reevaluer le path A
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-002 — Path B (UX quasi-unified) plutot que Path A (deux mondes)
|
||||||
|
|
||||||
|
**Statut** : Accepte le 2026-05-07
|
||||||
|
|
||||||
|
### Contexte
|
||||||
|
|
||||||
|
Path A (cross-link URL externe entre Docmost et Baserow) demande **2-4 semaines** mais expose l'UI Baserow aux utilisateurs (jonglage entre deux applis).
|
||||||
|
|
||||||
|
Path B (Tiptap nodes custom + API Baserow) demande **2-3 mois pour un fullstack senior, 4-6 mois pour AdminSys/DevOps solo** mais offre une UX unifiee.
|
||||||
|
|
||||||
|
### Decision
|
||||||
|
|
||||||
|
**Path B** retenu. Le user accepte la courbe d'apprentissage Tiptap/React et la timeline plus longue contre une UX qui ne disrupt pas l'experience metier.
|
||||||
|
|
||||||
|
### Consequences
|
||||||
|
|
||||||
|
- Phase 1 (Mois 1) : stack vanilla utilisable telle quelle, l'equipe peut commencer a alimenter le wiki et les DBs sans bridge
|
||||||
|
- Phase 2 (Mois 2-6) : iteration progressive sur les nodes Tiptap custom selon la douleur reelle des utilisateurs
|
||||||
|
- Risque : tentation d'aller direct au "tout brillant" sans valider l'usage. Garde-fou : Phase 1 obligatoire avant tout code custom.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-003 — Monorepo Git
|
||||||
|
|
||||||
|
**Statut** : Accepte le 2026-05-07
|
||||||
|
|
||||||
|
### Decision
|
||||||
|
|
||||||
|
Monorepo unique `formation-hub/` versionne ensemble :
|
||||||
|
- Compose files (`compose.yml`, overrides staging/prod)
|
||||||
|
- Bridge service (`bridge/`)
|
||||||
|
- Schemas Baserow (`baserow/schemas/`)
|
||||||
|
- Patches Docmost (`docmost/patches/`) si fork phase 2+
|
||||||
|
- Docs (`docs/`)
|
||||||
|
- CI/CD (`.github/workflows/`)
|
||||||
|
|
||||||
|
### Pourquoi
|
||||||
|
|
||||||
|
- Couplage fort entre infra, donnees, code custom — versionne ensemble = atomique
|
||||||
|
- Releases coordonnees (un tag = une version coherente du systeme)
|
||||||
|
- Un seul repo a cloner pour reproduire l'environnement
|
||||||
|
|
||||||
|
### Consequences
|
||||||
|
|
||||||
|
- Repo va grossir avec patches Docmost. Acceptable (pas de binaires lourds).
|
||||||
|
- CI/CD doit etre intelligent (ne build que ce qui a change).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-004 — Postgres separe Docmost / Baserow
|
||||||
|
|
||||||
|
**Statut** : Accepte le 2026-05-07
|
||||||
|
|
||||||
|
### Decision
|
||||||
|
|
||||||
|
Chaque service a son propre Postgres (deux containers) en local dev. Possibilite de partager une instance physique en prod via deux databases logiques si besoin perf.
|
||||||
|
|
||||||
|
### Pourquoi
|
||||||
|
|
||||||
|
- Isolation : un dump de Docmost n'affecte pas Baserow
|
||||||
|
- Versions Postgres potentiellement differentes selon les exigences
|
||||||
|
- Migration upstream de Docmost ou Baserow ne risque pas de casser l'autre
|
||||||
|
|
||||||
|
### Consequences
|
||||||
|
|
||||||
|
- Plus de RAM consommee (~200 Mo overhead par instance)
|
||||||
|
- Plus de complexite ops (deux backups distincts) — compense par scripts Makefile
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ADR-005 — Stack technique du bridge (Phase 2)
|
||||||
|
|
||||||
|
**Statut** : Provisoire — sera confirme avant Phase 2
|
||||||
|
|
||||||
|
### Hypothese
|
||||||
|
|
||||||
|
- **Runtime** : Node 22 LTS
|
||||||
|
- **Framework** : Hono (rapide, leger, TypeScript-first)
|
||||||
|
- **HTTP client** : ofetch ou native fetch
|
||||||
|
- **Cache** : Redis (memoire partagee avec Docmost ou dedie)
|
||||||
|
- **Validation** : zod
|
||||||
|
- **Tests** : vitest
|
||||||
|
|
||||||
|
### A confirmer en Phase 2
|
||||||
|
|
||||||
|
- Si on adopte Bun runtime au lieu de Node (perf + TS native)
|
||||||
|
- Si le bridge stocke un etat propre (Postgres dedie) ou reste stateless
|
||||||
322
docs/04-cahier-des-charges-techniques.md
Normal file
322
docs/04-cahier-des-charges-techniques.md
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
# Cahier des Charges Techniques (CDC)
|
||||||
|
|
||||||
|
> Specification technique complete : stack, choix, contraintes, NFR, architecture, roadmap.
|
||||||
|
> Version : 1.0 — Date : 2026-05-07.
|
||||||
|
> Statut : draft, a valider Yan/Ludo avant industrialisation.
|
||||||
|
|
||||||
|
## 1. Identification
|
||||||
|
|
||||||
|
| Champ | Valeur |
|
||||||
|
|-------|--------|
|
||||||
|
| Nom du projet | formation-hub |
|
||||||
|
| Owner technique | Corentin JOGUET (DevOps/AdminSys, bras droit Yan) |
|
||||||
|
| Validateurs | Yan (resp tech), Ludo (direction) |
|
||||||
|
| Sponsor metier | Direction Acadenice |
|
||||||
|
| Type | Outil interne — Notion-like self-host pour CFA + Agence dev |
|
||||||
|
| Date debut | 2026-05-07 |
|
||||||
|
| Date cible MVP | T+1 mois (Phase 1 — stack vanilla + setup metier) |
|
||||||
|
| Date cible v1.0 | T+3 mois (Phase 2 — bridge UX unifie) |
|
||||||
|
|
||||||
|
## 2. Contexte metier
|
||||||
|
|
||||||
|
Acadenice = double activite **CFA + Agence dev** (cf doc fondateur "Vision Acadenice"). Les formateurs sont aussi developpeurs sur projets clients : leur capacite annuelle se split entre les deux activites.
|
||||||
|
|
||||||
|
Le projet formation-hub fournit l'outil interne pour :
|
||||||
|
- Wiki collaboratif (SOPs, supports, doc technique)
|
||||||
|
- Suivi heures formation (CFA)
|
||||||
|
- Suivi projets et taches Agence
|
||||||
|
- Vue 360 capacite par personne (formation + agence)
|
||||||
|
- Acces guests clients (lien partage)
|
||||||
|
- Spaces personnels etudiants (libres, non modelises)
|
||||||
|
|
||||||
|
## 3. Objectifs
|
||||||
|
|
||||||
|
| Objectif | Mesure |
|
||||||
|
|----------|--------|
|
||||||
|
| Centraliser la documentation | 100% des SOPs et supports formation dans l'outil |
|
||||||
|
| Tracer les heures formateurs | Saisie reguliere par 100% des formateurs |
|
||||||
|
| Tracer les heures projets clients | Saisie reguliere par 100% des devs |
|
||||||
|
| Calcul automatique heures restantes | Tableau de bord temps reel par formation, par formateur, par projet |
|
||||||
|
| Reduire le couplage outils externes | Remplacer 0 a 3 outils actuels (Excel, Trello legers ?) |
|
||||||
|
| Self-host illimite users | Aucun cout de licence par seat, juste l'infra |
|
||||||
|
| Ouvert aux etudiants | Spaces personnels libres, beneficient des templates |
|
||||||
|
|
||||||
|
## 4. Perimetre fonctionnel
|
||||||
|
|
||||||
|
Couvert :
|
||||||
|
- Wiki Docmost avec mermaid/drawio/excalidraw natifs
|
||||||
|
- Bases de donnees Baserow (CFA + Agence)
|
||||||
|
- Permissions hierarchiques (workspace, space, page)
|
||||||
|
- Share links avec password / expiration
|
||||||
|
- Acces guests clients
|
||||||
|
|
||||||
|
Non couvert (out of scope v1) :
|
||||||
|
- Modelisation formelle des etudiants (inscriptions, suivi pedagogique)
|
||||||
|
- Generation factures clients
|
||||||
|
- ATS / recrutement
|
||||||
|
- Messagerie integree
|
||||||
|
- Application mobile native (UI mobile-friendly via responsive web)
|
||||||
|
|
||||||
|
## 5. Stack technique
|
||||||
|
|
||||||
|
### 5.1 Diagramme architecture cible
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
User([Utilisateur final<br/>Admin/Formateur/Dev/Etudiant/Client])
|
||||||
|
|
||||||
|
subgraph "Edge / Reverse Proxy"
|
||||||
|
Traefik[Traefik<br/>TLS Let's Encrypt<br/>routing par sous-domaine]
|
||||||
|
end
|
||||||
|
|
||||||
|
User -->|HTTPS| Traefik
|
||||||
|
Traefik -->|wiki.acadenice.fr| Docmost
|
||||||
|
Traefik -->|baserow.acadenice.fr| Baserow
|
||||||
|
Traefik -->|bridge.acadenice.fr| Bridge
|
||||||
|
|
||||||
|
subgraph "Application services"
|
||||||
|
Docmost[Docmost<br/>NestJS + React + Tiptap]
|
||||||
|
Baserow[Baserow<br/>Django + Caddy interne]
|
||||||
|
Bridge[Bridge service<br/>Node 22 + Hono<br/>Phase 2]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Storage"
|
||||||
|
DocmostDB[(Postgres<br/>docmost)]
|
||||||
|
DocmostRedis[(Redis<br/>docmost)]
|
||||||
|
BaserowDB[(Postgres<br/>baserow embedded)]
|
||||||
|
BaserowRedis[(Redis<br/>baserow embedded)]
|
||||||
|
FS[Local FS / MinIO<br/>docmost files]
|
||||||
|
end
|
||||||
|
|
||||||
|
Docmost --> DocmostDB
|
||||||
|
Docmost --> DocmostRedis
|
||||||
|
Docmost --> FS
|
||||||
|
Baserow --> BaserowDB
|
||||||
|
Baserow --> BaserowRedis
|
||||||
|
Bridge -->|API REST| Baserow
|
||||||
|
Bridge --> DocmostRedis
|
||||||
|
Bridge -->|API REST| Docmost
|
||||||
|
|
||||||
|
subgraph "Infra ops"
|
||||||
|
CronBackup[Cron host<br/>backups quotidiens]
|
||||||
|
Monitoring[Uptime monitoring<br/>a definir]
|
||||||
|
end
|
||||||
|
|
||||||
|
CronBackup -->|pg_dump + tar| DocmostDB
|
||||||
|
CronBackup -->|pg_dump + tar| BaserowDB
|
||||||
|
CronBackup -->|tar| FS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Composants
|
||||||
|
|
||||||
|
| Composant | Role | Techno | Version cible | License |
|
||||||
|
|-----------|------|--------|--------------|---------|
|
||||||
|
| **Docmost** | Wiki + collab + share + diagrammes natifs | NestJS, React, Tiptap, Postgres | latest stable (>= v0.8.2 mai 2026) | AGPL-3.0 |
|
||||||
|
| **Baserow** | DBs structurees + multi-vues + rollups + formules | Django, Postgres, Redis, Celery, Caddy | 1.30.x pinned | MIT (core) |
|
||||||
|
| **Bridge** (Phase 2) | API entre Docmost (Tiptap nodes custom) et Baserow | Node 22, Hono, zod, ofetch | 1.0 (a developper) | MIT (interne) |
|
||||||
|
| **PostgreSQL** | DB pour Docmost + DB pour Baserow (containers separes) | Postgres 16 alpine | 16.x | PostgreSQL License |
|
||||||
|
| **Redis** | Cache et queues (Docmost + Bridge) | Redis 7 alpine | 7.x | RSAL (free pour notre usage) |
|
||||||
|
| **MinIO** ou **local FS** | Storage attachments Docmost | MinIO | latest stable | AGPL-3.0 |
|
||||||
|
| **Traefik** | Reverse proxy, TLS auto | Traefik 3.x (deja deploye) | latest | MIT |
|
||||||
|
| **Docker / Compose** | Containerisation, orchestration | Docker 25+, compose v2 | latest | Apache 2.0 |
|
||||||
|
| **GitHub Actions** | CI/CD | GitHub Actions | — | — |
|
||||||
|
|
||||||
|
### 5.3 Versions pinning
|
||||||
|
|
||||||
|
- Docmost : `docmost/docmost:latest` initialement, **pinned** sur version mineure stable apres tests (ex: v0.8.2)
|
||||||
|
- Baserow : `baserow/baserow:1.30.1` (deja pinne)
|
||||||
|
- Postgres : `postgres:16-alpine`
|
||||||
|
- Redis : `redis:7-alpine`
|
||||||
|
- Node Bridge : `node:22-alpine`
|
||||||
|
- Bumps version : decision manuelle apres test staging
|
||||||
|
|
||||||
|
## 6. Choix de stack — justifications
|
||||||
|
|
||||||
|
| Choix | Alternatives ecartees | Raison |
|
||||||
|
|-------|-----------------------|--------|
|
||||||
|
| **Docmost** comme wiki | AFFiNE (10-seat limit), AppFlowy (1-user limit), Outline (BSL + pas de bidirec), SiYuan/Trilium (single-user), HedgeDoc (pas de DB ni bidirec) | Seul wiki AGPL avec users illimites, team workspaces, share links et diagrammes natifs (Mermaid + Draw.io + Excalidraw) integres |
|
||||||
|
| **Baserow** comme moteur DB | NocoDB (license non-OSI Sustainable Use), Teable (jeune, pas mature), Airtable (cloud paye), construire un moteur DB dans Docmost (4-6 mois) | MIT, mature, real-time collab, rollups+formules natifs, multi-vues completes, users illimites |
|
||||||
|
| **Stack composite** vs unifie | AFFiNE Team paid (~2200€/an) ou tout custom | Zero recurrent + Stack mainstream + decouple = remplaceable |
|
||||||
|
| **Bridge custom Node TS** | Embed iframe Baserow ou reecrire UI complete | UX unifie sans Tiptap reverse engineering Docmost. Effort 2-3 mois acceptable. |
|
||||||
|
| **Postgres separe** par service | Postgres partage avec 2 databases | Isolation versions, migrations independantes, dump/restore propres |
|
||||||
|
| **Hono** pour le bridge | Express, Fastify, NestJS, Bun | Leger, TypeScript-first, performance Edge-ready, simple a deployer |
|
||||||
|
| **Path B** (UX quasi-unified) vs Path A (deux mondes) | Cross-link URL externe entre Docmost et Baserow | UX unifie evite jonglage 2 onglets pour les utilisateurs |
|
||||||
|
|
||||||
|
(Voir `02-decision-record.md` pour les ADR detailles.)
|
||||||
|
|
||||||
|
## 7. Specifications non-fonctionnelles (NFR)
|
||||||
|
|
||||||
|
### 7.1 Performance
|
||||||
|
|
||||||
|
| Metrique | Cible | Mesure |
|
||||||
|
|----------|-------|--------|
|
||||||
|
| Latence saisie heures (UC-13, UCA-07) | < 2s p95 | Bridge endpoint timing |
|
||||||
|
| Latence chargement page wiki | < 1s p95 | Lighthouse |
|
||||||
|
| Recalcul rollups Baserow | < 5s | Baserow-side timing |
|
||||||
|
| Recherche full-text Docmost | < 500ms p95 | Docmost search timing |
|
||||||
|
|
||||||
|
### 7.2 Securite
|
||||||
|
|
||||||
|
| Aspect | Specification |
|
||||||
|
|--------|---------------|
|
||||||
|
| Auth Docmost | Email + password, SSO OIDC en option (Phase 3) |
|
||||||
|
| Auth Baserow | Email + password, JWT |
|
||||||
|
| Auth Bridge | API tokens longue duree pour service-to-service |
|
||||||
|
| TLS | Let's Encrypt via Traefik, renouvellement auto |
|
||||||
|
| Backup encryption | AES-256 sur backups distants (MinIO/S3 distant) |
|
||||||
|
| Secrets | Variables d'environnement docker-compose, fichier `.env` exclu de git |
|
||||||
|
| Audit log | Log toutes operations sensibles (suppression, archivage, partage externe) |
|
||||||
|
| RGPD | Suppression de donnees personnelles sur demande, retention etudiants/clients selon loi |
|
||||||
|
|
||||||
|
### 7.3 Disponibilite
|
||||||
|
|
||||||
|
| Aspect | Cible | Justification |
|
||||||
|
|--------|-------|---------------|
|
||||||
|
| Disponibilite stack | 99% (= 3.65j down/an) | Outil interne, pas critique |
|
||||||
|
| RPO (Recovery Point Objective) | 24h max | Backup quotidien |
|
||||||
|
| RTO (Recovery Time Objective) | 4h max | Restauration manuelle assistee |
|
||||||
|
| MTTR | < 1h pour bugs critiques, < 24h pour bugs mineurs | Bug critique = bloquant pour une activite |
|
||||||
|
|
||||||
|
### 7.4 Scalabilite
|
||||||
|
|
||||||
|
Cible v1 : ~30 users simultanes peak, 100 users total — **hors-perf** sur un VPS 4 vCPU/8 Go.
|
||||||
|
|
||||||
|
Croissance an 5 prevue : ~150 users total, ~50 simultanes peak. **Pas de refactor stack prevu** — juste un upsizing VPS si besoin.
|
||||||
|
|
||||||
|
### 7.5 Backup / Disaster recovery
|
||||||
|
|
||||||
|
| Aspect | Strategie |
|
||||||
|
|--------|-----------|
|
||||||
|
| Frequence | Quotidienne, 03:00 UTC |
|
||||||
|
| Targets | Postgres docmost (pg_dump.gz), Postgres baserow embedded (pg_dump.gz), Docmost FS (tar.gz), Baserow data (tar.gz) |
|
||||||
|
| Retention | 30 jours sur disque local, 90 jours sur stockage distant (S3 / Backblaze) |
|
||||||
|
| Test restauration | Mensuel, sur un environnement test isole |
|
||||||
|
| RPO | 24h |
|
||||||
|
| RTO | 4h |
|
||||||
|
|
||||||
|
## 8. Contraintes
|
||||||
|
|
||||||
|
| Contrainte | Justification |
|
||||||
|
|-----------|---------------|
|
||||||
|
| Self-host obligatoire | Souverainete des donnees, pas de SaaS payant |
|
||||||
|
| Stack OSS (AGPL/MIT/Apache acceptable) | Eviter lock-in vendor |
|
||||||
|
| Docker + Compose | Stack ops existante d'Acadenice |
|
||||||
|
| Traefik reverse proxy | Stack ops existante (labels TOML) |
|
||||||
|
| GitHub pour le code | Workflow standard equipe |
|
||||||
|
| Budget recurrent : 0 (hors infra ~30€/mois) | Decision direction |
|
||||||
|
| Equipe dev : Corentin solo + freelance ponctuel pour Tiptap | Pas d'embauche dediee |
|
||||||
|
|
||||||
|
## 9. Hypotheses / dependances
|
||||||
|
|
||||||
|
- Le hardware serveur (VPS Hetzner ou equivalent) est commande / disponible avant deploiement staging
|
||||||
|
- Un nom de domaine `acadenice.com` ou `acadenice.fr` est disponible pour les sous-domaines wiki/baserow/bridge
|
||||||
|
- Traefik tourne deja en prod chez Acadenice — on s'integre dans le reseau Docker existant
|
||||||
|
- L'API Outline reste accessible pendant la phase de validation (push docs depuis local pour relecture)
|
||||||
|
- Docmost et Baserow continuent d'etre maintenus activement par leur upstream pendant la duree du projet
|
||||||
|
|
||||||
|
## 10. Risques et mitigations
|
||||||
|
|
||||||
|
| Risque | Probabilite | Impact | Mitigation |
|
||||||
|
|--------|-------------|--------|-----------|
|
||||||
|
| Docmost upstream change ses API publiques | Faible | Moyen | Pinning de versions, tests staging avant bump |
|
||||||
|
| Baserow change son data model en breaking | Moyenne | Eleve | Backups frequents, migration test avant bump |
|
||||||
|
| Tiptap node-views complexe pour AdminSys solo | **Eleve** | Moyen | Freelance senior fullstack TS pour 2-3 jours pair-programming |
|
||||||
|
| AGPL conformite (publication code source si SaaS public) | Faible | Eleve | Outil reste interne — pas SaaS public, AGPL OK |
|
||||||
|
| Charge depasse capacite VPS 4 vCPU | Faible | Moyen | Upsizing simple, cloud-native |
|
||||||
|
| Perte de cle API Outline ou Baserow | Faible | Faible | Rotation manuelle simple, secrets dans .env |
|
||||||
|
| Manque adoption metier (saisie heures non faite) | Moyenne | Eleve | Onboarding etalonne + UX simple + relances admin |
|
||||||
|
|
||||||
|
## 11. Roadmap technique
|
||||||
|
|
||||||
|
### Phase 0 — Conception (en cours, fini fin mai)
|
||||||
|
|
||||||
|
- [x] Discovery + decisions
|
||||||
|
- [x] Data dictionary, MCD, MLD, UML use cases, state diagrams, MCT, MOT, class diagram, activity diagrams
|
||||||
|
- [x] CDC technique (ce doc)
|
||||||
|
- [ ] MPD Baserow (table-par-table)
|
||||||
|
- [ ] Validation metier (Yan + Ludo + admin pedagogique)
|
||||||
|
|
||||||
|
### Phase 1 — MVP vanilla (T+1 mois)
|
||||||
|
|
||||||
|
- [ ] Setup stack Docker compose locale → staging
|
||||||
|
- [ ] Configuration Docmost (workspace, spaces, share links)
|
||||||
|
- [ ] Configuration Baserow (4 BDDs CFA + 4 BDDs Agence)
|
||||||
|
- [ ] Migration data initiale (formations existantes, formateurs, clients, projets en cours)
|
||||||
|
- [ ] Onboarding 5-10 power users
|
||||||
|
- [ ] Backup + monitoring de base
|
||||||
|
|
||||||
|
### Phase 2 — Bridge UX unifie (T+3 mois)
|
||||||
|
|
||||||
|
- [ ] Bridge service skeleton + premier Tiptap node-view custom (mention `@formateur`, `@projet`)
|
||||||
|
- [ ] Routes /personne/:id, /projet/:id, /formation/:id en page Docmost-style
|
||||||
|
- [ ] Saisie heures formateur + intervention dev en bridge UI mobile-friendly
|
||||||
|
- [ ] Webhook Baserow → bridge → cache Redis pour mentions
|
||||||
|
- [ ] Migration progressive utilisateurs (formateurs + devs ouvrent le bridge plutot que Baserow direct)
|
||||||
|
|
||||||
|
### Phase 3 — Maturite (T+6 mois)
|
||||||
|
|
||||||
|
- [ ] Bidirec backlinks dans Docmost (custom)
|
||||||
|
- [ ] Workflow approbation heures realisees (review admin)
|
||||||
|
- [ ] Notifications avancees (Slack/Teams)
|
||||||
|
- [ ] Rapports PDF (formation + formateur + projet)
|
||||||
|
- [ ] SSO OIDC pour gros volume users
|
||||||
|
|
||||||
|
### Phase 4 — Optimisation & extensions (T+9 mois)
|
||||||
|
|
||||||
|
- [ ] Modelisation etudiants si besoin metier
|
||||||
|
- [ ] Integration calendrier (iCal export)
|
||||||
|
- [ ] API publique limitee pour clients (auto-service projets)
|
||||||
|
- [ ] Multi-langue UI (EN en plus de FR)
|
||||||
|
|
||||||
|
## 12. Estimations couts
|
||||||
|
|
||||||
|
### Infra (recurrent)
|
||||||
|
|
||||||
|
| Element | Cout |
|
||||||
|
|---------|------|
|
||||||
|
| VPS Hetzner CPX31 ou CCX23 (4 vCPU/8 Go) | 13-30€/mois |
|
||||||
|
| Stockage backup distant (Backblaze B2 / OVH Object Storage) | 5-10€/mois |
|
||||||
|
| Domaine + sous-domaines | 15€/an |
|
||||||
|
| **Total recurrent** | **~30€/mois = ~360€/an** |
|
||||||
|
|
||||||
|
### Dev (one-shot)
|
||||||
|
|
||||||
|
| Phase | Effort | Cout estime |
|
||||||
|
|-------|--------|-------------|
|
||||||
|
| Phase 0 — Conception | ~10h Corentin | Inclus salaire |
|
||||||
|
| Phase 1 — MVP vanilla | ~80h Corentin | Inclus salaire |
|
||||||
|
| Phase 2 — Bridge | ~200-300h Corentin + 2-3j freelance Tiptap | Salaire + ~2k€ freelance |
|
||||||
|
| Phase 3 — Maturite | ~150h Corentin | Inclus salaire |
|
||||||
|
| **Total externe** | | **~2-3k€** (freelance ponctuel) |
|
||||||
|
|
||||||
|
## 13. Glossaire
|
||||||
|
|
||||||
|
| Terme | Definition |
|
||||||
|
|-------|------------|
|
||||||
|
| CFA | Centre de Formation des Apprentis |
|
||||||
|
| RNCP | Repertoire National des Certifications Professionnelles (les blocs de competences) |
|
||||||
|
| Bloc de competences | Ensemble homogene et coherent de competences validees ensemble |
|
||||||
|
| Module | Lecon individuelle au sein d'un bloc |
|
||||||
|
| Attribution | Affectation d'un module a un formateur avec heures |
|
||||||
|
| Intervention | Travail d'un developpeur sur une tache projet client (avec heures) |
|
||||||
|
| Formation pedagogique | Cas ou un projet client sert de support de formation pour des etudiants |
|
||||||
|
| Bridge | Service intermediaire qu'on construit entre Docmost et Baserow |
|
||||||
|
| Tiptap | Editor framework utilise par Docmost (extension de ProseMirror) |
|
||||||
|
| Node-view | Composant React custom integre dans un editeur Tiptap |
|
||||||
|
| Rollup | Calcul d'agregation Baserow (sum, count, avg) sur une relation |
|
||||||
|
| Path A / Path B | Strategies d'integration Docmost-Baserow (cf ADR-002) |
|
||||||
|
|
||||||
|
## 14. References
|
||||||
|
|
||||||
|
- Doc fondateur Vision Acadenice : [KxvipVcxNV](https://wiki.acadenice.com/doc/vision-acadenice-document-fondateur-KxvipVcxNV)
|
||||||
|
- Discovery recap : `01-discovery-recap.md`
|
||||||
|
- Decision records : `02-decision-record.md`
|
||||||
|
- Data dictionary : `05-data-dictionary.md`
|
||||||
|
- MCD : `06-merise-mcd.md`
|
||||||
|
- MLD : `07-merise-mld.md`
|
||||||
|
- Sources externes verifiees :
|
||||||
|
- [Docmost AGPL pricing](https://docmost.com/pricing)
|
||||||
|
- [Docmost v0.3.0 release Mermaid+Drawio+Excalidraw](https://github.com/docmost/docmost/releases/tag/v0.3.0)
|
||||||
|
- [AFFiNE 10-seat limit](https://docs.affine.pro/self-host-affine/features/basic-user-quota)
|
||||||
|
- [AppFlowy 1-user limit](https://github.com/AppFlowy-IO/AppFlowy-Cloud/issues/1570)
|
||||||
|
- [Baserow vs NocoDB comparison](https://www.softr.io/blog/baserow-vs-nocodb)
|
||||||
220
docs/05-data-dictionary.md
Normal file
220
docs/05-data-dictionary.md
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
# Data Dictionary
|
||||||
|
|
||||||
|
> Dictionnaire de donnees complet du domaine **CFA + Agence d'Acadenice**.
|
||||||
|
> Source de verite pour le MCD, MLD et MPD. Mantra BYAN #33 : Data Dictionary First.
|
||||||
|
> Scope B valide : entite PERSONNE pivot multi-roles (formateur, developpeur, admin).
|
||||||
|
> **Etudiants** : non modelises ici, juste users Docmost.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Codes en `snake_case`, prefixes par mnemonique d'entite
|
||||||
|
- **Source** : `S` saisi, `C` calcule (rollup/formula), `A` automatique (timestamp/sequence)
|
||||||
|
- **Type abstrait** : independant de la techno (mapping Postgres + Baserow plus loin)
|
||||||
|
- **Nullable** : `O` oui, `N` non
|
||||||
|
|
||||||
|
## Mapping types abstrait → Postgres → Baserow
|
||||||
|
|
||||||
|
| Type abstrait | Postgres | Baserow |
|
||||||
|
|---------------|----------|---------|
|
||||||
|
| `INT` | `INTEGER` ou `BIGINT` (PK) | `Number` (sans decimal) |
|
||||||
|
| `DECIMAL(p,s)` | `NUMERIC(p,s)` | `Number` (avec decimal) |
|
||||||
|
| `VARCHAR(n)` | `VARCHAR(n)` | `Text` |
|
||||||
|
| `TEXT` | `TEXT` | `Long text` |
|
||||||
|
| `DATE` | `DATE` | `Date` |
|
||||||
|
| `TIMESTAMPTZ` | `TIMESTAMP WITH TIME ZONE` | `Last modified time` / `Created time` |
|
||||||
|
| `ENUM(...)` | `VARCHAR + CHECK` ou type ENUM | `Single select` |
|
||||||
|
| `MULTI_ENUM(...)` | tableau VARCHAR ou table associative | `Multiple select` |
|
||||||
|
| `EMAIL` | `VARCHAR(254) + CHECK regex` | `Email` |
|
||||||
|
| `FK` | `INTEGER + REFERENCES` | `Link to table` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Section 1 — Entite pivot
|
||||||
|
|
||||||
|
## Entite PERSONNE
|
||||||
|
|
||||||
|
Centrale au modele. Une personne peut cumuler plusieurs roles. Sa capacite annuelle totale se split entre formation et agence.
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `personne_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `personne_nom` | Nom de famille | VARCHAR(100) | N | — | S | trim |
|
||||||
|
| `personne_prenom` | Prenom | VARCHAR(100) | N | — | S | trim |
|
||||||
|
| `personne_email` | Email pro | EMAIL | N | — | S | UNIQUE, format email |
|
||||||
|
| `personne_telephone` | Telephone | VARCHAR(20) | O | NULL | S | format E.164 si rempli |
|
||||||
|
| `personne_capacite_annuelle` | Capacite totale heures/an | DECIMAL(6,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `personne_split_formation_pct` | Part allouee formation | DECIMAL(4,1) | N | 50.0 | S | `0-100`, `+ split_agence_pct = 100` |
|
||||||
|
| `personne_split_agence_pct` | Part allouee agence | DECIMAL(4,1) | N | 50.0 | S | `0-100` |
|
||||||
|
| `personne_roles` | Roles cumules | MULTI_ENUM | N | — | S | `formateur \| developpeur \| admin \| direction \| support` |
|
||||||
|
| `personne_heures_attribuees_formation` | Cumul heures formation attribuees | DECIMAL(6,2) | N | 0 | C | rollup `SUM(ATTRIBUTION.heures)` |
|
||||||
|
| `personne_heures_attribuees_agence` | Cumul heures agence attribuees | DECIMAL(6,2) | N | 0 | C | rollup `SUM(INTERVENTION.heures)` |
|
||||||
|
| `personne_heures_restantes_formation` | Capacite formation restante | DECIMAL(6,2) | N | 0 | C | formula |
|
||||||
|
| `personne_heures_restantes_agence` | Capacite agence restante | DECIMAL(6,2) | N | 0 | C | formula |
|
||||||
|
| `personne_heures_restantes_total` | Capacite totale restante | DECIMAL(6,2) | N | 0 | C | formula |
|
||||||
|
| `personne_statut` | Statut | ENUM | N | `actif` | S | `actif \| inactif` |
|
||||||
|
|
||||||
|
**Formules cles** :
|
||||||
|
|
||||||
|
```
|
||||||
|
personne_heures_restantes_formation = (capacite_annuelle * split_formation_pct / 100) - heures_attribuees_formation
|
||||||
|
personne_heures_restantes_agence = (capacite_annuelle * split_agence_pct / 100) - heures_attribuees_agence
|
||||||
|
personne_heures_restantes_total = capacite_annuelle - heures_attribuees_formation - heures_attribuees_agence
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** : un admin pur (pas formateur ni developpeur) peut avoir `capacite_annuelle = 0` et splits a 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Section 2 — Branche CFA
|
||||||
|
|
||||||
|
## Entite FORMATION
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `formation_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `formation_nom` | Nom | VARCHAR(200) | N | — | S | UNIQUE, trim |
|
||||||
|
| `formation_description` | Description | TEXT | O | NULL | S | — |
|
||||||
|
| `formation_filiere` | Filiere | ENUM | O | NULL | S | `dev \| graphisme \| marketing \| iot \| cybersec` |
|
||||||
|
| `formation_heures_totales` | Heures totales | DECIMAL(6,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `formation_heures_attribuees` | Heures attribuees blocs | DECIMAL(6,2) | N | 0 | C | rollup |
|
||||||
|
| `formation_heures_restantes` | Restantes | DECIMAL(6,2) | N | 0 | C | formula |
|
||||||
|
| `formation_statut` | Statut | ENUM | N | `draft` | S | `draft \| actif \| termine \| archive` |
|
||||||
|
| `formation_date_debut` | Date debut | DATE | O | NULL | S | — |
|
||||||
|
| `formation_date_fin` | Date fin | DATE | O | NULL | S | `>= date_debut` |
|
||||||
|
| `formation_created_at` | Cree le | TIMESTAMPTZ | N | NOW() | A | — |
|
||||||
|
| `formation_updated_at` | Modifie le | TIMESTAMPTZ | N | NOW() | A | — |
|
||||||
|
|
||||||
|
## Entite BLOC
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `bloc_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `bloc_formation_id` | Formation parente | INT FK | N | — | S | FK → FORMATION, CASCADE |
|
||||||
|
| `bloc_nom` | Nom | VARCHAR(200) | N | — | S | UNIQUE par formation |
|
||||||
|
| `bloc_description` | Description | TEXT | O | NULL | S | — |
|
||||||
|
| `bloc_heures_prevues` | Heures du bloc | DECIMAL(6,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `bloc_heures_attribuees` | Heures modules | DECIMAL(6,2) | N | 0 | C | rollup |
|
||||||
|
| `bloc_heures_restantes` | Restantes | DECIMAL(6,2) | N | 0 | C | formula |
|
||||||
|
| `bloc_ordre` | Ordre dans formation | INT | N | 0 | S | `>= 0` |
|
||||||
|
|
||||||
|
## Entite MODULE
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `module_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `module_bloc_id` | Bloc parent | INT FK | N | — | S | FK → BLOC, CASCADE |
|
||||||
|
| `module_nom` | Nom | VARCHAR(200) | N | — | S | trim |
|
||||||
|
| `module_description` | Description | TEXT | O | NULL | S | — |
|
||||||
|
| `module_heures_prevues` | Heures prevues | DECIMAL(5,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `module_heures_attribuees` | Heures attribuees | DECIMAL(5,2) | N | 0 | C | rollup |
|
||||||
|
| `module_heures_realisees` | Heures realisees | DECIMAL(5,2) | N | 0 | C | rollup |
|
||||||
|
| `module_statut` | Cycle de vie | ENUM | N | `a_attribuer` | S | `a_attribuer \| attribue \| en_cours \| realise \| annule` |
|
||||||
|
|
||||||
|
## Entite ATTRIBUTION (Module ↔ Personne[formateur])
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `attribution_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `attribution_module_id` | Module attribue | INT FK | N | — | S | FK → MODULE, CASCADE |
|
||||||
|
| `attribution_personne_id` | Formateur (Personne) | INT FK | N | — | S | FK → PERSONNE, RESTRICT. Personne doit avoir role `formateur` |
|
||||||
|
| `attribution_heures_attribuees` | Heures planifiees | DECIMAL(5,2) | N | 0 | S | `> 0` |
|
||||||
|
| `attribution_heures_realisees` | Heures effectuees | DECIMAL(5,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `attribution_date_debut` | Debut periode | DATE | O | NULL | S | — |
|
||||||
|
| `attribution_date_fin` | Fin periode | DATE | O | NULL | S | `>= date_debut` |
|
||||||
|
| `attribution_statut` | Statut | ENUM | N | `planifie` | S | `planifie \| en_cours \| realise \| annule` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Section 3 — Branche AGENCE
|
||||||
|
|
||||||
|
## Entite CLIENT
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `client_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `client_nom` | Nom client | VARCHAR(200) | N | — | S | UNIQUE |
|
||||||
|
| `client_contact_principal` | Contact (Nom + role) | VARCHAR(200) | O | NULL | S | — |
|
||||||
|
| `client_contact_email` | Email contact | EMAIL | O | NULL | S | format email |
|
||||||
|
| `client_contact_telephone` | Telephone | VARCHAR(20) | O | NULL | S | — |
|
||||||
|
| `client_secteur` | Secteur d'activite | VARCHAR(100) | O | NULL | S | — |
|
||||||
|
| `client_notes` | Notes libres | TEXT | O | NULL | S | — |
|
||||||
|
| `client_statut` | Statut | ENUM | N | `prospect` | S | `prospect \| actif \| inactif \| archive` |
|
||||||
|
| `client_created_at` | Cree le | TIMESTAMPTZ | N | NOW() | A | — |
|
||||||
|
|
||||||
|
## Entite PROJET
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `projet_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `projet_client_id` | Client | INT FK | N | — | S | FK → CLIENT, RESTRICT |
|
||||||
|
| `projet_nom` | Nom projet | VARCHAR(200) | N | — | S | UNIQUE par client |
|
||||||
|
| `projet_description` | Description | TEXT | O | NULL | S | — |
|
||||||
|
| `projet_type` | Type | ENUM | O | NULL | S | `site_web \| app_mobile \| api \| infra \| audit \| support \| autre` |
|
||||||
|
| `projet_charge_heures` | Charge estimee | DECIMAL(7,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `projet_heures_attribuees` | Heures attribuees taches | DECIMAL(7,2) | N | 0 | C | rollup |
|
||||||
|
| `projet_heures_realisees` | Heures realisees | DECIMAL(7,2) | N | 0 | C | rollup |
|
||||||
|
| `projet_heures_restantes` | Restantes | DECIMAL(7,2) | N | 0 | C | formula |
|
||||||
|
| `projet_date_debut` | Date debut | DATE | O | NULL | S | — |
|
||||||
|
| `projet_date_fin_prevue` | Date fin prevue | DATE | O | NULL | S | `>= date_debut` |
|
||||||
|
| `projet_date_livraison` | Date livraison effective | DATE | O | NULL | S | — |
|
||||||
|
| `projet_statut` | Statut | ENUM | N | `devis` | S | `devis \| en_cours \| livre \| cloture \| abandonne` |
|
||||||
|
| `projet_formation_id` | Formation pedagogique liee | INT FK | O | NULL | S | FK → FORMATION (lien optionnel pour projets pedagogiques) |
|
||||||
|
| `projet_url` | URL livraison | VARCHAR(500) | O | NULL | S | format URL |
|
||||||
|
| `projet_repository` | URL repo Git | VARCHAR(500) | O | NULL | S | format URL |
|
||||||
|
|
||||||
|
## Entite TACHE
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `tache_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `tache_projet_id` | Projet parent | INT FK | N | — | S | FK → PROJET, CASCADE |
|
||||||
|
| `tache_titre` | Titre | VARCHAR(200) | N | — | S | trim |
|
||||||
|
| `tache_description` | Description | TEXT | O | NULL | S | — |
|
||||||
|
| `tache_charge_heures` | Charge estimee | DECIMAL(5,2) | N | 0 | S | `>= 0` |
|
||||||
|
| `tache_heures_realisees` | Heures realisees | DECIMAL(5,2) | N | 0 | C | rollup |
|
||||||
|
| `tache_priorite` | Priorite | ENUM | O | NULL | S | `faible \| normale \| haute \| critique` |
|
||||||
|
| `tache_statut` | Statut | ENUM | N | `todo` | S | `todo \| in_progress \| review \| done \| abandoned` |
|
||||||
|
| `tache_date_debut` | Debut prevu | DATE | O | NULL | S | — |
|
||||||
|
| `tache_date_fin_prevue` | Fin prevue | DATE | O | NULL | S | `>= date_debut` |
|
||||||
|
|
||||||
|
## Entite INTERVENTION (Tache ↔ Personne[developpeur])
|
||||||
|
|
||||||
|
| Code | Designation | Type | Nullable | Default | Source | Contraintes |
|
||||||
|
|------|-------------|------|----------|---------|--------|-------------|
|
||||||
|
| `intervention_id` | Identifiant | INT | N | seq | A | PK |
|
||||||
|
| `intervention_tache_id` | Tache | INT FK | N | — | S | FK → TACHE, CASCADE |
|
||||||
|
| `intervention_personne_id` | Developpeur (Personne) | INT FK | N | — | S | FK → PERSONNE, RESTRICT. Personne doit avoir role `developpeur` |
|
||||||
|
| `intervention_heures` | Heures effectuees | DECIMAL(5,2) | N | 0 | S | `> 0` |
|
||||||
|
| `intervention_date` | Date intervention | DATE | N | TODAY | S | — |
|
||||||
|
| `intervention_notes` | Notes / commit ref | TEXT | O | NULL | S | — |
|
||||||
|
| `intervention_statut` | Statut | ENUM | N | `realise` | S | `planifie \| realise \| annule` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Section 4 — Cardinalites synthetiques
|
||||||
|
|
||||||
|
| Relation | Source | Cible | Cardinalite |
|
||||||
|
|----------|--------|-------|-------------|
|
||||||
|
| FORMATION → BLOC | (1,N) | (1,1) | une formation a au moins 1 bloc |
|
||||||
|
| BLOC → MODULE | (1,N) | (1,1) | un bloc a au moins 1 module |
|
||||||
|
| MODULE ↔ PERSONNE via ATTRIBUTION | (0,N) | (0,N) | n-n porteuse |
|
||||||
|
| CLIENT → PROJET | (0,N) | (1,1) | un client peut avoir 0+ projets |
|
||||||
|
| PROJET → TACHE | (0,N) | (1,1) | un projet peut etre vide ou avoir des taches |
|
||||||
|
| TACHE ↔ PERSONNE via INTERVENTION | (0,N) | (0,N) | n-n porteuse |
|
||||||
|
| PROJET ↔ FORMATION | (0,1) | (0,N) | lien optionnel projet pedagogique |
|
||||||
|
|
||||||
|
# Section 5 — Volumetrie estimee (an 1 / an 5)
|
||||||
|
|
||||||
|
| Entite | An 1 | An 5 |
|
||||||
|
|--------|------|------|
|
||||||
|
| PERSONNE | ~30 | ~80 |
|
||||||
|
| FORMATION | ~10 | ~50 |
|
||||||
|
| BLOC | ~50 | ~250 |
|
||||||
|
| MODULE | ~500 | ~2500 |
|
||||||
|
| ATTRIBUTION | ~600 | ~3000 |
|
||||||
|
| CLIENT | ~10 | ~50 |
|
||||||
|
| PROJET | ~20 | ~150 |
|
||||||
|
| TACHE | ~200 | ~2000 |
|
||||||
|
| INTERVENTION | ~2000 | ~20000 |
|
||||||
|
|
||||||
|
Volumetrie negligeable cote performance — indexation standard sur les FK suffit.
|
||||||
234
docs/06-merise-mcd.md
Normal file
234
docs/06-merise-mcd.md
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
# MCD — Modele Conceptuel de Donnees
|
||||||
|
|
||||||
|
> Vue conceptuelle des entites et relations. Scope B (CFA + Agence via PERSONNE pivot).
|
||||||
|
> Dictionnaire de donnees complet : `05-data-dictionary.md`.
|
||||||
|
|
||||||
|
## 1. Vue d'ensemble
|
||||||
|
|
||||||
|
**8 entites organisees en 3 zones** :
|
||||||
|
|
||||||
|
| Zone | Entites |
|
||||||
|
|------|---------|
|
||||||
|
| Pivot | PERSONNE |
|
||||||
|
| CFA | FORMATION, BLOC, MODULE + ATTRIBUTION (assoc.) |
|
||||||
|
| Agence | CLIENT, PROJET, TACHE + INTERVENTION (assoc.) |
|
||||||
|
|
||||||
|
PERSONNE est le **pivot** entre les deux activites : la meme personne peut avoir le role `formateur` (lie a ATTRIBUTION) et `developpeur` (lie a INTERVENTION). Sa capacite annuelle est splittee entre formation et agence.
|
||||||
|
|
||||||
|
## 2. Diagrammes entites-relations
|
||||||
|
|
||||||
|
Le modele complet a 9 entites — afficher tous les attributs sur un seul ER produit du spaghetti. On decompose en **5 vues** : globale simplifiee + 3 zones detaillees + lien pedagogique.
|
||||||
|
|
||||||
|
### 2.1 Vue globale (simplifiee — entites et relations seules)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
PERSONNE ||--o{ ATTRIBUTION : "formateur"
|
||||||
|
PERSONNE ||--o{ INTERVENTION : "developpeur"
|
||||||
|
FORMATION ||--o{ BLOC : ""
|
||||||
|
BLOC ||--o{ MODULE : ""
|
||||||
|
MODULE ||--o{ ATTRIBUTION : ""
|
||||||
|
CLIENT ||--o{ PROJET : ""
|
||||||
|
PROJET ||--o{ TACHE : ""
|
||||||
|
TACHE ||--o{ INTERVENTION : ""
|
||||||
|
PROJET }o--o| FORMATION : "pedagogique"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Zone CFA (formations + heures formateurs)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
FORMATION ||--o{ BLOC : "1,N contient"
|
||||||
|
BLOC ||--o{ MODULE : "1,N comprend"
|
||||||
|
MODULE ||--o{ ATTRIBUTION : "0,N attribuee"
|
||||||
|
PERSONNE ||--o{ ATTRIBUTION : "0,N enseigne"
|
||||||
|
|
||||||
|
FORMATION {
|
||||||
|
int formation_id PK
|
||||||
|
string formation_nom UK
|
||||||
|
enum formation_filiere
|
||||||
|
decimal formation_heures_totales
|
||||||
|
enum formation_statut
|
||||||
|
date formation_date_debut
|
||||||
|
date formation_date_fin
|
||||||
|
}
|
||||||
|
BLOC {
|
||||||
|
int bloc_id PK
|
||||||
|
int bloc_formation_id FK
|
||||||
|
string bloc_nom
|
||||||
|
decimal bloc_heures_prevues
|
||||||
|
int bloc_ordre
|
||||||
|
}
|
||||||
|
MODULE {
|
||||||
|
int module_id PK
|
||||||
|
int module_bloc_id FK
|
||||||
|
string module_nom
|
||||||
|
decimal module_heures_prevues
|
||||||
|
enum module_statut
|
||||||
|
}
|
||||||
|
ATTRIBUTION {
|
||||||
|
int attribution_id PK
|
||||||
|
int attribution_module_id FK
|
||||||
|
int attribution_personne_id FK
|
||||||
|
decimal heures_attribuees
|
||||||
|
decimal heures_realisees
|
||||||
|
date date_debut
|
||||||
|
enum statut
|
||||||
|
}
|
||||||
|
PERSONNE {
|
||||||
|
int personne_id PK
|
||||||
|
string nom_prenom
|
||||||
|
decimal capacite_annuelle
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Zone Agence (projets clients + heures devs)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
CLIENT ||--o{ PROJET : "1,N a"
|
||||||
|
PROJET ||--o{ TACHE : "0,N comporte"
|
||||||
|
TACHE ||--o{ INTERVENTION : "0,N realisee"
|
||||||
|
PERSONNE ||--o{ INTERVENTION : "0,N realise"
|
||||||
|
|
||||||
|
CLIENT {
|
||||||
|
int client_id PK
|
||||||
|
string client_nom UK
|
||||||
|
string contact_email
|
||||||
|
enum statut
|
||||||
|
}
|
||||||
|
PROJET {
|
||||||
|
int projet_id PK
|
||||||
|
int projet_client_id FK
|
||||||
|
string projet_nom
|
||||||
|
enum type
|
||||||
|
decimal charge_heures
|
||||||
|
date date_debut
|
||||||
|
enum statut
|
||||||
|
}
|
||||||
|
TACHE {
|
||||||
|
int tache_id PK
|
||||||
|
int tache_projet_id FK
|
||||||
|
string titre
|
||||||
|
decimal charge_heures
|
||||||
|
enum priorite
|
||||||
|
enum statut
|
||||||
|
}
|
||||||
|
INTERVENTION {
|
||||||
|
int intervention_id PK
|
||||||
|
int intervention_tache_id FK
|
||||||
|
int intervention_personne_id FK
|
||||||
|
decimal heures
|
||||||
|
date intervention_date
|
||||||
|
enum statut
|
||||||
|
}
|
||||||
|
PERSONNE {
|
||||||
|
int personne_id PK
|
||||||
|
string nom_prenom
|
||||||
|
decimal capacite_annuelle
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Zone Personne pivot (capacite + roles)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
PERSONNE ||--o{ ATTRIBUTION : "role formateur"
|
||||||
|
PERSONNE ||--o{ INTERVENTION : "role developpeur"
|
||||||
|
|
||||||
|
PERSONNE {
|
||||||
|
int personne_id PK
|
||||||
|
string personne_nom
|
||||||
|
string personne_prenom
|
||||||
|
string personne_email UK
|
||||||
|
decimal capacite_annuelle
|
||||||
|
decimal split_formation_pct
|
||||||
|
decimal split_agence_pct
|
||||||
|
string roles "multi-select"
|
||||||
|
decimal heures_attribuees_formation "rollup"
|
||||||
|
decimal heures_attribuees_agence "rollup"
|
||||||
|
decimal heures_restantes_total "formula"
|
||||||
|
enum statut
|
||||||
|
}
|
||||||
|
ATTRIBUTION {
|
||||||
|
int attribution_id PK
|
||||||
|
int attribution_module_id FK
|
||||||
|
int attribution_personne_id FK
|
||||||
|
decimal heures_attribuees
|
||||||
|
}
|
||||||
|
INTERVENTION {
|
||||||
|
int intervention_id PK
|
||||||
|
int intervention_tache_id FK
|
||||||
|
int intervention_personne_id FK
|
||||||
|
decimal heures
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Lien projet pedagogique (cross CFA-Agence)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
PROJET }o--o| FORMATION : "0,1 projet pedagogique"
|
||||||
|
|
||||||
|
PROJET {
|
||||||
|
int projet_id PK
|
||||||
|
int projet_formation_id FK "nullable"
|
||||||
|
string projet_nom
|
||||||
|
}
|
||||||
|
FORMATION {
|
||||||
|
int formation_id PK
|
||||||
|
string formation_nom
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Notation** : ce lien est optionnel cote PROJET (un projet peut etre purement client). Cote FORMATION, plusieurs projets peuvent etre lies a une formation pedagogique.
|
||||||
|
|
||||||
|
## 3. Cardinalites detaillees
|
||||||
|
|
||||||
|
| Relation | Source | Cardinalite | Cible | Cardinalite | Sens metier |
|
||||||
|
|----------|--------|-------------|-------|-------------|-------------|
|
||||||
|
| CONTIENT (CFA) | FORMATION | (1,N) | BLOC | (1,1) | une formation comprend des blocs RNCP |
|
||||||
|
| COMPREND (CFA) | BLOC | (1,N) | MODULE | (1,1) | un bloc se decompose en modules |
|
||||||
|
| ATTRIBUTION (CFA) | MODULE | (0,N) | PERSONNE | (0,N) | un module est dispense par 0-N formateurs |
|
||||||
|
| EMPLOIE (Agence) | CLIENT | (1,N) | PROJET | (1,1) | un client peut avoir des projets |
|
||||||
|
| COMPORTE (Agence) | PROJET | (0,N) | TACHE | (1,1) | un projet est decoupe en taches |
|
||||||
|
| INTERVENTION (Agence) | TACHE | (0,N) | PERSONNE | (0,N) | un dev peut intervenir sur 0-N taches |
|
||||||
|
| PROJET_PEDAGOGIQUE | PROJET | (0,1) | FORMATION | (0,N) | un projet client peut servir de support pedagogique a une formation |
|
||||||
|
|
||||||
|
## 4. Calculs (rollups + formulas) cles
|
||||||
|
|
||||||
|
```
|
||||||
|
PERSONNE.heures_attribuees_formation = SUM(ATTRIBUTION.heures_attribuees)
|
||||||
|
WHERE attribution_personne_id = personne_id
|
||||||
|
AND attribution_statut != 'annule'
|
||||||
|
|
||||||
|
PERSONNE.heures_attribuees_agence = SUM(INTERVENTION.heures)
|
||||||
|
WHERE intervention_personne_id = personne_id
|
||||||
|
AND intervention_statut != 'annule'
|
||||||
|
|
||||||
|
PERSONNE.heures_restantes_total = capacite_annuelle
|
||||||
|
- heures_attribuees_formation
|
||||||
|
- heures_attribuees_agence
|
||||||
|
|
||||||
|
PROJET.heures_realisees = SUM(TACHE.heures_realisees) WHERE tache_projet_id = projet_id
|
||||||
|
TACHE.heures_realisees = SUM(INTERVENTION.heures) WHERE intervention_tache_id = tache_id
|
||||||
|
|
||||||
|
(rollups CFA inchanges depuis version precedente — voir Data Dictionary)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Regles de gestion (extension)
|
||||||
|
|
||||||
|
- **RG-PERSONNE-01** : `personne_split_formation_pct + personne_split_agence_pct = 100` (CHECK constraint)
|
||||||
|
- **RG-PERSONNE-02** : Une attribution ne peut etre creee que si la personne a `formateur` dans ses roles.
|
||||||
|
- **RG-PERSONNE-03** : Une intervention ne peut etre creee que si la personne a `developpeur` dans ses roles.
|
||||||
|
- **RG-PERSONNE-04** : `heures_restantes_total >= 0` est un warning UI, pas un blocage (depassement possible avec justification).
|
||||||
|
|
||||||
|
(RG CFA conserves : voir version precedente du MCD pour RG-FORMATION, RG-BLOC, RG-MODULE)
|
||||||
|
|
||||||
|
## 6. Questions ouvertes (a valider metier)
|
||||||
|
|
||||||
|
- [ ] Le split formation/agence est-il fixe par personne ou variable par periode (ex: trimestre 1 a 70/30, trimestre 2 a 50/50) ?
|
||||||
|
- [ ] Faut-il modeliser une notion de **session** (un module enseigne plusieurs fois a des promotions differentes) ?
|
||||||
|
- [ ] Faut-il une notion de **promotion** ou de **classe** dans le CFA ?
|
||||||
|
- [ ] Pour les projets pedagogiques (lien PROJET ↔ FORMATION), comment tracer les etudiants impliques ? (Si on ne modelise pas l'etudiant, on ne peut pas formellement le lier au projet — a discuter.)
|
||||||
|
- [ ] Workflow d'approbation des heures realisees (admin valide avant facturation ou paie) ?
|
||||||
359
docs/07-merise-mld.md
Normal file
359
docs/07-merise-mld.md
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
# MLD — Modele Logique de Donnees
|
||||||
|
|
||||||
|
> Traduction du MCD en schema relationnel. Scope B (CFA + Agence + PERSONNE pivot).
|
||||||
|
> Implementation Baserow concrete : `15-baserow-mpd.md` (a venir).
|
||||||
|
|
||||||
|
## 1. Vue d'ensemble du schema relationnel
|
||||||
|
|
||||||
|
Decoupe en sous-vues pour eviter le spaghetti d'auto-layout : globale simplifiee + 3 zones + lien pedagogique.
|
||||||
|
|
||||||
|
### 1.1 Vue globale (PK/FK seuls)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
personne ||--o{ attribution : "RESTRICT"
|
||||||
|
personne ||--o{ intervention : "RESTRICT"
|
||||||
|
formation ||--o{ bloc : "CASCADE"
|
||||||
|
bloc ||--o{ module : "CASCADE"
|
||||||
|
module ||--o{ attribution : "CASCADE"
|
||||||
|
client ||--o{ projet : "RESTRICT"
|
||||||
|
projet ||--o{ tache : "CASCADE"
|
||||||
|
tache ||--o{ intervention : "CASCADE"
|
||||||
|
projet }o--o| formation : "SET NULL"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Zone CFA — schema relationnel
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
formation ||--o{ bloc : "FK bloc_formation_id"
|
||||||
|
bloc ||--o{ module : "FK module_bloc_id"
|
||||||
|
module ||--o{ attribution : "FK attribution_module_id"
|
||||||
|
personne ||--o{ attribution : "FK attribution_personne_id"
|
||||||
|
|
||||||
|
formation {
|
||||||
|
INT formation_id PK
|
||||||
|
VARCHAR formation_nom UK
|
||||||
|
ENUM formation_filiere
|
||||||
|
DECIMAL heures_totales
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
bloc {
|
||||||
|
INT bloc_id PK
|
||||||
|
INT bloc_formation_id FK
|
||||||
|
VARCHAR bloc_nom
|
||||||
|
DECIMAL heures_prevues
|
||||||
|
}
|
||||||
|
module {
|
||||||
|
INT module_id PK
|
||||||
|
INT module_bloc_id FK
|
||||||
|
VARCHAR module_nom
|
||||||
|
DECIMAL heures_prevues
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
attribution {
|
||||||
|
INT attribution_id PK
|
||||||
|
INT attribution_module_id FK
|
||||||
|
INT attribution_personne_id FK
|
||||||
|
DECIMAL heures_attribuees
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
personne {
|
||||||
|
INT personne_id PK
|
||||||
|
VARCHAR nom_prenom
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Zone Agence — schema relationnel
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
client ||--o{ projet : "FK projet_client_id"
|
||||||
|
projet ||--o{ tache : "FK tache_projet_id"
|
||||||
|
tache ||--o{ intervention : "FK intervention_tache_id"
|
||||||
|
personne ||--o{ intervention : "FK intervention_personne_id"
|
||||||
|
|
||||||
|
client {
|
||||||
|
INT client_id PK
|
||||||
|
VARCHAR client_nom UK
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
projet {
|
||||||
|
INT projet_id PK
|
||||||
|
INT projet_client_id FK
|
||||||
|
VARCHAR projet_nom
|
||||||
|
ENUM type
|
||||||
|
DECIMAL charge_heures
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
tache {
|
||||||
|
INT tache_id PK
|
||||||
|
INT tache_projet_id FK
|
||||||
|
VARCHAR titre
|
||||||
|
DECIMAL charge_heures
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
intervention {
|
||||||
|
INT intervention_id PK
|
||||||
|
INT intervention_tache_id FK
|
||||||
|
INT intervention_personne_id FK
|
||||||
|
DECIMAL heures
|
||||||
|
DATE intervention_date
|
||||||
|
}
|
||||||
|
personne {
|
||||||
|
INT personne_id PK
|
||||||
|
VARCHAR nom_prenom
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Zone Personne pivot — table & FK sortantes
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
personne ||--o{ attribution : "FK personne_id"
|
||||||
|
personne ||--o{ intervention : "FK personne_id"
|
||||||
|
|
||||||
|
personne {
|
||||||
|
INT personne_id PK
|
||||||
|
VARCHAR personne_nom
|
||||||
|
VARCHAR personne_prenom
|
||||||
|
VARCHAR personne_email UK
|
||||||
|
DECIMAL capacite_annuelle
|
||||||
|
DECIMAL split_formation_pct
|
||||||
|
DECIMAL split_agence_pct
|
||||||
|
TEXT roles "multi-select"
|
||||||
|
ENUM statut
|
||||||
|
}
|
||||||
|
attribution {
|
||||||
|
INT attribution_id PK
|
||||||
|
INT module_id FK
|
||||||
|
INT personne_id FK
|
||||||
|
DECIMAL heures_attribuees
|
||||||
|
}
|
||||||
|
intervention {
|
||||||
|
INT intervention_id PK
|
||||||
|
INT tache_id FK
|
||||||
|
INT personne_id FK
|
||||||
|
DECIMAL heures
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 Lien pedagogique cross-zone
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
projet }o--o| formation : "FK projet_formation_id (SET NULL)"
|
||||||
|
|
||||||
|
projet {
|
||||||
|
INT projet_id PK
|
||||||
|
INT projet_formation_id FK "nullable"
|
||||||
|
}
|
||||||
|
formation {
|
||||||
|
INT formation_id PK
|
||||||
|
VARCHAR formation_nom
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vue flowchart — navigation FK
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
P[personne]:::pivot
|
||||||
|
F[formation] --> B[bloc] --> M[module] --> A[attribution]
|
||||||
|
P --> A
|
||||||
|
C[client] --> Pr[projet] --> T[tache] --> I[intervention]
|
||||||
|
P --> I
|
||||||
|
Pr -.optionnel.-> F
|
||||||
|
|
||||||
|
classDef pivot fill:#FF825C,stroke:#333,color:#fff
|
||||||
|
classDef cfa fill:#FFB347,stroke:#333,color:#000
|
||||||
|
classDef agence fill:#5CB3FF,stroke:#333,color:#fff
|
||||||
|
class F,B,M,A cfa
|
||||||
|
class C,Pr,T,I agence
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Regles de passage MCD → MLD (rappel)
|
||||||
|
|
||||||
|
1. Chaque entite → table.
|
||||||
|
2. Relation 1,N → la PK cote (1,1) devient FK cote (1,N).
|
||||||
|
3. Relation N,N porteuse → table associative avec PK composite (ou PK auto + UNIQUE composite).
|
||||||
|
4. Champs calcules → soit caches via triggers, soit recalcules par Baserow rollup.
|
||||||
|
|
||||||
|
## 3. Tables — definitions DDL
|
||||||
|
|
||||||
|
### Table `personne`
|
||||||
|
|
||||||
|
```
|
||||||
|
personne (
|
||||||
|
personne_id INT PK AUTO,
|
||||||
|
personne_nom VARCHAR(100) NOT NULL,
|
||||||
|
personne_prenom VARCHAR(100) NOT NULL,
|
||||||
|
personne_email VARCHAR(254) NOT NULL UNIQUE,
|
||||||
|
personne_telephone VARCHAR(20),
|
||||||
|
personne_capacite_annuelle DECIMAL(6,2) NOT NULL DEFAULT 0,
|
||||||
|
personne_split_formation_pct DECIMAL(4,1) NOT NULL DEFAULT 50.0,
|
||||||
|
personne_split_agence_pct DECIMAL(4,1) NOT NULL DEFAULT 50.0,
|
||||||
|
personne_roles VARCHAR(200) NOT NULL, -- csv ou table N:N selon Baserow
|
||||||
|
personne_statut ENUM NOT NULL DEFAULT 'actif'
|
||||||
|
)
|
||||||
|
CHECK (personne_split_formation_pct + personne_split_agence_pct = 100)
|
||||||
|
CHECK (personne_email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
|
||||||
|
INDEX idx_personne_statut ON personne(personne_statut)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tables CFA (formation, bloc, module, attribution)
|
||||||
|
|
||||||
|
```
|
||||||
|
formation (
|
||||||
|
formation_id INT PK AUTO,
|
||||||
|
formation_nom VARCHAR(200) NOT NULL UNIQUE,
|
||||||
|
formation_description TEXT,
|
||||||
|
formation_filiere ENUM('dev','graphisme','marketing','iot','cybersec'),
|
||||||
|
formation_heures_totales DECIMAL(6,2) NOT NULL DEFAULT 0,
|
||||||
|
formation_statut ENUM('draft','actif','termine','archive') NOT NULL DEFAULT 'draft',
|
||||||
|
formation_date_debut DATE,
|
||||||
|
formation_date_fin DATE,
|
||||||
|
formation_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
formation_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
)
|
||||||
|
CHECK (formation_date_fin >= formation_date_debut OR formation_date_fin IS NULL)
|
||||||
|
|
||||||
|
bloc (
|
||||||
|
bloc_id INT PK AUTO,
|
||||||
|
bloc_formation_id INT NOT NULL FK → formation(formation_id) ON DELETE CASCADE,
|
||||||
|
bloc_nom VARCHAR(200) NOT NULL,
|
||||||
|
bloc_description TEXT,
|
||||||
|
bloc_heures_prevues DECIMAL(6,2) NOT NULL DEFAULT 0,
|
||||||
|
bloc_ordre INT NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE (bloc_formation_id, bloc_nom)
|
||||||
|
)
|
||||||
|
INDEX idx_bloc_formation ON bloc(bloc_formation_id)
|
||||||
|
|
||||||
|
module (
|
||||||
|
module_id INT PK AUTO,
|
||||||
|
module_bloc_id INT NOT NULL FK → bloc(bloc_id) ON DELETE CASCADE,
|
||||||
|
module_nom VARCHAR(200) NOT NULL,
|
||||||
|
module_description TEXT,
|
||||||
|
module_heures_prevues DECIMAL(5,2) NOT NULL DEFAULT 0,
|
||||||
|
module_statut ENUM('a_attribuer','attribue','en_cours','realise','annule') NOT NULL DEFAULT 'a_attribuer'
|
||||||
|
)
|
||||||
|
INDEX idx_module_bloc ON module(module_bloc_id)
|
||||||
|
INDEX idx_module_statut ON module(module_statut)
|
||||||
|
|
||||||
|
attribution (
|
||||||
|
attribution_id INT PK AUTO,
|
||||||
|
attribution_module_id INT NOT NULL FK → module(module_id) ON DELETE CASCADE,
|
||||||
|
attribution_personne_id INT NOT NULL FK → personne(personne_id) ON DELETE RESTRICT,
|
||||||
|
attribution_heures_attribuees DECIMAL(5,2) NOT NULL,
|
||||||
|
attribution_heures_realisees DECIMAL(5,2) NOT NULL DEFAULT 0,
|
||||||
|
attribution_date_debut DATE,
|
||||||
|
attribution_date_fin DATE,
|
||||||
|
attribution_statut ENUM('planifie','en_cours','realise','annule') NOT NULL DEFAULT 'planifie'
|
||||||
|
)
|
||||||
|
CHECK (attribution_heures_attribuees > 0)
|
||||||
|
CHECK (attribution_heures_realisees >= 0)
|
||||||
|
INDEX idx_attribution_module ON attribution(attribution_module_id)
|
||||||
|
INDEX idx_attribution_personne ON attribution(attribution_personne_id)
|
||||||
|
INDEX idx_attribution_statut ON attribution(attribution_statut)
|
||||||
|
UNIQUE (attribution_module_id, attribution_personne_id, attribution_date_debut)
|
||||||
|
WHERE attribution_statut != 'annule' -- index partiel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tables Agence (client, projet, tache, intervention)
|
||||||
|
|
||||||
|
```
|
||||||
|
client (
|
||||||
|
client_id INT PK AUTO,
|
||||||
|
client_nom VARCHAR(200) NOT NULL UNIQUE,
|
||||||
|
client_contact_principal VARCHAR(200),
|
||||||
|
client_contact_email VARCHAR(254),
|
||||||
|
client_contact_telephone VARCHAR(20),
|
||||||
|
client_secteur VARCHAR(100),
|
||||||
|
client_notes TEXT,
|
||||||
|
client_statut ENUM('prospect','actif','inactif','archive') NOT NULL DEFAULT 'prospect',
|
||||||
|
client_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
)
|
||||||
|
|
||||||
|
projet (
|
||||||
|
projet_id INT PK AUTO,
|
||||||
|
projet_client_id INT NOT NULL FK → client(client_id) ON DELETE RESTRICT,
|
||||||
|
projet_nom VARCHAR(200) NOT NULL,
|
||||||
|
projet_description TEXT,
|
||||||
|
projet_type ENUM('site_web','app_mobile','api','infra','audit','support','autre'),
|
||||||
|
projet_charge_heures DECIMAL(7,2) NOT NULL DEFAULT 0,
|
||||||
|
projet_date_debut DATE,
|
||||||
|
projet_date_fin_prevue DATE,
|
||||||
|
projet_date_livraison DATE,
|
||||||
|
projet_statut ENUM('devis','en_cours','livre','cloture','abandonne') NOT NULL DEFAULT 'devis',
|
||||||
|
projet_formation_id INT FK → formation(formation_id) ON DELETE SET NULL,
|
||||||
|
projet_url VARCHAR(500),
|
||||||
|
projet_repository VARCHAR(500),
|
||||||
|
UNIQUE (projet_client_id, projet_nom)
|
||||||
|
)
|
||||||
|
INDEX idx_projet_client ON projet(projet_client_id)
|
||||||
|
INDEX idx_projet_statut ON projet(projet_statut)
|
||||||
|
INDEX idx_projet_formation ON projet(projet_formation_id)
|
||||||
|
|
||||||
|
tache (
|
||||||
|
tache_id INT PK AUTO,
|
||||||
|
tache_projet_id INT NOT NULL FK → projet(projet_id) ON DELETE CASCADE,
|
||||||
|
tache_titre VARCHAR(200) NOT NULL,
|
||||||
|
tache_description TEXT,
|
||||||
|
tache_charge_heures DECIMAL(5,2) NOT NULL DEFAULT 0,
|
||||||
|
tache_priorite ENUM('faible','normale','haute','critique'),
|
||||||
|
tache_statut ENUM('todo','in_progress','review','done','abandoned') NOT NULL DEFAULT 'todo',
|
||||||
|
tache_date_debut DATE,
|
||||||
|
tache_date_fin_prevue DATE
|
||||||
|
)
|
||||||
|
INDEX idx_tache_projet ON tache(tache_projet_id)
|
||||||
|
INDEX idx_tache_statut ON tache(tache_statut)
|
||||||
|
|
||||||
|
intervention (
|
||||||
|
intervention_id INT PK AUTO,
|
||||||
|
intervention_tache_id INT NOT NULL FK → tache(tache_id) ON DELETE CASCADE,
|
||||||
|
intervention_personne_id INT NOT NULL FK → personne(personne_id) ON DELETE RESTRICT,
|
||||||
|
intervention_heures DECIMAL(5,2) NOT NULL,
|
||||||
|
intervention_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||||
|
intervention_notes TEXT,
|
||||||
|
intervention_statut ENUM('planifie','realise','annule') NOT NULL DEFAULT 'realise'
|
||||||
|
)
|
||||||
|
CHECK (intervention_heures > 0)
|
||||||
|
INDEX idx_intervention_tache ON intervention(intervention_tache_id)
|
||||||
|
INDEX idx_intervention_personne ON intervention(intervention_personne_id)
|
||||||
|
INDEX idx_intervention_date ON intervention(intervention_date)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Comportement ON DELETE
|
||||||
|
|
||||||
|
| Relation | ON DELETE | Justification |
|
||||||
|
|----------|-----------|---------------|
|
||||||
|
| formation → bloc | CASCADE | Cycle de vie partage |
|
||||||
|
| bloc → module | CASCADE | Cycle de vie partage |
|
||||||
|
| module → attribution | CASCADE | Si module supprime, attributions deviennent orphelines |
|
||||||
|
| client → projet | RESTRICT | Empeche suppression client ayant projets |
|
||||||
|
| projet → tache | CASCADE | Cycle de vie partage |
|
||||||
|
| tache → intervention | CASCADE | Idem |
|
||||||
|
| personne → attribution / intervention | RESTRICT | Force a archiver `personne_statut = inactif` plutot que supprimer |
|
||||||
|
| projet → formation (lien pedagogique) | SET NULL | Suppression formation laisse le projet exister |
|
||||||
|
|
||||||
|
## 5. Mapping vers Baserow
|
||||||
|
|
||||||
|
| Concept SQL | Baserow equivalent |
|
||||||
|
|-------------|--------------------|
|
||||||
|
| Table | Database → Table |
|
||||||
|
| FK | `Link to table` (gere bidirec auto) |
|
||||||
|
| ENUM | `Single select` |
|
||||||
|
| MULTI_ENUM (personne_roles) | `Multiple select` |
|
||||||
|
| FK ON DELETE CASCADE | UI Baserow ou webhook → bridge |
|
||||||
|
| INDEX | Implicite sur `Link to table` |
|
||||||
|
| CHECK CONSTRAINT | Validation cote bridge ou formules de validation |
|
||||||
|
| Calculated rollup/formula | `Lookup` + `Formula` + `Count` |
|
||||||
|
|
||||||
|
Voir `15-baserow-mpd.md` (a venir) pour la traduction concrete table-par-table.
|
||||||
|
|
||||||
|
## 6. Questions ouvertes
|
||||||
|
|
||||||
|
- [ ] Materialiser les calculs ou recalculer a la lecture ? Baserow rollups = cache materialise auto. OK pour notre volumetrie.
|
||||||
|
- [ ] Soft-delete ou hard-delete ? Si Qualiopi exige tracabilite, soft-delete obligatoire (ajouter `*_deleted_at TIMESTAMPTZ NULLABLE` partout).
|
||||||
|
- [ ] Audit log par row ? Baserow a `Last modified by` + `Last modified time` natifs. Suffisant ou il faut un journal d'evenements separe ?
|
||||||
|
- [ ] Multi-tenant futur ? Pour l'instant Acadenice mono-instance. Si rachat / scaling, ajouter `tenant_id` a toutes les tables.
|
||||||
226
docs/08-merise-mct.md
Normal file
226
docs/08-merise-mct.md
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
# MCT — Modele Conceptuel de Traitements
|
||||||
|
|
||||||
|
> Vue dynamique des operations metier. Complete le MCD (statique) et les state diagrams (cycles de vie).
|
||||||
|
> Methodologie : Merise. Chaque operation = evenement declencheur + regle de synchronisation + actions + evenement resultat.
|
||||||
|
|
||||||
|
## 1. Conventions
|
||||||
|
|
||||||
|
- **Evenement** : un fait survenant dans le systeme, declenche une operation. Note `EV-XX`.
|
||||||
|
- **Operation** : traitement metier qui transforme un etat. Note `OP-XX`.
|
||||||
|
- **Regle de synchronisation** : condition logique sur les evenements entrants (AND/OR).
|
||||||
|
- **Regle d'emission** : condition sur les evenements de sortie selon le resultat.
|
||||||
|
|
||||||
|
## 2. Liste des evenements
|
||||||
|
|
||||||
|
| Code | Evenement | Origine |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| EV-01 | Demande creation formation | Admin |
|
||||||
|
| EV-02 | Specs blocs renseignees | Admin |
|
||||||
|
| EV-03 | Specs modules renseignees | Admin |
|
||||||
|
| EV-04 | Demande attribution module | Admin |
|
||||||
|
| EV-05 | Formateur dispo (capacite >= heures requises) | System (calcul) |
|
||||||
|
| EV-06 | Date debut module atteinte | System (cron) |
|
||||||
|
| EV-07 | Saisie heures realisees | Formateur |
|
||||||
|
| EV-08 | Date fin module atteinte | System (cron) |
|
||||||
|
| EV-09 | Demande annulation attribution | Admin |
|
||||||
|
| EV-10 | Rollup recalcule | System (auto) |
|
||||||
|
| EV-11 | Capacite formateur depassee | System (calcul) |
|
||||||
|
| EV-12 | Demande archivage formation | Admin |
|
||||||
|
| EV-13 | Tous modules d'une formation realises | System (calcul) |
|
||||||
|
| EV-14 | Demande edition page wiki | Admin / Formateur |
|
||||||
|
| EV-15 | Lien partage cree | Admin |
|
||||||
|
|
||||||
|
## 3. Liste des operations
|
||||||
|
|
||||||
|
| Code | Operation | Acteur principal |
|
||||||
|
|------|-----------|------------------|
|
||||||
|
| OP-01 | Creer une formation | Admin |
|
||||||
|
| OP-02 | Decomposer en blocs et modules | Admin |
|
||||||
|
| OP-03 | Attribuer un module a un formateur | Admin |
|
||||||
|
| OP-04 | Saisir heures realisees | Formateur |
|
||||||
|
| OP-05 | Annuler une attribution | Admin |
|
||||||
|
| OP-06 | Cloturer un module | System / Admin |
|
||||||
|
| OP-07 | Cloturer une formation | System / Admin |
|
||||||
|
| OP-08 | Recalculer rollups | System (auto) |
|
||||||
|
| OP-09 | Notifier depassement capacite | System (auto) |
|
||||||
|
| OP-10 | Archiver une formation | Admin |
|
||||||
|
| OP-11 | Generer rapport formation | Admin |
|
||||||
|
| OP-12 | Generer rapport formateur | Admin |
|
||||||
|
| OP-13 | Inviter client par lien partage | Admin |
|
||||||
|
| OP-14 | Backup quotidien | System (cron) |
|
||||||
|
|
||||||
|
## 4. Operations detaillees
|
||||||
|
|
||||||
|
### OP-01 — Creer une formation
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
EV01([EV-01 Demande creation formation]) --> SYNC1{Regle sync:<br/>EV-01 saisi}
|
||||||
|
SYNC1 --> OP01[OP-01 Creer formation]
|
||||||
|
OP01 -->|Validation OK| EV_OUT_OK([EV: Formation creee statut=draft])
|
||||||
|
OP01 -->|Nom deja existant| EV_OUT_ERR([EV: Erreur saisie])
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Entrants** : EV-01
|
||||||
|
- **Synchro** : EV-01 saisi
|
||||||
|
- **Actions** :
|
||||||
|
1. Valider unicite du nom
|
||||||
|
2. Valider `heures_totales > 0`
|
||||||
|
3. INSERT formation avec `statut = draft`, timestamps auto
|
||||||
|
- **Sortants** :
|
||||||
|
- Si OK → "Formation creee" (statut = draft)
|
||||||
|
- Si erreur → "Erreur saisie"
|
||||||
|
|
||||||
|
### OP-03 — Attribuer un module a un formateur
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
EV04([EV-04 Demande attribution]) --> SYNC2{AND}
|
||||||
|
EV05([EV-05 Formateur dispo]) --> SYNC2
|
||||||
|
SYNC2 --> OP03[OP-03 Creer attribution]
|
||||||
|
OP03 -->|RG-01 OK| EV_ATTR([EV: Attribution creee statut=planifie])
|
||||||
|
OP03 -->|RG-01 KO| EV_BLOCK([EV: Erreur depassement heures module])
|
||||||
|
EV_ATTR --> OP08_TRIG[Trigger OP-08 Recalculer rollups]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Entrants** : EV-04 AND EV-05
|
||||||
|
- **Synchro** : les deux evenements presents (formateur dispo verifie avant ouverture du formulaire)
|
||||||
|
- **Actions** :
|
||||||
|
1. Valider RG-01 : `SUM(attributions.heures) + nouvelle_attribution.heures <= module.heures_prevues`
|
||||||
|
2. Valider RG-02 : warning si `formateur.heures_attribuees + nouvelle.heures > formateur.capacite` (mais pas blocage)
|
||||||
|
3. INSERT attribution avec `statut = planifie`
|
||||||
|
4. Declenche EV-10 (rollup recalcule)
|
||||||
|
- **Sortants** :
|
||||||
|
- Si OK → "Attribution creee" + EV-10
|
||||||
|
- Si RG-01 KO → "Erreur depassement"
|
||||||
|
- Si RG-02 warning → "Attribution creee" + "Warning capacite"
|
||||||
|
|
||||||
|
### OP-04 — Saisir heures realisees
|
||||||
|
|
||||||
|
- **Entrants** : EV-07 (saisie formateur)
|
||||||
|
- **Synchro** : EV-07 ET attribution.statut IN (`planifie`, `en_cours`)
|
||||||
|
- **Actions** :
|
||||||
|
1. Valider RG-05 : `heures_realisees <= heures_attribuees + tolerance`
|
||||||
|
2. UPDATE attribution.heures_realisees
|
||||||
|
3. Si `heures_realisees == heures_attribuees` → attribution.statut = `realise`
|
||||||
|
4. Declenche EV-10 (rollup recalcule)
|
||||||
|
- **Sortants** : "Heures saisies" + EV-10. Si RG-05 KO → "Erreur depassement heures"
|
||||||
|
|
||||||
|
### OP-06 — Cloturer un module
|
||||||
|
|
||||||
|
- **Entrants** : EV-08 OR EV-13
|
||||||
|
- **Synchro** :
|
||||||
|
- EV-08 (date fin atteinte) ET `module.statut = en_cours`
|
||||||
|
- OU manuel admin via bouton "Marquer realise"
|
||||||
|
- **Actions** :
|
||||||
|
1. Verifier : toutes attributions du module en `realise` ou `annule`
|
||||||
|
2. UPDATE module.statut = `realise`
|
||||||
|
3. Si tous les modules de la formation = `realise` → declenche EV-13
|
||||||
|
- **Sortants** : "Module cloture"
|
||||||
|
|
||||||
|
### OP-07 — Cloturer une formation
|
||||||
|
|
||||||
|
- **Entrants** : EV-13 (tous modules realises) ET formation.date_fin atteinte
|
||||||
|
- **Synchro** : AND des deux
|
||||||
|
- **Actions** : UPDATE formation.statut = `termine`
|
||||||
|
- **Sortants** : "Formation terminee"
|
||||||
|
|
||||||
|
### OP-08 — Recalculer rollups
|
||||||
|
|
||||||
|
- **Entrants** : EV-10 (toute modification de attribution, module, bloc, formation)
|
||||||
|
- **Actions** :
|
||||||
|
1. Recalcul cascadant : attribution → module → bloc → formation
|
||||||
|
2. Recalcul formateur.heures_attribuees / heures_restantes
|
||||||
|
3. Si formateur.heures_restantes < 0 → declenche EV-11
|
||||||
|
- **Sortants** : "Rollups a jour" + EV-11 si depassement
|
||||||
|
|
||||||
|
### OP-09 — Notifier depassement capacite
|
||||||
|
|
||||||
|
- **Entrants** : EV-11
|
||||||
|
- **Actions** :
|
||||||
|
1. Identifier admins du workspace
|
||||||
|
2. Envoyer notification (email + dans-app) avec details
|
||||||
|
- **Sortants** : "Notification envoyee"
|
||||||
|
|
||||||
|
## 5. Diagramme global du flux
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
EV01([EV-01]) --> OP01[OP-01 Creer formation]
|
||||||
|
OP01 --> EV02_PRE([Formation creee])
|
||||||
|
|
||||||
|
EV02_PRE --> OP02[OP-02 Decomposer en blocs/modules]
|
||||||
|
OP02 --> EV_MOD([Modules crees a_attribuer])
|
||||||
|
|
||||||
|
EV_MOD --> OP03[OP-03 Attribuer module]
|
||||||
|
OP03 --> EV_ATTR_OK([Attribution planifie])
|
||||||
|
EV_ATTR_OK --> OP08[OP-08 Recalcul rollups]
|
||||||
|
|
||||||
|
EV06([EV-06 Date debut]) --> EV_EN_COURS([Module en_cours])
|
||||||
|
EV_EN_COURS --> OP04[OP-04 Saisie heures realisees]
|
||||||
|
OP04 --> OP08
|
||||||
|
|
||||||
|
OP04 --> EV_REAL([Module realise])
|
||||||
|
EV_REAL --> OP06[OP-06 Cloturer module]
|
||||||
|
|
||||||
|
OP06 --> EV13([EV-13 Tous modules realises])
|
||||||
|
EV13 --> OP07[OP-07 Cloturer formation]
|
||||||
|
OP07 --> EV_TERM([Formation terminee])
|
||||||
|
|
||||||
|
OP08 --> EV11{Capacite<br/>depassee?}
|
||||||
|
EV11 -->|Oui| OP09[OP-09 Notifier admin]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Operations secondaires
|
||||||
|
|
||||||
|
| Operation | Trigger | Frequence |
|
||||||
|
|-----------|---------|-----------|
|
||||||
|
| OP-11 Generer rapport formation | Demande admin | A la demande |
|
||||||
|
| OP-12 Generer rapport formateur | Demande admin | A la demande |
|
||||||
|
| OP-13 Inviter client | Demande admin | A la demande |
|
||||||
|
| OP-14 Backup | Cron quotidien | 1x/jour |
|
||||||
|
|
||||||
|
## 7. Operations Agence (extension scope B)
|
||||||
|
|
||||||
|
| Code | Operation | Acteur principal | Trigger |
|
||||||
|
|------|-----------|------------------|---------|
|
||||||
|
| OPA-01 | Creer client | Admin | Manuel |
|
||||||
|
| OPA-02 | Creer projet | Admin | Devis signe |
|
||||||
|
| OPA-03 | Decomposer projet en taches | Admin | Apres creation projet |
|
||||||
|
| OPA-04 | Attribuer tache a un developpeur | Admin | Manuel |
|
||||||
|
| OPA-05 | Saisir intervention | Developpeur | Apres travail effectue |
|
||||||
|
| OPA-06 | Marquer tache `done` | Developpeur ou Admin | Validation |
|
||||||
|
| OPA-07 | Cloturer projet | Admin | Toutes taches done + livraison validee |
|
||||||
|
| OPA-08 | Recalculer rollups Agence | System | Sur evenement (intervention modifiee) |
|
||||||
|
| OPA-09 | Lier projet a formation pedagogique | Admin | Optionnel, sur fiche projet |
|
||||||
|
| OPA-10 | Recalculer capacite Personne (formation + agence) | System | Sur evenement (attribution OU intervention modifiee) |
|
||||||
|
|
||||||
|
### OPA-05 — Saisir intervention (developpeur)
|
||||||
|
|
||||||
|
- **Entrants** : Developpeur ouvre l'app + selectionne tache
|
||||||
|
- **Synchro** : Personne.roles contient `developpeur` ET tache.statut != `abandoned/done`
|
||||||
|
- **Actions** :
|
||||||
|
1. Valider `intervention_heures > 0`
|
||||||
|
2. INSERT intervention statut `realise`
|
||||||
|
3. Declenche OPA-08 + OPA-10 (recalcul rollups)
|
||||||
|
- **Sortants** : "Intervention saisie" + "Rollups a jour"
|
||||||
|
|
||||||
|
### OPA-10 — Recalculer capacite Personne unifiee
|
||||||
|
|
||||||
|
C'est une evolution de l'OP-08 du MCT initial. Maintenant que Personne pivot existe :
|
||||||
|
|
||||||
|
```
|
||||||
|
Personne.heures_attribuees_formation = SUM(ATTRIBUTION.heures_attribuees) WHERE personne ET statut != annule
|
||||||
|
Personne.heures_attribuees_agence = SUM(INTERVENTION.heures) WHERE personne ET statut != annule
|
||||||
|
Personne.heures_restantes_total = capacite_annuelle - heures_attribuees_formation - heures_attribuees_agence
|
||||||
|
```
|
||||||
|
|
||||||
|
Si `heures_restantes_total < 0` → declenche notification depassement (OP-09 etendu pour capacite totale).
|
||||||
|
|
||||||
|
## 8. Questions ouvertes (a valider en MOT)
|
||||||
|
|
||||||
|
- [ ] OP-04 (saisie heures) : declenchee quand par le formateur ? En fin de session ? En fin de mois ?
|
||||||
|
- [ ] OP-06/OP-07 : cloture automatique a la date fin OU manuelle apres validation admin ?
|
||||||
|
- [ ] OP-09 (notification depassement) : warning soft ou blocage dur ? Email + Slack/Teams ?
|
||||||
|
- [ ] OP-11/OP-12 (rapports) : format PDF, CSV, Excel ? Push vers comptabilite ?
|
||||||
|
- [ ] OP-08 (rollups) : tolerance de delai acceptable ? Eventual consistency OK ou TR strict ?
|
||||||
186
docs/09-merise-mot.md
Normal file
186
docs/09-merise-mot.md
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
# MOT — Modele Organisationnel de Traitements
|
||||||
|
|
||||||
|
> Vue organisationnelle des operations : QUI fait QUOI, QUAND, COMMENT, AVEC QUEL OUTIL.
|
||||||
|
> Methodologie : Merise. Le MOT prend les operations du MCT et les "concretise" cote organisation.
|
||||||
|
|
||||||
|
## 1. Conventions
|
||||||
|
|
||||||
|
- **Type** :
|
||||||
|
- `M` = Manuel (saisie utilisateur)
|
||||||
|
- `A` = Automatique (declenche par evenement)
|
||||||
|
- `B` = Batch (planifie)
|
||||||
|
- `S` = Semi-auto (validation manuelle d'un calcul auto)
|
||||||
|
- **Temps** :
|
||||||
|
- `TR` = Temps reel (synchrone)
|
||||||
|
- `D` = Differe (asynchrone, < 1min)
|
||||||
|
- `B` = Batch (planifie cron)
|
||||||
|
- **Outil** : composant qui execute (Baserow UI, Bridge, Cron, Docmost, Email, etc.)
|
||||||
|
|
||||||
|
## 2. Tableau MOT consolide
|
||||||
|
|
||||||
|
| Code | Operation (MCT) | Acteur | Type | Temps | Outil | Notes |
|
||||||
|
|------|-----------------|--------|------|-------|-------|-------|
|
||||||
|
| OP-01 | Creer formation | Admin | M | TR | Baserow UI | Formulaire avec validation |
|
||||||
|
| OP-02 | Decomposer en blocs/modules | Admin | M | TR | Baserow UI | Vue hierarchique |
|
||||||
|
| OP-03 | Attribuer module | Admin | M | TR | Baserow UI ou bridge form | Vue kanban "a attribuer" |
|
||||||
|
| OP-04 | Saisir heures realisees | Formateur | M | TR | Bridge UI mobile-friendly | A faire en fin de session ideal |
|
||||||
|
| OP-05 | Annuler attribution | Admin | M | TR | Baserow UI | Avec champ justification obligatoire |
|
||||||
|
| OP-06 | Cloturer module | System + Admin | S | TR | Baserow auto + bouton admin | Auto si toutes attributions realisees |
|
||||||
|
| OP-07 | Cloturer formation | System | A | D | Cron horaire + bridge webhook | Auto si EV-13 + date_fin |
|
||||||
|
| OP-08 | Recalculer rollups | System | A | TR | Baserow rollup natif | Re-calc transparent |
|
||||||
|
| OP-09 | Notifier depassement | System | A | TR | Bridge → SMTP / Slack webhook | Email + canal interne |
|
||||||
|
| OP-10 | Archiver formation | Admin | M | TR | Baserow UI | Action sensible, confirmation requise |
|
||||||
|
| OP-11 | Rapport formation (PDF) | Admin | M | TR | Bridge endpoint /reports/formation/:id | Export PDF |
|
||||||
|
| OP-12 | Rapport formateur (PDF) | Admin | M | TR | Bridge endpoint /reports/formateur/:id | Export PDF |
|
||||||
|
| OP-13 | Inviter client par lien | Admin | M | TR | Docmost share dialog | Lien expirable |
|
||||||
|
| OP-14 | Backup quotidien | System | B | B (03:00) | Cron + Makefile target | pg_dump + tar |
|
||||||
|
|
||||||
|
## 3. Vue par acteur
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A([Admin]) --> OP01[Creer formation]
|
||||||
|
A --> OP02[Decomposer]
|
||||||
|
A --> OP03[Attribuer]
|
||||||
|
A --> OP05[Annuler attribution]
|
||||||
|
A --> OP06b[Cloturer module manuel]
|
||||||
|
A --> OP10[Archiver]
|
||||||
|
A --> OP11[Rapport formation]
|
||||||
|
A --> OP12[Rapport formateur]
|
||||||
|
A --> OP13[Inviter client]
|
||||||
|
```
|
||||||
|
|
||||||
|
Charge typique : ~1-2h / semaine de saisie + lectures rapports a la demande.
|
||||||
|
|
||||||
|
### Formateur
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
F([Formateur]) --> OP04[Saisir heures realisees]
|
||||||
|
F --> OP14b[Consulter ses attributions]
|
||||||
|
F --> OP15[Editer pages wiki autorisees]
|
||||||
|
```
|
||||||
|
|
||||||
|
Charge typique : 5-10 min en fin de chaque session de formation pour saisir les heures.
|
||||||
|
|
||||||
|
### System (auto)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
S([System]) --> OP06a[Cloturer module auto]
|
||||||
|
S --> OP07[Cloturer formation auto]
|
||||||
|
S --> OP08[Recalculer rollups]
|
||||||
|
S --> OP09[Notifier depassement]
|
||||||
|
S --> OP14[Backup quotidien]
|
||||||
|
```
|
||||||
|
|
||||||
|
Charge : transparent. Cron daily backup, webhooks rollups TR.
|
||||||
|
|
||||||
|
## 4. Repartition Outils
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph "Baserow"
|
||||||
|
OP01[OP-01 Creer formation]
|
||||||
|
OP02[OP-02 Decomposer]
|
||||||
|
OP03[OP-03 Attribuer]
|
||||||
|
OP05[OP-05 Annuler]
|
||||||
|
OP08[OP-08 Recalc rollups<br/>natif]
|
||||||
|
end
|
||||||
|
subgraph "Bridge service"
|
||||||
|
OP04[OP-04 Saisie heures<br/>UI mobile]
|
||||||
|
OP07[OP-07 Cloturer formation<br/>cron]
|
||||||
|
OP09[OP-09 Notifier depassement<br/>webhook + SMTP]
|
||||||
|
OP11[OP-11 Rapport formation<br/>PDF]
|
||||||
|
OP12[OP-12 Rapport formateur<br/>PDF]
|
||||||
|
end
|
||||||
|
subgraph "Docmost"
|
||||||
|
OP13[OP-13 Inviter client<br/>share link]
|
||||||
|
OP15[OP-15 Editer wiki]
|
||||||
|
end
|
||||||
|
subgraph "Cron host"
|
||||||
|
OP14[OP-14 Backup quotidien]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Postes de travail / Devices
|
||||||
|
|
||||||
|
| Acteur | Device principal | Browser | Mobile-friendly attendu ? |
|
||||||
|
|--------|------------------|---------|---------------------------|
|
||||||
|
| Admin | Desktop | Firefox / Chrome | Non prioritaire |
|
||||||
|
| Formateur | Mobile + Desktop | Chrome mobile / Firefox | **OUI** — saisie heures rapide en fin de session |
|
||||||
|
| Etudiant | Mobile + Desktop | Tous | **OUI** |
|
||||||
|
| Client | Mobile + Desktop | Tous | OUI |
|
||||||
|
|
||||||
|
**Implication** : le bridge UI pour OP-04 (saisie heures) doit etre **mobile-first**. Baserow's default UI est desktop-oriented — donc soit on fait un formulaire Baserow public, soit on construit un mini-form dans le bridge.
|
||||||
|
|
||||||
|
## 6. Planning des operations automatiques
|
||||||
|
|
||||||
|
| Operation | Frequence | Heure | Owner |
|
||||||
|
|-----------|-----------|-------|-------|
|
||||||
|
| OP-08 Recalcul rollups | Sur evenement TR | — | Baserow natif |
|
||||||
|
| OP-07 Cloturer formation | Toutes les heures | xx:00 | Cron bridge |
|
||||||
|
| OP-09 Notifier depassement | Sur evenement TR | — | Bridge webhook |
|
||||||
|
| OP-14 Backup quotidien | Quotidien | 03:00 | Cron host |
|
||||||
|
| Audit log retention | Mensuel | 1er du mois 04:00 | Cron host |
|
||||||
|
|
||||||
|
## 7. SLA / Contraintes operationnelles
|
||||||
|
|
||||||
|
| Aspect | SLA cible | Justification |
|
||||||
|
|--------|-----------|---------------|
|
||||||
|
| Recalcul rollups | < 5s | UX : eviter latence visible apres saisie |
|
||||||
|
| Backup recovery (RPO) | 24h max | Backup quotidien |
|
||||||
|
| Backup recovery (RTO) | 4h max | Restauration manuelle assistee |
|
||||||
|
| Disponibilite stack | 99% (= 3.65j down/an acceptable) | Pas SI critique, c'est de l'interne |
|
||||||
|
| Latence saisie heures | < 2s | Frustration formateur sinon |
|
||||||
|
|
||||||
|
## 8. Operations Agence (extension scope B)
|
||||||
|
|
||||||
|
| Code | Operation (MCT) | Acteur | Type | Temps | Outil |
|
||||||
|
|------|-----------------|--------|------|-------|-------|
|
||||||
|
| OPA-01 | Creer client | Admin | M | TR | Baserow UI |
|
||||||
|
| OPA-02 | Creer projet | Admin | M | TR | Baserow UI |
|
||||||
|
| OPA-03 | Decomposer en taches | Admin | M | TR | Baserow UI (vue kanban) |
|
||||||
|
| OPA-04 | Attribuer tache a dev | Admin | M | TR | Baserow UI |
|
||||||
|
| OPA-05 | Saisir intervention | Developpeur | M | TR | Bridge UI mobile-friendly |
|
||||||
|
| OPA-06 | Marquer tache done | Dev / Admin | M | TR | Baserow UI ou bridge |
|
||||||
|
| OPA-07 | Cloturer projet | Admin | M | TR | Baserow UI (action sensible) |
|
||||||
|
| OPA-08 | Recalculer rollups Agence | System | A | TR | Baserow rollup natif |
|
||||||
|
| OPA-09 | Lier projet a formation pedago | Admin | M | TR | Baserow UI |
|
||||||
|
| OPA-10 | Recalculer capacite Personne unifiee | System | A | TR | Baserow + bridge si formules complexes |
|
||||||
|
|
||||||
|
### Vue par acteur — Developpeur (nouveau)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
D([Developpeur]) --> OPA05[Saisir intervention]
|
||||||
|
D --> OPA08b[Consulter ses taches]
|
||||||
|
D --> OPA06[Marquer tache done]
|
||||||
|
D --> OPA15[Editer docs technique wiki]
|
||||||
|
```
|
||||||
|
|
||||||
|
Charge typique : 5-10 min par jour de travail pour saisir les heures par tache.
|
||||||
|
|
||||||
|
### SLA Agence
|
||||||
|
|
||||||
|
| Aspect | SLA cible | Justification |
|
||||||
|
|--------|-----------|---------------|
|
||||||
|
| Latence saisie intervention | < 2s | Frustration dev sinon |
|
||||||
|
| Recalcul rollups capacite Personne | < 5s | UX dashboards admin |
|
||||||
|
| Backup Baserow Agence | 24h max | RPO acceptable |
|
||||||
|
|
||||||
|
### Postes de travail Developpeur
|
||||||
|
|
||||||
|
| Acteur | Device principal | Mobile-friendly |
|
||||||
|
|--------|------------------|-----------------|
|
||||||
|
| Developpeur | Desktop (IDE) + Mobile (saisie heures rapide) | OUI |
|
||||||
|
|
||||||
|
## 9. Questions ouvertes pour validation
|
||||||
|
|
||||||
|
- [ ] OP-04 saisie heures : formulaire web bridge OU formulaire public Baserow OU app mobile dediee ?
|
||||||
|
- [ ] OP-09 notification : email ? Slack ? Webhooks vers outil interne ?
|
||||||
|
- [ ] OP-14 backup : sur le meme host ou push S3 distant (Backblaze, OVH Object Storage) ?
|
||||||
|
- [ ] Audit log : duree de retention ? Format (json append-only / table dediee) ?
|
||||||
|
- [ ] Multi-langue UI : aujourd'hui FR seul, mais Baserow et Docmost supportent EN nativement
|
||||||
202
docs/10-state-diagrams.md
Normal file
202
docs/10-state-diagrams.md
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# State Diagrams — Cycle de vie des entites
|
||||||
|
|
||||||
|
> Vue dynamique des entites du domaine. Scope B (CFA + Agence).
|
||||||
|
> Methodologie : UML state machine.
|
||||||
|
|
||||||
|
## 1. Pourquoi des state diagrams
|
||||||
|
|
||||||
|
Les enums `*_statut` du MCD ne disent pas **quelles transitions sont autorisees**. Un cycle de vie explicite :
|
||||||
|
- Donne une regle de gestion claire (RG-XX par transition)
|
||||||
|
- Sert de spec UI (boutons disponibles selon l'etat)
|
||||||
|
- Aide la validation metier
|
||||||
|
- Sert de checklist pour les tests
|
||||||
|
|
||||||
|
# Section CFA
|
||||||
|
|
||||||
|
## 2. FORMATION
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> draft : Creer formation
|
||||||
|
|
||||||
|
draft --> actif : Activer (au moins 1 bloc + 1 module)
|
||||||
|
draft --> archive : Annuler avant lancement
|
||||||
|
|
||||||
|
actif --> termine : Date fin atteinte ET tous modules realises
|
||||||
|
actif --> archive : Annulation justifiee
|
||||||
|
|
||||||
|
termine --> archive : Archiver retention reglementaire
|
||||||
|
|
||||||
|
archive --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **RG-FORMATION-01** : `draft → actif` autorise uniquement si au moins 1 bloc avec au moins 1 module.
|
||||||
|
- **RG-FORMATION-02** : `actif → termine` automatique si `formation_date_fin <= NOW()` ET tous les modules en `realise` ou `annule`.
|
||||||
|
|
||||||
|
## 3. MODULE
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> a_attribuer : Creer module
|
||||||
|
|
||||||
|
a_attribuer --> attribue : Premiere attribution creee
|
||||||
|
a_attribuer --> annule : Module supprime
|
||||||
|
|
||||||
|
attribue --> en_cours : Date debut formation atteinte
|
||||||
|
attribue --> a_attribuer : Toutes attributions annulees
|
||||||
|
attribue --> annule
|
||||||
|
|
||||||
|
en_cours --> realise : Toutes heures realisees OU date fin
|
||||||
|
en_cours --> annule
|
||||||
|
|
||||||
|
realise --> [*]
|
||||||
|
annule --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. ATTRIBUTION (Module-Personne formateur)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> planifie : Admin attribue module
|
||||||
|
|
||||||
|
planifie --> en_cours : Date debut OU formateur demarre
|
||||||
|
planifie --> annule
|
||||||
|
|
||||||
|
en_cours --> realise : Formateur saisit heures et confirme
|
||||||
|
en_cours --> annule
|
||||||
|
|
||||||
|
realise --> [*]
|
||||||
|
annule --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Section AGENCE
|
||||||
|
|
||||||
|
## 5. CLIENT
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> prospect : Premier contact
|
||||||
|
|
||||||
|
prospect --> actif : Devis signe
|
||||||
|
prospect --> archive : Pas de suite
|
||||||
|
|
||||||
|
actif --> inactif : Pas de projet en cours
|
||||||
|
inactif --> actif : Reactivation
|
||||||
|
|
||||||
|
actif --> archive : Cessation activite
|
||||||
|
inactif --> archive : Idem
|
||||||
|
|
||||||
|
archive --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **RG-CLIENT-01** : Suppression interdite tant que `projet_statut != cloture/abandonne` exists.
|
||||||
|
|
||||||
|
## 6. PROJET
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> devis : Demande client recue
|
||||||
|
|
||||||
|
devis --> en_cours : Devis signe et facture
|
||||||
|
devis --> abandonne : Pas de suite client
|
||||||
|
|
||||||
|
en_cours --> livre : Tous livrables/taches done
|
||||||
|
en_cours --> abandonne : Annulation client ou impossibilite
|
||||||
|
|
||||||
|
livre --> cloture : Validation client + facture finale
|
||||||
|
livre --> en_cours : Reouverture pour corrections
|
||||||
|
|
||||||
|
cloture --> [*]
|
||||||
|
abandonne --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **RG-PROJET-01** : `devis → en_cours` necessite signature client (champ `projet_date_debut` rempli).
|
||||||
|
- **RG-PROJET-02** : `en_cours → livre` automatique quand toutes les taches sont en `done`.
|
||||||
|
|
||||||
|
## 7. TACHE
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> todo : Creer tache
|
||||||
|
|
||||||
|
todo --> in_progress : Dev demarre
|
||||||
|
todo --> abandoned : Tache annulee
|
||||||
|
|
||||||
|
in_progress --> review : Dev marque pret pour review
|
||||||
|
in_progress --> abandoned
|
||||||
|
|
||||||
|
review --> done : Admin valide
|
||||||
|
review --> in_progress : Demande revisions
|
||||||
|
|
||||||
|
done --> [*]
|
||||||
|
abandoned --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **RG-TACHE-01** : `review` est optionnel (workflow facultatif). Pour les petites taches, `in_progress → done` direct possible.
|
||||||
|
|
||||||
|
## 8. INTERVENTION (Tache-Personne developpeur)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> planifie : Intervention prevue (rare)
|
||||||
|
[*] --> realise : Saisie a posteriori (cas standard)
|
||||||
|
|
||||||
|
planifie --> realise : Dev confirme execution
|
||||||
|
planifie --> annule
|
||||||
|
|
||||||
|
realise --> annule : Annulation justifiee (rare)
|
||||||
|
|
||||||
|
realise --> [*]
|
||||||
|
annule --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note : la plupart des INTERVENTIONS sont saisies a posteriori (dev fait le travail, puis loggue les heures). Le statut `planifie` est rare, utilise pour reservations bloquantes.
|
||||||
|
|
||||||
|
# Section COMMUNS
|
||||||
|
|
||||||
|
## 9. PERSONNE
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> actif : Embauche / arrivee
|
||||||
|
|
||||||
|
actif --> inactif : Depart, suspension, vacances longues
|
||||||
|
|
||||||
|
inactif --> actif : Retour
|
||||||
|
|
||||||
|
inactif --> [*] : Suppression\n(interdite si attributions/interventions actives)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **RG-PERSONNE-05** : `actif → inactif` autorise meme avec attributions/interventions actives, mais bloque les **nouvelles** assignations.
|
||||||
|
- **RG-PERSONNE-06** : Suppression interdite si `personne_id` existe dans ATTRIBUTION ou INTERVENTION non-`annule` (FK ON DELETE RESTRICT).
|
||||||
|
|
||||||
|
# Synthese
|
||||||
|
|
||||||
|
| Entite | Etats | Evenements declencheurs |
|
||||||
|
|--------|-------|------------------------|
|
||||||
|
| FORMATION | draft, actif, termine, archive | Creation, activation, date fin, archivage |
|
||||||
|
| BLOC | (sans cycle propre) | suit la formation |
|
||||||
|
| MODULE | a_attribuer, attribue, en_cours, realise, annule | Attribution, demarrage, completion, annulation |
|
||||||
|
| ATTRIBUTION | planifie, en_cours, realise, annule | Attribution, demarrage, saisie heures, annulation |
|
||||||
|
| CLIENT | prospect, actif, inactif, archive | Devis signe, projets actifs, cessation |
|
||||||
|
| PROJET | devis, en_cours, livre, cloture, abandonne | Signature, livraison, validation client, annulation |
|
||||||
|
| TACHE | todo, in_progress, review, done, abandoned | Demarrage, soumission review, validation, annulation |
|
||||||
|
| INTERVENTION | planifie, realise, annule | Saisie a posteriori (cas standard), annulation rare |
|
||||||
|
| PERSONNE | actif, inactif | Embauche, depart, retour |
|
||||||
|
|
||||||
|
# Implementation
|
||||||
|
|
||||||
|
| Niveau | Implementation |
|
||||||
|
|--------|----------------|
|
||||||
|
| MLD | Champs `*_statut` typed ENUM avec valeurs limitees |
|
||||||
|
| MPD Baserow | `Single select` avec options exactement = etats |
|
||||||
|
| Logique transition | Bridge service : valide les transitions autorisees, refuse les illegales |
|
||||||
|
| UI | Boutons d'action conditionnels selon etat courant |
|
||||||
|
| Audit | Log par transition `(entity_id, from_state, to_state, by_user, at_timestamp, reason)` |
|
||||||
|
| Auto | Cron horaire pour transitions auto (date fin → termine, etc.) |
|
||||||
|
|
||||||
|
# Questions ouvertes
|
||||||
|
|
||||||
|
- [ ] Faut-il un audit log explicite des transitions (qui a annule quoi quand) ? Probable oui pour Qualiopi.
|
||||||
|
- [ ] Les automatisations de transition doivent-elles tourner en cron, en webhook Baserow, ou a la lecture (lazy) ?
|
||||||
|
- [ ] Faut-il un etat `en_validation` entre `realise` et "facture client" pour PROJET (workflow de validation comptable) ?
|
||||||
240
docs/11-uml-use-cases.md
Normal file
240
docs/11-uml-use-cases.md
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
# UML — Use Cases
|
||||||
|
|
||||||
|
> Vue UML des acteurs et de leurs cas d'usage. Scope B (CFA + Agence + Pivot Personne).
|
||||||
|
> Methodologie : Merise pour les donnees, UML pour les comportements et acteurs.
|
||||||
|
|
||||||
|
## 1. Acteurs
|
||||||
|
|
||||||
|
| Acteur | Role(s) Personne | Privileges principaux |
|
||||||
|
|--------|------------------|----------------------|
|
||||||
|
| **Admin** (direction, Yan, Corentin) | `admin`, `direction` | Plein controle CFA + Agence + Operations |
|
||||||
|
| **Formateur** | `formateur` (peut cumuler `developpeur`) | Vue ses attributions, saisit heures realisees, edite pages wiki |
|
||||||
|
| **Developpeur** | `developpeur` (peut cumuler `formateur`) | Vue ses interventions, saisit heures sur taches, edite docs technique |
|
||||||
|
| **Etudiant** | (hors modele Baserow) | Acces space personnel Docmost + lecture supports formation publies |
|
||||||
|
| **Client** (guest) | (hors modele Baserow) | Lecture lien partage uniquement |
|
||||||
|
| **System** | (acteur secondaire) | Calculs auto, notifications, backups |
|
||||||
|
|
||||||
|
**Note PERSONNE pivot** : un meme individu peut cumuler plusieurs roles (ex: Yan = formateur + developpeur + admin). Sa capacite annuelle est splittee.
|
||||||
|
|
||||||
|
## 2. Diagrammes use case (par domaine)
|
||||||
|
|
||||||
|
Splitte en 4 sous-diagrammes pour eviter le spaghetti : CFA, Agence, Cross/Wiki, System.
|
||||||
|
|
||||||
|
### 2.1 Use cases CFA
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Admin([Admin])
|
||||||
|
Formateur([Formateur])
|
||||||
|
|
||||||
|
Admin --> UC01[Creer formation]
|
||||||
|
Admin --> UC02[Decomposer blocs/modules]
|
||||||
|
Admin --> UC03[Attribuer module formateur]
|
||||||
|
Admin --> UC04[Reattribuer/annuler]
|
||||||
|
Admin --> UC05[Consulter heures formation]
|
||||||
|
Admin --> UC06[Consulter capacite formateur]
|
||||||
|
Admin --> UC07[Rapport formation]
|
||||||
|
|
||||||
|
Formateur --> UC03
|
||||||
|
Formateur --> UC13[Saisir heures realisees]
|
||||||
|
Formateur --> UC14[Consulter ses attributions]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Use cases Agence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Admin([Admin])
|
||||||
|
Dev([Developpeur])
|
||||||
|
|
||||||
|
Admin --> UCA01[Creer client]
|
||||||
|
Admin --> UCA02[Creer projet]
|
||||||
|
Admin --> UCA03[Decomposer en taches]
|
||||||
|
Admin --> UCA04[Attribuer tache dev]
|
||||||
|
Admin --> UCA05[Consulter avancement projet]
|
||||||
|
Admin --> UCA06[Cloturer projet et facturer]
|
||||||
|
|
||||||
|
Dev --> UCA07[Saisir intervention]
|
||||||
|
Dev --> UCA08[Consulter ses taches]
|
||||||
|
Dev --> UCA09[Marquer tache done]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Use cases Cross / Wiki
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Admin([Admin])
|
||||||
|
Formateur([Formateur])
|
||||||
|
Dev([Developpeur])
|
||||||
|
Etudiant([Etudiant])
|
||||||
|
Client([Client guest])
|
||||||
|
|
||||||
|
Admin --> UCX01[Lier projet a formation pedagogique]
|
||||||
|
Admin --> UCX02[Consulter capacite totale Personne]
|
||||||
|
Admin --> UCX03[Ajuster split formation/agence]
|
||||||
|
Admin --> UCW01[Gerer wiki + droits]
|
||||||
|
Admin --> UCW02[Inviter client par lien partage]
|
||||||
|
Admin --> UCW03[Creer space etudiant]
|
||||||
|
|
||||||
|
Formateur --> UCW04[Editer pages wiki]
|
||||||
|
Dev --> UCW04
|
||||||
|
|
||||||
|
Etudiant --> UCW05[Acces space personnel]
|
||||||
|
Etudiant --> UCW06[Lire supports formation]
|
||||||
|
Etudiant --> UCW07[Editer ses notes]
|
||||||
|
|
||||||
|
Client --> UCW08[Lire page partagee]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Use cases System (auto)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
System([System])
|
||||||
|
|
||||||
|
System --> UCS01[Recalculer rollups]
|
||||||
|
System --> UCS02[Notifier depassement capacite]
|
||||||
|
System --> UCS03[Cloturer modules/projets auto]
|
||||||
|
System --> UCS04[Backup quotidien]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Use cases CFA detailles (top prioritaires)
|
||||||
|
|
||||||
|
### UC-01 — Creer une formation
|
||||||
|
|
||||||
|
- **Acteur** : Admin
|
||||||
|
- **Pre-conditions** : Admin authentifie
|
||||||
|
- **Scenario** :
|
||||||
|
1. Admin ouvre vue "Formations"
|
||||||
|
2. Clique "Nouvelle formation"
|
||||||
|
3. Renseigne nom, filiere, heures totales, dates, statut = `draft`
|
||||||
|
4. Sauvegarde
|
||||||
|
- **Post-condition** : Formation creee, `heures_attribuees = 0`, statut `draft`
|
||||||
|
|
||||||
|
### UC-03 — Attribuer module a un formateur
|
||||||
|
|
||||||
|
- **Acteur** : Admin (ou Formateur acceptant attribution proposee)
|
||||||
|
- **Pre-conditions** : Module existe (`module_statut != annule`), Personne avec role `formateur` et statut `actif`
|
||||||
|
- **Scenario** :
|
||||||
|
1. Admin selectionne un module dans kanban "a attribuer"
|
||||||
|
2. Choisit Personne dans la liste (filtre par `roles contient formateur` et `heures_restantes_formation >= heures_required`)
|
||||||
|
3. Saisit heures + dates
|
||||||
|
4. Confirme → ATTRIBUTION creee statut `planifie`
|
||||||
|
5. System recalcule rollups (module, bloc, formation, personne)
|
||||||
|
- **RG** : RG-01 module heures, RG-02 capacite formateur (warning), RG-PERSONNE-02 role formateur requis
|
||||||
|
|
||||||
|
### UC-13 — Saisir heures realisees (cours)
|
||||||
|
|
||||||
|
- **Acteur** : Formateur (Personne avec role `formateur`)
|
||||||
|
- **Pre-conditions** : Formateur authentifie, attribution active (`planifie` ou `en_cours`)
|
||||||
|
- **Scenario** :
|
||||||
|
1. Formateur ouvre app mobile-friendly "Mes attributions"
|
||||||
|
2. Selectionne attribution du jour
|
||||||
|
3. Saisit `attribution_heures_realisees`
|
||||||
|
4. Confirme
|
||||||
|
|
||||||
|
## 4. Use cases AGENCE detailles (nouveau)
|
||||||
|
|
||||||
|
### UCA-02 — Creer un projet client
|
||||||
|
|
||||||
|
- **Acteur** : Admin
|
||||||
|
- **Pre-conditions** : Admin authentifie. Client existe (sinon UCA-01 d'abord).
|
||||||
|
- **Scenario** :
|
||||||
|
1. Admin ouvre vue "Projets" filtree par client
|
||||||
|
2. Clique "Nouveau projet"
|
||||||
|
3. Renseigne nom, type (site_web/app/api/...), description, charge estimee, dates
|
||||||
|
4. Optionnel : lie a une FORMATION si projet pedagogique
|
||||||
|
5. Statut = `devis`
|
||||||
|
6. Sauvegarde
|
||||||
|
- **Post-condition** : Projet cree, `heures_realisees = 0`
|
||||||
|
|
||||||
|
### UCA-04 — Attribuer une tache a un developpeur
|
||||||
|
|
||||||
|
- **Acteur** : Admin (ou developpeur s'auto-attribuant si modele permissif)
|
||||||
|
- **Pre-conditions** : Tache existe statut `todo`, Personne avec role `developpeur` actif
|
||||||
|
- **Scenario** :
|
||||||
|
1. Admin ouvre kanban projet, vue par statut
|
||||||
|
2. Drag tache de `todo` vers une swimlane Personne
|
||||||
|
3. (Pas d'INTERVENTION cree a l'attribution — l'INTERVENTION sera creee a chaque saisie d'heures)
|
||||||
|
- **Post-condition** : Tache associee informellement a un dev (via property "assignee" sur la tache), prete pour saisie INTERVENTION
|
||||||
|
|
||||||
|
> Note : la tache n'a pas de FK directe vers la personne — le lien dev-tache se fait via les INTERVENTIONS. L'assignment "logique" peut etre une property optionnelle sur tache (`tache_assignee_id`) si on veut tracer une assignation avant saisie d'heures.
|
||||||
|
|
||||||
|
### UCA-07 — Saisir une intervention
|
||||||
|
|
||||||
|
- **Acteur** : Developpeur
|
||||||
|
- **Pre-conditions** : Developpeur authentifie, tache existe
|
||||||
|
- **Scenario** :
|
||||||
|
1. Dev ouvre app mobile-friendly "Mes taches"
|
||||||
|
2. Selectionne tache du jour
|
||||||
|
3. Saisit nombre d'heures + date + notes optionnelles (commit ref, lien PR)
|
||||||
|
4. Confirme → INTERVENTION creee statut `realise`
|
||||||
|
5. System recalcule rollups (tache, projet, personne)
|
||||||
|
- **RG** : `intervention_heures > 0`, RG-PERSONNE-03 role developpeur requis
|
||||||
|
|
||||||
|
### UCA-09 — Marquer une tache comme done
|
||||||
|
|
||||||
|
- **Acteur** : Developpeur (ou Admin)
|
||||||
|
- **Scenario** :
|
||||||
|
1. Dev marque tache comme `review` ou `done`
|
||||||
|
2. Si admin valide review, tache passe `done`
|
||||||
|
- **Workflow** : optionnel d'avoir un statut intermediate `review` pour validation admin
|
||||||
|
|
||||||
|
## 5. Use cases CROSS (Personne pivot)
|
||||||
|
|
||||||
|
### UCX-02 — Consulter capacite totale d'une Personne
|
||||||
|
|
||||||
|
- **Acteur** : Admin
|
||||||
|
- **Pre-conditions** : Personne existe
|
||||||
|
- **Scenario** :
|
||||||
|
1. Admin ouvre fiche Personne
|
||||||
|
2. Voit dashboard :
|
||||||
|
- Capacite totale annuelle : 1500h
|
||||||
|
- Split formation/agence : 50/50
|
||||||
|
- Heures attribuees formation : 400h (sur 750 alloues)
|
||||||
|
- Heures attribuees agence : 600h (sur 750 alloues)
|
||||||
|
- Heures restantes total : 500h
|
||||||
|
- Liste de ses attributions formation
|
||||||
|
- Liste de ses interventions agence
|
||||||
|
- **Post-condition** : vue 360 sur la charge d'une personne
|
||||||
|
|
||||||
|
### UCX-03 — Ajuster split formation/agence
|
||||||
|
|
||||||
|
- **Acteur** : Admin
|
||||||
|
- **Scenario** :
|
||||||
|
1. Admin edite fiche Personne
|
||||||
|
2. Modifie `split_formation_pct` / `split_agence_pct`
|
||||||
|
3. CHECK : somme = 100
|
||||||
|
4. Sauvegarde → recalcul `heures_restantes_formation` et `heures_restantes_agence`
|
||||||
|
- **Cas d'usage typique** : Yan a 60% formation, 40% agence en periode peda intense, puis 30% formation, 70% agence pendant les vacances scolaires.
|
||||||
|
|
||||||
|
## 6. Diagramme de sequence — UCA-07 (saisir intervention)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor Dev as Developpeur
|
||||||
|
participant UI as Bridge UI mobile
|
||||||
|
participant API as Bridge API
|
||||||
|
participant Baserow
|
||||||
|
participant DB as Baserow Engine
|
||||||
|
|
||||||
|
Dev->>UI: Saisit heures sur tache T
|
||||||
|
UI->>API: POST /interventions
|
||||||
|
API->>API: Valide heures > 0, role dev OK
|
||||||
|
API->>Baserow: POST /database/rows/intervention
|
||||||
|
Baserow->>DB: INSERT intervention
|
||||||
|
DB->>DB: Recalcul rollups (tache, projet, personne)
|
||||||
|
DB-->>Baserow: ok
|
||||||
|
Baserow-->>API: 201 row
|
||||||
|
API-->>UI: 201 ok
|
||||||
|
UI-->>Dev: Toast confirmation + nouvelle capacite restante
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Cas d'usage hors-scope (a trancher)
|
||||||
|
|
||||||
|
- Workflow d'approbation des heures realisees par admin avant facturation
|
||||||
|
- Notification automatique formateur/dev quand tache attribuee
|
||||||
|
- Generation de feuilles de presence PDF
|
||||||
|
- Integration calendrier (iCal export attributions/taches)
|
||||||
|
- Gestion des conges/indispos qui reduisent la capacite (calendrier integre)
|
||||||
|
- API publique pour clients (visualiser leurs projets en mode auto-service)
|
||||||
289
docs/12-uml-class-diagram.md
Normal file
289
docs/12-uml-class-diagram.md
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
# UML Class Diagram
|
||||||
|
|
||||||
|
> Vue orientee objet du modele. Scope B (CFA + Agence + Personne pivot).
|
||||||
|
> Apporte les **methodes** que le MCD ne montre pas. Pont entre modele de donnees et code du bridge service Phase 2.
|
||||||
|
|
||||||
|
## 1. Pourquoi un class diagram en plus du MCD
|
||||||
|
|
||||||
|
Le MCD montre les **donnees** (entites + attributs + relations). Le class diagram montre :
|
||||||
|
- Les **methodes** sur chaque classe
|
||||||
|
- La **visibilite** (public/private/protected)
|
||||||
|
- Les **types de relations OO** (composition, agregation, association)
|
||||||
|
- Les patterns applicables
|
||||||
|
|
||||||
|
## 2. Diagrammes par zone
|
||||||
|
|
||||||
|
Splitte en 3 sous-vues : CFA, Agence, Personne pivot. Plus un diagramme global simplifie pour la vue d'ensemble.
|
||||||
|
|
||||||
|
### 2.1 Vue globale (relations seules)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
Personne -- Attribution : "role formateur"
|
||||||
|
Personne -- Intervention : "role developpeur"
|
||||||
|
Formation *-- Bloc
|
||||||
|
Bloc *-- Module
|
||||||
|
Module -- Attribution
|
||||||
|
Client -- Projet
|
||||||
|
Projet *-- Tache
|
||||||
|
Tache -- Intervention
|
||||||
|
Projet -- Formation : "projet pedagogique"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Zone CFA — classes detaillees
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Formation {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+Filiere filiere
|
||||||
|
+decimal heuresTotales
|
||||||
|
-decimal heuresAttribuees$
|
||||||
|
+Statut statut
|
||||||
|
+activer() void
|
||||||
|
+archiver() void
|
||||||
|
+ajouterBloc(Bloc) void
|
||||||
|
+heuresRestantes() decimal
|
||||||
|
+rapportPDF() Buffer
|
||||||
|
}
|
||||||
|
class Bloc {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+decimal heuresPrevues
|
||||||
|
-decimal heuresAttribuees$
|
||||||
|
+ajouterModule(Module) void
|
||||||
|
+heuresRestantes() decimal
|
||||||
|
}
|
||||||
|
class Module {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+decimal heuresPrevues
|
||||||
|
-decimal heuresAttribuees$
|
||||||
|
-decimal heuresRealisees$
|
||||||
|
+Statut statut
|
||||||
|
+creerAttribution(Personne, decimal, Date, Date) Attribution
|
||||||
|
+annuler() void
|
||||||
|
+cloturer() void
|
||||||
|
}
|
||||||
|
class Attribution {
|
||||||
|
+int id
|
||||||
|
+decimal heuresAttribuees
|
||||||
|
+decimal heuresRealisees
|
||||||
|
+Statut statut
|
||||||
|
+demarrer() void
|
||||||
|
+saisirHeuresRealisees(decimal) void
|
||||||
|
+cloturer() void
|
||||||
|
+annuler(string) void
|
||||||
|
}
|
||||||
|
|
||||||
|
Formation "1" *-- "1..*" Bloc : composition
|
||||||
|
Bloc "1" *-- "1..*" Module : composition
|
||||||
|
Module "1" -- "0..*" Attribution : association
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Zone Agence — classes detaillees
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Client {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+string contactPrincipal
|
||||||
|
+Email contactEmail
|
||||||
|
+Statut statut
|
||||||
|
+creerProjet(string) Projet
|
||||||
|
+archiver() void
|
||||||
|
}
|
||||||
|
class Projet {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+Type type
|
||||||
|
+decimal chargeHeures
|
||||||
|
-decimal heuresRealisees$
|
||||||
|
+Statut statut
|
||||||
|
+ajouterTache(string, decimal) Tache
|
||||||
|
+lierFormationPedagogique(Formation) void
|
||||||
|
+livrer() void
|
||||||
|
+cloturer() void
|
||||||
|
+rapportPDF() Buffer
|
||||||
|
}
|
||||||
|
class Tache {
|
||||||
|
+int id
|
||||||
|
+string titre
|
||||||
|
+decimal chargeHeures
|
||||||
|
-decimal heuresRealisees$
|
||||||
|
+Priorite priorite
|
||||||
|
+Statut statut
|
||||||
|
+creerIntervention(Personne, decimal, Date) Intervention
|
||||||
|
+marquerInProgress() void
|
||||||
|
+marquerReview() void
|
||||||
|
+marquerDone() void
|
||||||
|
}
|
||||||
|
class Intervention {
|
||||||
|
+int id
|
||||||
|
+decimal heures
|
||||||
|
+Date date
|
||||||
|
+Statut statut
|
||||||
|
+annuler(string) void
|
||||||
|
}
|
||||||
|
|
||||||
|
Client "1" -- "1..*" Projet : association
|
||||||
|
Projet "1" *-- "0..*" Tache : composition
|
||||||
|
Tache "1" -- "0..*" Intervention : association
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Zone Personne pivot — classe + roles
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Personne {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+string prenom
|
||||||
|
+Email email
|
||||||
|
+decimal capaciteAnnuelle
|
||||||
|
+decimal splitFormationPct
|
||||||
|
+decimal splitAgencePct
|
||||||
|
+Set~Role~ roles
|
||||||
|
+Statut statut
|
||||||
|
-decimal heuresAttribueesFormation$
|
||||||
|
-decimal heuresAttribueesAgence$
|
||||||
|
+heuresRestantesFormation() decimal
|
||||||
|
+heuresRestantesAgence() decimal
|
||||||
|
+heuresRestantesTotal() decimal
|
||||||
|
+ajouterRole(Role) void
|
||||||
|
+retirerRole(Role) void
|
||||||
|
+activer() void
|
||||||
|
+inactiver() void
|
||||||
|
+rapportPDF() Buffer
|
||||||
|
}
|
||||||
|
class Attribution {
|
||||||
|
+int id
|
||||||
|
+decimal heuresAttribuees
|
||||||
|
+Statut statut
|
||||||
|
}
|
||||||
|
class Intervention {
|
||||||
|
+int id
|
||||||
|
+decimal heures
|
||||||
|
+Statut statut
|
||||||
|
}
|
||||||
|
|
||||||
|
Personne "1" -- "0..*" Attribution : "role formateur"
|
||||||
|
Personne "1" -- "0..*" Intervention : "role developpeur"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Lien pedagogique cross-zone
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Projet {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
+lierFormationPedagogique(Formation) void
|
||||||
|
}
|
||||||
|
class Formation {
|
||||||
|
+int id
|
||||||
|
+string nom
|
||||||
|
}
|
||||||
|
Projet "0..*" -- "0..1" Formation : "projet pedagogique"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notation** :
|
||||||
|
- `+` public, `-` private, `#` protected
|
||||||
|
- `$` champ derive/calcule (rollup ou formula, pas stocke directement)
|
||||||
|
- `*--` composition (cycle de vie partage)
|
||||||
|
- `--` association simple
|
||||||
|
|
||||||
|
## 3. Methodes detaillees — Personne
|
||||||
|
|
||||||
|
| Methode | Signature | Description |
|
||||||
|
|---------|-----------|-------------|
|
||||||
|
| `heuresRestantesFormation()` | `decimal` | `(capacite * split_formation_pct/100) - heures_attribuees_formation` |
|
||||||
|
| `heuresRestantesAgence()` | `decimal` | `(capacite * split_agence_pct/100) - heures_attribuees_agence` |
|
||||||
|
| `heuresRestantesTotal()` | `decimal` | `capacite - heures_attribuees_formation - heures_attribuees_agence` |
|
||||||
|
| `ajouterRole(role)` | `Role → void` | Ajoute role aux roles existants. Idempotent. |
|
||||||
|
| `retirerRole(role)` | `Role → void` | Retire role. Verifie qu'aucune attribution/intervention active n'utilise ce role. |
|
||||||
|
| `activer()` | `void` | Statut → actif |
|
||||||
|
| `inactiver()` | `void` | Statut → inactif. Bloque nouvelles assignations. |
|
||||||
|
|
||||||
|
## 4. Methodes detaillees — Module / Tache
|
||||||
|
|
||||||
|
### Module.creerAttribution(personne, heures, dateDebut, dateFin)
|
||||||
|
|
||||||
|
Verifications :
|
||||||
|
- `personne.roles.contains(Role.formateur)` — sinon throw
|
||||||
|
- `RG-01` : `SUM(this.attributions.heures) + heures <= this.heuresPrevues`
|
||||||
|
- Warning si `heures > personne.heuresRestantesFormation()`
|
||||||
|
|
||||||
|
Effet : INSERT attribution + recalcul rollups en cascade.
|
||||||
|
|
||||||
|
### Tache.creerIntervention(personne, heures, date)
|
||||||
|
|
||||||
|
Verifications :
|
||||||
|
- `personne.roles.contains(Role.developpeur)` — sinon throw
|
||||||
|
- `heures > 0`
|
||||||
|
- `personne.statut == actif`
|
||||||
|
|
||||||
|
Effet : INSERT intervention + recalcul rollups (tache, projet, personne).
|
||||||
|
|
||||||
|
## 5. Patterns OO appliques
|
||||||
|
|
||||||
|
| Pattern | Ou | Pourquoi |
|
||||||
|
|---------|-----|----------|
|
||||||
|
| **Value Object** | Email, Decimal heures, Filiere, Type, Priorite | Immutables, validation a la construction |
|
||||||
|
| **State Pattern** | Statut sur toutes les entites avec cycle de vie | Encapsule transitions valides |
|
||||||
|
| **Repository** | PersonneRepo, ProjetRepo, etc. | Abstrait l'acces Baserow API |
|
||||||
|
| **Factory** | Module.creerAttribution(), Tache.creerIntervention() | Encapsule logique creation + validations |
|
||||||
|
| **Observer** | Webhooks Baserow → bridge listeners | Evenements rollup → recalculs |
|
||||||
|
| **Strategy** | Calcul capacite Personne (split formation/agence) | Permet de varier la regle (ex: split par periode) |
|
||||||
|
|
||||||
|
## 6. Mapping vers le code du bridge service (Phase 2)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// bridge/src/domain/personne.ts
|
||||||
|
import { Decimal } from 'decimal.js';
|
||||||
|
|
||||||
|
export type Role = 'formateur' | 'developpeur' | 'admin' | 'direction' | 'support';
|
||||||
|
|
||||||
|
export class Personne {
|
||||||
|
constructor(
|
||||||
|
public readonly id: number,
|
||||||
|
public nom: string,
|
||||||
|
public prenom: string,
|
||||||
|
public email: string,
|
||||||
|
public capaciteAnnuelle: Decimal,
|
||||||
|
public splitFormationPct: Decimal,
|
||||||
|
public splitAgencePct: Decimal,
|
||||||
|
public roles: Set<Role>,
|
||||||
|
public statut: 'actif' | 'inactif',
|
||||||
|
private _heuresAttribueesFormation: Decimal,
|
||||||
|
private _heuresAttribueesAgence: Decimal,
|
||||||
|
) {
|
||||||
|
if (!this.splitFormationPct.plus(this.splitAgencePct).equals(100)) {
|
||||||
|
throw new Error('Splits doivent sommer a 100');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heuresRestantesFormation(): Decimal {
|
||||||
|
const alloue = this.capaciteAnnuelle.times(this.splitFormationPct).div(100);
|
||||||
|
return alloue.minus(this._heuresAttribueesFormation);
|
||||||
|
}
|
||||||
|
heuresRestantesAgence(): Decimal {
|
||||||
|
const alloue = this.capaciteAnnuelle.times(this.splitAgencePct).div(100);
|
||||||
|
return alloue.minus(this._heuresAttribueesAgence);
|
||||||
|
}
|
||||||
|
heuresRestantesTotal(): Decimal {
|
||||||
|
return this.capaciteAnnuelle.minus(this._heuresAttribueesFormation).minus(this._heuresAttribueesAgence);
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Limites du class diagram
|
||||||
|
|
||||||
|
- Ne montre pas la persistence (deja fait par MLD)
|
||||||
|
- Ne montre pas les sequences (deja fait par UML use cases sequence diagrams)
|
||||||
|
- Redondant avec MCD sur les attributs simples
|
||||||
|
|
||||||
|
C'est intentionnel : chaque vue eclaire un angle different. Le class diagram = angle **comportemental statique cote code**.
|
||||||
192
docs/13-uml-activity-diagrams.md
Normal file
192
docs/13-uml-activity-diagrams.md
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
# UML — Activity Diagrams
|
||||||
|
|
||||||
|
> Vue dynamique des **workflows complets** (parcours utilisateur de bout en bout).
|
||||||
|
> Complement aux state diagrams (cycle de vie d'une entite) et au MCT (operations isolees).
|
||||||
|
> Chaque diagramme = un parcours metier qui traverse plusieurs entites et acteurs.
|
||||||
|
|
||||||
|
## 1. AD-01 — Attribuer un module a un formateur
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Admin demande attribution]) --> A1[Selectionner module a attribuer]
|
||||||
|
A1 --> A2{Module existe<br/>statut != annule?}
|
||||||
|
A2 -->|Non| Err1([Erreur: module invalide])
|
||||||
|
A2 -->|Oui| A3[Lister formateurs avec capacite >= heures requises]
|
||||||
|
A3 --> A4{Au moins 1<br/>formateur dispo?}
|
||||||
|
A4 -->|Non| Err2([Pas de formateur dispo - ajouter capacite ou reduire heures])
|
||||||
|
A4 -->|Oui| A5[Admin choisit formateur]
|
||||||
|
A5 --> A6[Saisir heures + dates]
|
||||||
|
A6 --> A7{Heures attribution +<br/>existantes <= heures module?}
|
||||||
|
A7 -->|Non| Err3([RG-01 KO: depassement heures module])
|
||||||
|
A7 -->|Oui| A8{Heures + capacite<br/>existante > capacite annuelle?}
|
||||||
|
A8 -->|Oui| Warn[Warning: depassement capacite formateur]
|
||||||
|
A8 -->|Non| A9
|
||||||
|
Warn --> A9[Confirmer attribution]
|
||||||
|
A9 --> A10[INSERT attribution statut=planifie]
|
||||||
|
A10 --> A11[Recalculer rollups en cascade]
|
||||||
|
A11 --> A12[Notifier formateur par email]
|
||||||
|
A12 --> End([Attribution confirmee])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. AD-02 — Saisir heures realisees (parcours formateur)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Formateur fin de cours]) --> B1[Ouvre app sur mobile]
|
||||||
|
B1 --> B2{Authentifie?}
|
||||||
|
B2 -->|Non| B3[Login SSO ou email/password]
|
||||||
|
B3 --> B4
|
||||||
|
B2 -->|Oui| B4[Affiche liste de mes attributions actives]
|
||||||
|
B4 --> B5[Selectionne l'attribution du jour]
|
||||||
|
B5 --> B6[Saisit heures realisees]
|
||||||
|
B6 --> B7{heures_realisees<br/><= heures_attribuees + tolerance?}
|
||||||
|
B7 -->|Non| B8[Saisit raison du depassement]
|
||||||
|
B8 --> B9
|
||||||
|
B7 -->|Oui| B9[Confirme]
|
||||||
|
B9 --> B10[UPDATE attribution.heures_realisees]
|
||||||
|
B10 --> B11{heures_realisees<br/>== heures_attribuees?}
|
||||||
|
B11 -->|Oui| B12[attribution.statut = realise]
|
||||||
|
B11 -->|Non| B13[attribution.statut reste en_cours]
|
||||||
|
B12 --> B14[Recalculer rollups]
|
||||||
|
B13 --> B14
|
||||||
|
B14 --> End([Saisie confirmee])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. AD-03 — Cycle complet d'une formation (vue globale)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Direction valide nouvelle formation]) --> C1[Admin cree FORMATION statut=draft]
|
||||||
|
C1 --> C2[Admin cree BLOC RNCP-1, BLOC RNCP-2, ...]
|
||||||
|
C2 --> C3[Admin cree MODULES dans chaque bloc]
|
||||||
|
C3 --> C4{Tous modules<br/>prets a attribuer?}
|
||||||
|
C4 -->|Non| C3
|
||||||
|
C4 -->|Oui| C5[Admin active formation: statut=actif]
|
||||||
|
C5 --> C6{Pour chaque module}
|
||||||
|
C6 --> C7[AD-01: Attribuer formateur]
|
||||||
|
C7 --> C6
|
||||||
|
C6 -->|Tous attribues| C8[Date debut atteinte]
|
||||||
|
C8 --> C9[Modules passent en_cours auto]
|
||||||
|
C9 --> C10{Pour chaque session}
|
||||||
|
C10 --> C11[AD-02: Formateur saisit heures]
|
||||||
|
C11 --> C10
|
||||||
|
C10 -->|Toutes sessions terminees| C12[Modules passent realise auto]
|
||||||
|
C12 --> C13{Tous modules realises<br/>+ date_fin atteinte?}
|
||||||
|
C13 -->|Non| C10
|
||||||
|
C13 -->|Oui| C14[Formation statut=termine]
|
||||||
|
C14 --> C15[Generer rapport final + archivage]
|
||||||
|
C15 --> End([Formation terminee])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. AD-04 — Parcours projet client (Agence)
|
||||||
|
|
||||||
|
> Note : couvre la branche **Agence dev** d'Acadenice. Necessite l'extension du modele decrite dans `02-scope-etendu-cfa-agence.md`.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Client signe devis]) --> D1[Admin cree CLIENT si nouveau]
|
||||||
|
D1 --> D2[Admin cree PROJET lie au client]
|
||||||
|
D2 --> D3[Decouper en LIVRABLES ou TACHES]
|
||||||
|
D3 --> D4[Estimer charge en heures]
|
||||||
|
D4 --> D5[Identifier devs disponibles<br/>capacite agence restante]
|
||||||
|
D5 --> D6[INTERVENTION: lier dev a tache, heures]
|
||||||
|
D6 --> D7{Dev = formateur aussi?}
|
||||||
|
D7 -->|Oui| D8[Verifier capacite totale<br/>= capacite_agence + capacite_formation]
|
||||||
|
D7 -->|Non| D9
|
||||||
|
D8 --> D9[Confirmer intervention]
|
||||||
|
D9 --> D10{Pour chaque session de travail}
|
||||||
|
D10 --> D11[Dev saisit heures realisees]
|
||||||
|
D11 --> D10
|
||||||
|
D10 -->|Tache terminee| D12[Tache statut=livre]
|
||||||
|
D12 --> D13{Tous livrables<br/>livres?}
|
||||||
|
D13 -->|Non| D10
|
||||||
|
D13 -->|Oui| D14[PROJET statut=cloture]
|
||||||
|
D14 --> D15[Facturation client]
|
||||||
|
D15 --> End([Projet cloture])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. AD-05 — Allocation capacite Formateur-Developpeur (cas mixte)
|
||||||
|
|
||||||
|
> Cas specifique Acadenice : un formateur peut aussi etre dev sur projet client. Sa capacite totale annuelle est splittee entre formation et agence.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Personne avec role formateur + developpeur]) --> E1[Capacite totale annuelle = X heures]
|
||||||
|
E1 --> E2[Repartition par defaut:<br/>50 pct formation, 50 pct agence<br/>configurable par personne]
|
||||||
|
E2 --> E3{Allocation cours<br/>ou projet?}
|
||||||
|
|
||||||
|
E3 -->|Cours formation| E4[heures_cours_attribuees<br/>+= nouvelles heures]
|
||||||
|
E4 --> E5{capacite_formation_restante >= 0?}
|
||||||
|
E5 -->|Non| Err1[Bloquer ou warning]
|
||||||
|
E5 -->|Oui| E6[OK]
|
||||||
|
|
||||||
|
E3 -->|Projet client| E7[heures_projet_attribuees<br/>+= nouvelles heures]
|
||||||
|
E7 --> E8{capacite_agence_restante >= 0?}
|
||||||
|
E8 -->|Non| Err2[Bloquer ou warning]
|
||||||
|
E8 -->|Oui| E9[OK]
|
||||||
|
|
||||||
|
E6 --> Final[Recalculer:<br/>capacite_totale_restante = X - cours_attribuees - projet_attribuees]
|
||||||
|
E9 --> Final
|
||||||
|
Final --> End([Affichage tableau de bord<br/>Personne])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. AD-06 — Inscription etudiant a une formation
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Prospect candidate]) --> F1[Admin cree ETUDIANT prospect]
|
||||||
|
F1 --> F2[Phase selection: tests, entretiens]
|
||||||
|
F2 --> F3{Selectionne?}
|
||||||
|
F3 -->|Non| F4[Statut = refuse, lettre]
|
||||||
|
F4 --> EndKo([Fin])
|
||||||
|
F3 -->|Oui| F5[Etudiant statut = admis]
|
||||||
|
F5 --> F6[INSCRIPTION: lier ETUDIANT a FORMATION]
|
||||||
|
F6 --> F7[Creer space personnel Docmost]
|
||||||
|
F7 --> F8[Envoyer onboarding info]
|
||||||
|
F8 --> F9{Date debut formation?}
|
||||||
|
F9 -->|Non encore| F8
|
||||||
|
F9 -->|Oui| F10[Etudiant.statut = en_cours]
|
||||||
|
F10 --> F11[Formation classique]
|
||||||
|
F11 --> F12{Reussi?}
|
||||||
|
F12 -->|Oui| F13[Etudiant.statut = diplome]
|
||||||
|
F12 -->|Non| F14[Etudiant.statut = abandonne ou redouble]
|
||||||
|
F13 --> EndOk([Diplome remis])
|
||||||
|
F14 --> EndKo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. AD-07 — Validation et publication d'une page wiki
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([Auteur ouvre page Docmost]) --> G1[Edite contenu]
|
||||||
|
G1 --> G2[Sauvegarde brouillon auto]
|
||||||
|
G2 --> G3{Pret a publier?}
|
||||||
|
G3 -->|Non| G1
|
||||||
|
G3 -->|Oui| G4[Demande review optionnelle]
|
||||||
|
G4 --> G5{Reviewer assigne?}
|
||||||
|
G5 -->|Non| G7
|
||||||
|
G5 -->|Oui| G6[Reviewer commente]
|
||||||
|
G6 --> G7[Auteur applique changements]
|
||||||
|
G7 --> G8[Publie page]
|
||||||
|
G8 --> G9[Indexation search]
|
||||||
|
G9 --> G10{Page partagee externe?}
|
||||||
|
G10 -->|Oui| G11[Genere share link]
|
||||||
|
G11 --> G12[Envoie lien aux destinataires]
|
||||||
|
G10 -->|Non| End
|
||||||
|
G12 --> End([Page publiee])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Liste des activity diagrams a faire (post-validation scope)
|
||||||
|
|
||||||
|
| Code | Titre | Statut |
|
||||||
|
|------|-------|--------|
|
||||||
|
| AD-01 | Attribuer module a formateur | Fait |
|
||||||
|
| AD-02 | Saisir heures realisees | Fait |
|
||||||
|
| AD-03 | Cycle complet d'une formation | Fait |
|
||||||
|
| AD-04 | Parcours projet client (Agence) | Brouillon, depend de scope etendu |
|
||||||
|
| AD-05 | Allocation Formateur-Dev (capacite mixte) | Brouillon, depend de scope etendu |
|
||||||
|
| AD-06 | Inscription etudiant | Fait |
|
||||||
|
| AD-07 | Validation page wiki | Fait |
|
||||||
|
| AD-08 | Onboarding nouveau salarie / formateur | A faire |
|
||||||
|
| AD-09 | Maintenance site web client (recurring) | A faire si scope etendu |
|
||||||
|
| AD-10 | Backup + restauration disaster recovery | A faire (ops) |
|
||||||
516
docs/14-repo-structure-gitops.md
Normal file
516
docs/14-repo-structure-gitops.md
Normal file
|
|
@ -0,0 +1,516 @@
|
||||||
|
# Repo Structure & GitOps
|
||||||
|
|
||||||
|
> Specification du monorepo GitHub : arborescence, branching, CI/CD, DevOps, SecOps, quality gates.
|
||||||
|
> A valider avant creation du repo public.
|
||||||
|
> Audience : Corentin (DevOps owner), Yan, freelance ponctuel.
|
||||||
|
|
||||||
|
## 1. Principes
|
||||||
|
|
||||||
|
| Principe | Pourquoi |
|
||||||
|
|----------|----------|
|
||||||
|
| **Monorepo** | Tout versionne ensemble (infra, donnees, code custom, docs) — un tag = etat coherent |
|
||||||
|
| **Trunk-based development** | Branches courtes (max 2-3j), merge direct sur `main` apres review + CI |
|
||||||
|
| **Infra as code** | Tout ce qui touche a l'infra (compose, traefik labels, makefile, ci) versionne |
|
||||||
|
| **Secrets exclus du versioning** | Variables d'env via `.env` ignore + GitHub Secrets pour CI/CD |
|
||||||
|
| **Quality gates obligatoires** | Pas de merge sans CI vert (lint + tests + security scan) |
|
||||||
|
| **Reproductibilite** | `git clone + .env + make up` = stack identique, partout |
|
||||||
|
|
||||||
|
## 2. Arborescence cible du repo
|
||||||
|
|
||||||
|
```
|
||||||
|
formation-hub/
|
||||||
|
├── .github/
|
||||||
|
│ ├── workflows/
|
||||||
|
│ │ ├── ci.yml # tests + lint + security scan a chaque push/PR
|
||||||
|
│ │ ├── deploy-staging.yml # deploy automatique sur push main
|
||||||
|
│ │ ├── deploy-prod.yml # deploy sur tag v*
|
||||||
|
│ │ └── nightly-backup-test.yml # test mensuel restauration backup
|
||||||
|
│ ├── ISSUE_TEMPLATE/
|
||||||
|
│ │ ├── bug_report.md
|
||||||
|
│ │ ├── feature_request.md
|
||||||
|
│ │ └── security.md
|
||||||
|
│ ├── PULL_REQUEST_TEMPLATE.md
|
||||||
|
│ ├── CODEOWNERS # qui review quoi
|
||||||
|
│ └── dependabot.yml # auto bumps deps
|
||||||
|
│
|
||||||
|
├── docs/ # documentation projet (push aussi sur Outline)
|
||||||
|
│ ├── 01-discovery-recap.md
|
||||||
|
│ ├── 02-scope-etendu-cfa-agence.md
|
||||||
|
│ ├── 03-decision-record.md # ADR
|
||||||
|
│ ├── 04-cahier-des-charges-techniques.md
|
||||||
|
│ ├── 05-data-dictionary.md
|
||||||
|
│ ├── 06-merise-mcd.md
|
||||||
|
│ ├── 07-merise-mld.md
|
||||||
|
│ ├── 08-merise-mct.md
|
||||||
|
│ ├── 09-merise-mot.md
|
||||||
|
│ ├── 10-state-diagrams.md
|
||||||
|
│ ├── 11-uml-use-cases.md
|
||||||
|
│ ├── 12-uml-class-diagram.md
|
||||||
|
│ ├── 13-uml-activity-diagrams.md
|
||||||
|
│ ├── 14-repo-structure-gitops.md # CE DOC
|
||||||
|
│ ├── 15-baserow-mpd.md
|
||||||
|
│ ├── 16-plan-tests.md
|
||||||
|
│ ├── 17-plan-deployment.md
|
||||||
|
│ └── 18-plan-operations.md
|
||||||
|
│
|
||||||
|
├── compose.yml # stack locale dev
|
||||||
|
├── compose.staging.yml # override staging (labels Traefik staging)
|
||||||
|
├── compose.prod.yml # override prod (labels prod, replicas, healthcheck strict)
|
||||||
|
│
|
||||||
|
├── docmost/
|
||||||
|
│ ├── README.md # specifique a la branche Docmost
|
||||||
|
│ ├── patches/ # diffs upstream (Phase 2+ si fork)
|
||||||
|
│ └── Dockerfile.fork # Phase 2+ si custom build
|
||||||
|
│
|
||||||
|
├── baserow/
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── schemas/ # JSON exports des tables (versionnes)
|
||||||
|
│ │ ├── personne.json
|
||||||
|
│ │ ├── formation.json
|
||||||
|
│ │ ├── ...
|
||||||
|
│ ├── seed/
|
||||||
|
│ │ ├── seed.py # script idempotent setup initial
|
||||||
|
│ │ └── fixtures/ # data de test
|
||||||
|
│ └── migrations/ # migrations versionnees (apres setup initial)
|
||||||
|
│
|
||||||
|
├── bridge/ # service Node TS Phase 2+
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── tsconfig.json
|
||||||
|
│ ├── biome.json # lint + format Biome (rapide, no config)
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── index.ts
|
||||||
|
│ │ ├── domain/ # domain models (Personne, Module, Tache, ...)
|
||||||
|
│ │ ├── adapters/ # baserow-client, docmost-client, redis-cache
|
||||||
|
│ │ ├── routes/ # API endpoints
|
||||||
|
│ │ ├── webhooks/ # baserow webhook handlers
|
||||||
|
│ │ └── lib/ # utils
|
||||||
|
│ ├── tests/
|
||||||
|
│ │ ├── unit/
|
||||||
|
│ │ ├── integration/
|
||||||
|
│ │ └── e2e/
|
||||||
|
│ └── .env.example
|
||||||
|
│
|
||||||
|
├── traefik/ # config Traefik si versionnee (sinon reseau Docker existant)
|
||||||
|
│ ├── README.md
|
||||||
|
│ └── dynamic-config.yml
|
||||||
|
│
|
||||||
|
├── scripts/
|
||||||
|
│ ├── backup.sh # appele par cron host
|
||||||
|
│ ├── restore.sh # restauration assistee
|
||||||
|
│ ├── healthcheck.sh # check rapide endpoints
|
||||||
|
│ └── seed-baserow.sh # wrapper du baserow/seed/seed.py
|
||||||
|
│
|
||||||
|
├── .gitignore
|
||||||
|
├── .editorconfig
|
||||||
|
├── .env.example
|
||||||
|
├── Makefile # commandes ops standardisees
|
||||||
|
├── LICENSE # AGPL-3.0 (compatible avec Docmost AGPL)
|
||||||
|
├── SECURITY.md # politique de divulgation
|
||||||
|
├── CONTRIBUTING.md # convention commits, PR, branches
|
||||||
|
├── CHANGELOG.md # tenu a jour par release
|
||||||
|
└── README.md # quickstart + lien vers docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Branching strategy
|
||||||
|
|
||||||
|
**Trunk-based development simplifie** :
|
||||||
|
|
||||||
|
```
|
||||||
|
main (protege)
|
||||||
|
├── feat/saisie-heures-ui (max 2-3j vie)
|
||||||
|
├── fix/baserow-rollup-cache (max 1j vie)
|
||||||
|
├── chore/bump-deps (auto via dependabot)
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
| Aspect | Regle |
|
||||||
|
|--------|-------|
|
||||||
|
| Branche par defaut | `main` |
|
||||||
|
| Protection `main` | Required reviews 1+, CI must pass, no force push |
|
||||||
|
| Convention nom branche | `<type>/<description-kebab>` ou type = `feat \| fix \| chore \| docs \| refactor \| test` |
|
||||||
|
| Duree de vie max d'une branche | 3 jours (rebase ou drop si plus vieux) |
|
||||||
|
| Squash merge | Oui, un commit propre par PR |
|
||||||
|
| Tags | `v<MAJOR>.<MINOR>.<PATCH>` (semver) declenche deploy prod |
|
||||||
|
|
||||||
|
## 4. Convention de commits
|
||||||
|
|
||||||
|
Format : `<type>(<scope>): <description>`
|
||||||
|
|
||||||
|
| Type | Usage |
|
||||||
|
|------|-------|
|
||||||
|
| `feat` | Nouvelle fonctionnalite |
|
||||||
|
| `fix` | Bug fix |
|
||||||
|
| `docs` | Documentation seulement |
|
||||||
|
| `refactor` | Refactor sans changement comportement |
|
||||||
|
| `test` | Ajout/modif tests |
|
||||||
|
| `chore` | Maintenance, deps, tooling |
|
||||||
|
| `ops` | Infra, CI/CD, ops |
|
||||||
|
| `sec` | Security fix ou hardening |
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
- `feat(bridge): add formateur mention tiptap node`
|
||||||
|
- `fix(baserow): correct rollup cache invalidation on annulation`
|
||||||
|
- `ops(ci): add SAST scan with semgrep`
|
||||||
|
- `sec(deps): bump postgres to 16.4 for CVE-2026-XXXX`
|
||||||
|
|
||||||
|
**Pas d'emoji** dans les commits, pas de signature Claude (regle Acadenice).
|
||||||
|
|
||||||
|
## 5. CI/CD GitHub Actions
|
||||||
|
|
||||||
|
### 5.1 Workflow `ci.yml` (a chaque push + PR)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: npx biome check bridge/
|
||||||
|
|
||||||
|
type-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with: { node-version: 22 }
|
||||||
|
- run: cd bridge && npm ci && npm run typecheck
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres: { image: postgres:16-alpine, env: { POSTGRES_PASSWORD: test }, ports: ["5432:5432"] }
|
||||||
|
redis: { image: redis:7-alpine, ports: ["6379:6379"] }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with: { node-version: 22 }
|
||||||
|
- run: cd bridge && npm ci && npm run test
|
||||||
|
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Secret scanning
|
||||||
|
uses: trufflesecurity/trufflehog@main
|
||||||
|
- name: SAST
|
||||||
|
uses: returntocorp/semgrep-action@v1
|
||||||
|
- name: Dependency check
|
||||||
|
run: cd bridge && npm audit --audit-level=high
|
||||||
|
- name: License check
|
||||||
|
run: cd bridge && npx license-checker --failOn 'GPL-3.0;AGPL-3.0' --excludePackages 'bridge'
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint, type-check, test, security]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: docker compose build
|
||||||
|
- run: docker compose up -d
|
||||||
|
- run: ./scripts/healthcheck.sh
|
||||||
|
- run: docker compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Workflow `deploy-staging.yml` (sur push main)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy Staging
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: staging
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build & push images
|
||||||
|
run: |
|
||||||
|
docker build -t registry.acadenice.fr/formation-hub/bridge:${{ github.sha }} bridge/
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.acadenice.fr -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
docker push registry.acadenice.fr/formation-hub/bridge:${{ github.sha }}
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.STAGING_HOST }}
|
||||||
|
username: ${{ secrets.STAGING_USER }}
|
||||||
|
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
cd /opt/formation-hub
|
||||||
|
git pull
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml pull
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml up -d
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Workflow `deploy-prod.yml` (sur tag v*)
|
||||||
|
|
||||||
|
Identique a staging mais cible prod, requiert approbation manuelle (`environment: production` avec required reviewers dans GitHub UI).
|
||||||
|
|
||||||
|
### 5.4 Workflow `nightly-backup-test.yml` (mensuel)
|
||||||
|
|
||||||
|
Restauration automatique d'un backup recent sur env isole + verification integrite. Alerte si fail.
|
||||||
|
|
||||||
|
## 6. DevOps — environnements
|
||||||
|
|
||||||
|
| Env | Host | Domain | Branche source | Auto deploy |
|
||||||
|
|-----|------|--------|----------------|-------------|
|
||||||
|
| **local** | machine de dev | `localhost:3000/8080/4000` | n'importe | manuel via `make up` |
|
||||||
|
| **staging** | VPS Hetzner staging | `wiki.staging.acadenice.fr` etc. | `main` | oui sur push |
|
||||||
|
| **prod** | VPS Hetzner prod | `wiki.acadenice.fr` etc. | tags `v*` | oui mais avec approval review |
|
||||||
|
|
||||||
|
### Differences env
|
||||||
|
|
||||||
|
| Aspect | local | staging | prod |
|
||||||
|
|--------|-------|---------|------|
|
||||||
|
| Donnees | seed fixtures | data realiste anonymisee | data reelle |
|
||||||
|
| Backups | aucun | quotidien local | quotidien local + S3 distant |
|
||||||
|
| Healthchecks | optional | active | strict + alerting |
|
||||||
|
| Replicas | 1 | 1 | 1 (peut passer a 2 si charge) |
|
||||||
|
| Logs | stdout | persisted 7j | persisted 90j + push central |
|
||||||
|
| Monitoring | aucun | uptime basique | uptime + perf + alerting |
|
||||||
|
|
||||||
|
## 7. Secret management
|
||||||
|
|
||||||
|
**Exclus de git, sans exception.**
|
||||||
|
|
||||||
|
| Type secret | Stockage |
|
||||||
|
|-------------|----------|
|
||||||
|
| Local dev | `.env` (gitignored) |
|
||||||
|
| GitHub Actions | GitHub Secrets (env-scoped : staging, production) |
|
||||||
|
| Staging/Prod runtime | `.env.staging` / `.env.prod` sur le serveur, ownership root, perms 600 |
|
||||||
|
| API tokens longue duree (Outline, etc.) | Vault / pass / 1Password en interne — exclus du disque non-encrypted |
|
||||||
|
| Backup encryption key | Hors git, hors GitHub, conserve dans coffre-fort hors-bande |
|
||||||
|
|
||||||
|
**Rotation** : tokens API rotates annuellement, secrets sensibles (DB, JWT) rotates trimestriellement.
|
||||||
|
|
||||||
|
## 8. SecOps
|
||||||
|
|
||||||
|
### 8.1 Quality gates avant merge
|
||||||
|
|
||||||
|
Tous obligatoires (PR bloquee si rouge) :
|
||||||
|
- [ ] CI lint vert (Biome)
|
||||||
|
- [ ] Type-check vert (tsc)
|
||||||
|
- [ ] Tests unit + integration verts
|
||||||
|
- [ ] Coverage >= 70% sur fichiers modifies (decision a affiner)
|
||||||
|
- [ ] Secret scanning (TruffleHog) zero hit
|
||||||
|
- [ ] SAST (Semgrep) zero finding `error` severity
|
||||||
|
- [ ] Dependency check (npm audit) zero CVE `high` ou `critical`
|
||||||
|
- [ ] License check (license-checker) pas de GPL/AGPL non-compatibles
|
||||||
|
- [ ] Docker build OK + healthcheck stack passe
|
||||||
|
- [ ] Review humaine 1+ approval
|
||||||
|
|
||||||
|
### 8.2 Outils SecOps
|
||||||
|
|
||||||
|
| Outil | Role | Frequence |
|
||||||
|
|-------|------|-----------|
|
||||||
|
| **TruffleHog** | Secret scanning dans le code | A chaque push |
|
||||||
|
| **Semgrep** | SAST (Static Application Security Testing) | A chaque push |
|
||||||
|
| **npm audit** | CVE deps Node | A chaque push |
|
||||||
|
| **Dependabot** | Auto-bump deps + security alerts | Auto, hebdomadaire |
|
||||||
|
| **Trivy** ou **Grype** | Scan vulnerabilities images Docker | Avant push registry |
|
||||||
|
| **OWASP ZAP** | DAST sur staging (Phase 2+) | Hebdomadaire |
|
||||||
|
| **Falco** ou logs analysis | Runtime intrusion detection | Continu prod |
|
||||||
|
|
||||||
|
### 8.3 Politique de divulgation (`SECURITY.md`)
|
||||||
|
|
||||||
|
- Email contact : security@acadenice.fr
|
||||||
|
- Reponse sous 48h ouvrees
|
||||||
|
- Disclosure responsable, embargo coordonne
|
||||||
|
- CVE assignee si publique
|
||||||
|
|
||||||
|
### 8.4 Audit log applicatif
|
||||||
|
|
||||||
|
Operations sensibles loggees avec :
|
||||||
|
- Acteur (personne_id)
|
||||||
|
- Action (creation/modification/suppression/partage_externe)
|
||||||
|
- Cible (entity_type + entity_id)
|
||||||
|
- Timestamp
|
||||||
|
- IP source
|
||||||
|
- Justification (si fournie)
|
||||||
|
|
||||||
|
Stockage : table dediee `audit_log` ou journal append-only fichier (a trancher MPD).
|
||||||
|
Retention : 5 ans (Qualiopi-compatible).
|
||||||
|
|
||||||
|
## 9. PR template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Description
|
||||||
|
<!-- Quoi et pourquoi -->
|
||||||
|
|
||||||
|
## Type de changement
|
||||||
|
- [ ] feat
|
||||||
|
- [ ] fix
|
||||||
|
- [ ] docs
|
||||||
|
- [ ] refactor
|
||||||
|
- [ ] test
|
||||||
|
- [ ] chore
|
||||||
|
- [ ] ops
|
||||||
|
- [ ] sec
|
||||||
|
|
||||||
|
## Issue liee
|
||||||
|
Closes #...
|
||||||
|
|
||||||
|
## Tests realises
|
||||||
|
- [ ] Tests unit ajoutes/modifies
|
||||||
|
- [ ] Tests integration ajoutes/modifies
|
||||||
|
- [ ] Test manuel local
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] CI vert
|
||||||
|
- [ ] Pas de secret commit (verifier diff)
|
||||||
|
- [ ] Doc mise a jour si necessaire
|
||||||
|
- [ ] Migration data si schema change
|
||||||
|
- [ ] Changelog mis a jour si user-facing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Issue templates
|
||||||
|
|
||||||
|
### Bug report
|
||||||
|
```markdown
|
||||||
|
## Description
|
||||||
|
<!-- Quoi -->
|
||||||
|
|
||||||
|
## Etapes pour reproduire
|
||||||
|
1. ...
|
||||||
|
|
||||||
|
## Comportement attendu
|
||||||
|
<!-- ... -->
|
||||||
|
|
||||||
|
## Comportement observe
|
||||||
|
<!-- ... -->
|
||||||
|
|
||||||
|
## Env (local/staging/prod)
|
||||||
|
- Version (commit SHA ou tag) :
|
||||||
|
- Browser/device :
|
||||||
|
|
||||||
|
## Logs / screenshots
|
||||||
|
<!-- ... -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature request
|
||||||
|
```markdown
|
||||||
|
## Probleme metier
|
||||||
|
<!-- Quoi resoudre -->
|
||||||
|
|
||||||
|
## Solution proposee
|
||||||
|
<!-- Comment -->
|
||||||
|
|
||||||
|
## Alternatives considerees
|
||||||
|
<!-- Autres options -->
|
||||||
|
|
||||||
|
## Impact estime
|
||||||
|
<!-- Effort + valeur -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security
|
||||||
|
**Privee** par defaut. Reporting via email security@acadenice.fr.
|
||||||
|
|
||||||
|
## 11. Release process
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Merge PRs sur main → deploy staging auto
|
||||||
|
2. Tester sur staging (qualif metier + smoke tests)
|
||||||
|
3. Si OK :
|
||||||
|
- Update CHANGELOG.md (section "Unreleased" → version)
|
||||||
|
- Tag : git tag -a v1.2.3 -m "Release v1.2.3"
|
||||||
|
- git push origin v1.2.3
|
||||||
|
4. GitHub Action deploy-prod se declenche
|
||||||
|
5. Approval manual review (Yan ou Corentin)
|
||||||
|
6. Deploy prod execute
|
||||||
|
7. Post-deploy : surveiller logs + metriques 30 min
|
||||||
|
8. Si issue : execute rollback (cf section 12)
|
||||||
|
```
|
||||||
|
|
||||||
|
Convention semver :
|
||||||
|
- MAJOR : breaking changes (migration data forcee, rupture API)
|
||||||
|
- MINOR : nouvelle feature, backward-compatible
|
||||||
|
- PATCH : bug fix, security fix
|
||||||
|
|
||||||
|
## 12. Rollback process
|
||||||
|
|
||||||
|
| Scenario | Action |
|
||||||
|
|----------|--------|
|
||||||
|
| Bug critique en prod (data loss / down) | Re-deploy version precedente : `git checkout v1.2.2 && deploy-prod.yml` |
|
||||||
|
| Schema migration foireuse | Restore Postgres depuis backup precedant le deploy + redeploy version stable |
|
||||||
|
| Compromission credentials | Rotate secrets immediate + audit logs + isoler env si necessaire |
|
||||||
|
| Bug minor en staging | Hotfix sur main, redeploy staging, ne pas tag prod |
|
||||||
|
|
||||||
|
Runbook detaille dans `18-plan-operations.md` (a venir).
|
||||||
|
|
||||||
|
## 13. CODEOWNERS
|
||||||
|
|
||||||
|
```
|
||||||
|
# Default
|
||||||
|
* @corentin
|
||||||
|
|
||||||
|
# Infra & ops
|
||||||
|
/.github/workflows/ @corentin @yan
|
||||||
|
/compose*.yml @corentin
|
||||||
|
/Makefile @corentin
|
||||||
|
/scripts/ @corentin
|
||||||
|
|
||||||
|
# Code custom
|
||||||
|
/bridge/ @corentin
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
/docs/ @corentin
|
||||||
|
```
|
||||||
|
|
||||||
|
## 14. Quickstart pour nouveau dev
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone
|
||||||
|
git clone git@github.com:acadenice/formation-hub.git
|
||||||
|
cd formation-hub
|
||||||
|
|
||||||
|
# 2. Setup
|
||||||
|
cp .env.example .env
|
||||||
|
# editer .env avec secrets (cf SECURITY.md)
|
||||||
|
|
||||||
|
# 3. Up
|
||||||
|
make up
|
||||||
|
|
||||||
|
# 4. Acces
|
||||||
|
# - Docmost : http://localhost:3000
|
||||||
|
# - Baserow : http://localhost:8080
|
||||||
|
# - Bridge : http://localhost:4000 (Phase 2)
|
||||||
|
|
||||||
|
# 5. Workflow contribution
|
||||||
|
git checkout -b feat/ma-feature
|
||||||
|
# code, test
|
||||||
|
git commit -m "feat(bridge): description"
|
||||||
|
git push origin feat/ma-feature
|
||||||
|
# Ouvrir PR sur GitHub, attendre CI vert + review
|
||||||
|
```
|
||||||
|
|
||||||
|
## 15. Etapes avant creation du repo public
|
||||||
|
|
||||||
|
Checklist Corentin avant `git init` + `git push -u origin main` :
|
||||||
|
|
||||||
|
- [ ] LICENSE (AGPL-3.0 par alignement avec Docmost)
|
||||||
|
- [ ] SECURITY.md
|
||||||
|
- [ ] CONTRIBUTING.md
|
||||||
|
- [ ] README.md (quickstart + liens docs)
|
||||||
|
- [ ] .gitignore complet (verifie pas de fichier sensible)
|
||||||
|
- [ ] .env.example commite, .env exclu
|
||||||
|
- [ ] Workflows CI/CD `.github/workflows/` prets (au moins ci.yml)
|
||||||
|
- [ ] PR template + issue templates
|
||||||
|
- [ ] CODEOWNERS
|
||||||
|
- [ ] dependabot.yml configure
|
||||||
|
- [ ] GitHub Secrets configures (`REGISTRY_*`, `STAGING_*`, `PROD_*`)
|
||||||
|
- [ ] Branch protection rules sur `main` actives
|
||||||
|
- [ ] Required reviewers configures sur `production` env
|
||||||
|
|
||||||
|
Une fois tout vert : push `main`, configure Dependabot, enable security alerts. Le repo est pret.
|
||||||
|
|
||||||
|
## 16. Questions a trancher
|
||||||
|
|
||||||
|
- [ ] Repo **public** (open-source) ou **prive** ? Implications RGPD si etudiants/clients dans audit logs visible.
|
||||||
|
- [ ] Self-host GitLab interne plutot que GitHub (souverainete) ? Pas le cas aujourd'hui mais a noter.
|
||||||
|
- [ ] Registry images Docker : GitHub Container Registry, Harbor self-host, ou registry.acadenice.fr (a deployer) ?
|
||||||
|
- [ ] Couverture tests minimum : 70% / 80% / autre ? Strictesse vs vitesse.
|
||||||
|
- [ ] Tooling lint/format : Biome (rapide, all-in-one) vs ESLint+Prettier classique ?
|
||||||
428
docs/15-baserow-mpd.md
Normal file
428
docs/15-baserow-mpd.md
Normal file
|
|
@ -0,0 +1,428 @@
|
||||||
|
# MPD — Modele Physique de Donnees (Baserow)
|
||||||
|
|
||||||
|
> Implementation concrete dans Baserow : 9 tables avec types exacts, formules, vues, permissions.
|
||||||
|
> Ce doc est **actionnable** : Corentin l'ouvre cote a cote avec Baserow et cree les tables une par une.
|
||||||
|
> Source : `07-merise-mld.md` (MLD relationnel) + `05-data-dictionary.md`.
|
||||||
|
|
||||||
|
## 1. Setup initial
|
||||||
|
|
||||||
|
### 1.1 Hierarchie Baserow
|
||||||
|
|
||||||
|
```
|
||||||
|
Workspace : Acadenice formation-hub
|
||||||
|
└── Database : formation-hub
|
||||||
|
├── Tables CFA : formation, bloc, module, attribution
|
||||||
|
├── Tables Agence : client, projet, tache, intervention
|
||||||
|
└── Table pivot : personne
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Order de creation (dependances FK)
|
||||||
|
|
||||||
|
Creer les tables dans cet ordre — chaque table dependant des precedentes via FK :
|
||||||
|
|
||||||
|
1. **personne** (aucune dep)
|
||||||
|
2. **client** (aucune dep)
|
||||||
|
3. **formation** (aucune dep)
|
||||||
|
4. **bloc** (FK → formation)
|
||||||
|
5. **projet** (FK → client, optionnel FK → formation)
|
||||||
|
6. **module** (FK → bloc)
|
||||||
|
7. **tache** (FK → projet)
|
||||||
|
8. **attribution** (FK → module + personne)
|
||||||
|
9. **intervention** (FK → tache + personne)
|
||||||
|
|
||||||
|
### 1.3 Conventions Baserow
|
||||||
|
|
||||||
|
| Concept | Implementation Baserow |
|
||||||
|
|---------|------------------------|
|
||||||
|
| ID auto-increment (PK) | Baserow `id` natif (genere auto) |
|
||||||
|
| Nom du field | `snake_case`, prefixes par mnemonique d'entite (ex `formation_nom`, `personne_email`) |
|
||||||
|
| Field "Primary" | Baserow oblige a avoir un Primary field — choisir le nom le plus explicite (ex `formation_nom`) |
|
||||||
|
| Foreign Key | Field type `Link to table` |
|
||||||
|
| ENUM | Field type `Single select` avec options exactes |
|
||||||
|
| MULTI ENUM | Field type `Multiple select` |
|
||||||
|
| Champ calcule (formula) | Field type `Formula` |
|
||||||
|
| Champ derive d'une relation | Field type `Lookup` ou `Count` |
|
||||||
|
| Audit timestamps | Fields `Created on` + `Last modified time` natifs |
|
||||||
|
| Audit acteur | Fields `Created by` + `Last modified by` natifs |
|
||||||
|
|
||||||
|
### 1.4 API token
|
||||||
|
|
||||||
|
Apres creation des tables :
|
||||||
|
- Settings → API tokens → Create token
|
||||||
|
- Permissions : `read`/`create`/`update`/`delete` sur la database `formation-hub`
|
||||||
|
- Stocker dans `.env` cote bridge : `BASEROW_API_TOKEN=...`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Table `personne`
|
||||||
|
|
||||||
|
**Primary field** : `personne_nom` (texte affiche par defaut dans les links)
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `personne_nom` | Text | — | Nom de famille (Primary) |
|
||||||
|
| 2 | `personne_prenom` | Text | — | Prenom |
|
||||||
|
| 3 | `personne_email` | Email | — | Email pro (unique recommande, validation cote bridge) |
|
||||||
|
| 4 | `personne_telephone` | Phone number | — | Telephone (optionnel) |
|
||||||
|
| 5 | `personne_capacite_annuelle` | Number | Decimal places: 2 | Heures totales/an |
|
||||||
|
| 6 | `personne_split_formation_pct` | Number | Decimal places: 1, default 50 | % capacite alloue formation |
|
||||||
|
| 7 | `personne_split_agence_pct` | Number | Decimal places: 1, default 50 | % capacite alloue agence |
|
||||||
|
| 8 | `personne_roles` | Multiple select | Options: `formateur`, `developpeur`, `admin`, `direction`, `support` | Roles cumules |
|
||||||
|
| 9 | `personne_statut` | Single select | Options: `actif` (default), `inactif` | Statut |
|
||||||
|
| 10 | `personne_attributions` | Link to table | Lien vers `attribution` (champ inverse auto-cree apres creation de attribution) | Toutes les attributions de cette personne |
|
||||||
|
| 11 | `personne_interventions` | Link to table | Lien vers `intervention` (apres creation) | Toutes les interventions |
|
||||||
|
| 12 | `personne_heures_attribuees_formation` | Formula | `sum(lookup('personne_attributions', 'attribution_heures_attribuees_active'))` | Rollup des attributions actives |
|
||||||
|
| 13 | `personne_heures_attribuees_agence` | Formula | `sum(lookup('personne_interventions', 'intervention_heures_active'))` | Rollup des interventions actives |
|
||||||
|
| 14 | `personne_heures_restantes_formation` | Formula | `(field('personne_capacite_annuelle') * field('personne_split_formation_pct') / 100) - field('personne_heures_attribuees_formation')` | Capacite formation restante |
|
||||||
|
| 15 | `personne_heures_restantes_agence` | Formula | `(field('personne_capacite_annuelle') * field('personne_split_agence_pct') / 100) - field('personne_heures_attribuees_agence')` | Capacite agence restante |
|
||||||
|
| 16 | `personne_heures_restantes_total` | Formula | `field('personne_capacite_annuelle') - field('personne_heures_attribuees_formation') - field('personne_heures_attribuees_agence')` | Capacite totale restante |
|
||||||
|
|
||||||
|
**Vues recommandees** :
|
||||||
|
- `Tous` (grid, default) — tableau complet
|
||||||
|
- `Actifs` (grid, filtre `personne_statut = actif`)
|
||||||
|
- `Formateurs` (grid, filtre `personne_roles contient formateur`)
|
||||||
|
- `Developpeurs` (grid, filtre `personne_roles contient developpeur`)
|
||||||
|
- `Capacite restante` (grid, sort `personne_heures_restantes_total ascending`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Table `formation`
|
||||||
|
|
||||||
|
**Primary field** : `formation_nom`
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `formation_nom` | Text | — | Nom (Primary, unique conseille) |
|
||||||
|
| 2 | `formation_description` | Long text | rich text autorise | Description longue |
|
||||||
|
| 3 | `formation_filiere` | Single select | `dev`, `graphisme`, `marketing`, `iot`, `cybersec` | Filiere |
|
||||||
|
| 4 | `formation_heures_totales` | Number | Decimal places: 2 | Heures totales prevues |
|
||||||
|
| 5 | `formation_statut` | Single select | `draft` (default), `actif`, `termine`, `archive` | Cycle de vie |
|
||||||
|
| 6 | `formation_date_debut` | Date | format `YYYY-MM-DD` | Date debut |
|
||||||
|
| 7 | `formation_date_fin` | Date | — | Date fin |
|
||||||
|
| 8 | `formation_blocs` | Link to table | Lien vers `bloc` (apres creation bloc) | Blocs de la formation |
|
||||||
|
| 9 | `formation_projets_pedagogiques` | Link to table | Lien vers `projet` (optionnel) | Projets agence lies en pedagogique |
|
||||||
|
| 10 | `formation_heures_attribuees` | Formula | `sum(lookup('formation_blocs', 'bloc_heures_prevues'))` | Rollup heures des blocs |
|
||||||
|
| 11 | `formation_heures_restantes` | Formula | `field('formation_heures_totales') - field('formation_heures_attribuees')` | Reste a attribuer |
|
||||||
|
| 12 | `formation_created_at` | Created on | — | Audit |
|
||||||
|
| 13 | `formation_updated_at` | Last modified time | — | Audit |
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- `Actives` (grid, filtre `formation_statut = actif`)
|
||||||
|
- `Par filiere` (grid, group by `formation_filiere`)
|
||||||
|
- `Calendrier` (calendar view, sur `formation_date_debut`)
|
||||||
|
- `Capacite restante` (grid, sort `formation_heures_restantes ascending`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Table `bloc`
|
||||||
|
|
||||||
|
**Primary field** : `bloc_nom`
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `bloc_nom` | Text | — | Nom du bloc (Primary) |
|
||||||
|
| 2 | `bloc_description` | Long text | — | Description |
|
||||||
|
| 3 | `bloc_formation` | Link to table | Lien vers `formation` (single) | Formation parente |
|
||||||
|
| 4 | `bloc_heures_prevues` | Number | Decimal places: 2 | Heures du bloc |
|
||||||
|
| 5 | `bloc_ordre` | Number | Decimal places: 0 | Ordre dans la formation |
|
||||||
|
| 6 | `bloc_modules` | Link to table | Lien vers `module` (apres creation) | Modules du bloc |
|
||||||
|
| 7 | `bloc_heures_attribuees` | Formula | `sum(lookup('bloc_modules', 'module_heures_prevues_active'))` | Rollup heures modules actifs |
|
||||||
|
| 8 | `bloc_heures_restantes` | Formula | `field('bloc_heures_prevues') - field('bloc_heures_attribuees')` | Reste a decomposer |
|
||||||
|
|
||||||
|
**Note** : la regle metier "un bloc a un nom unique par formation" se valide **cote bridge** ou via une vue filtree de duplication.
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- `Par formation` (grid, group by `bloc_formation`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Table `module`
|
||||||
|
|
||||||
|
**Primary field** : `module_nom`
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `module_nom` | Text | — | Nom du module (Primary) |
|
||||||
|
| 2 | `module_description` | Long text | — | Description |
|
||||||
|
| 3 | `module_bloc` | Link to table | Lien vers `bloc` (single) | Bloc parent |
|
||||||
|
| 4 | `module_heures_prevues` | Number | Decimal places: 2 | Heures du module |
|
||||||
|
| 5 | `module_statut` | Single select | `a_attribuer` (default), `attribue`, `en_cours`, `realise`, `annule` | Cycle de vie |
|
||||||
|
| 6 | `module_attributions` | Link to table | Lien vers `attribution` (apres creation) | Attributions du module |
|
||||||
|
| 7 | `module_heures_prevues_active` | Formula | `if(field('module_statut') = 'annule', 0, field('module_heures_prevues'))` | Pour rollup bloc (exclut annule) |
|
||||||
|
| 8 | `module_heures_attribuees` | Formula | `sum(lookup('module_attributions', 'attribution_heures_attribuees_active'))` | Rollup |
|
||||||
|
| 9 | `module_heures_realisees` | Formula | `sum(lookup('module_attributions', 'attribution_heures_realisees'))` | Rollup |
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- **`A attribuer`** (kanban, group by `module_statut`) — vue principale pour l'admin
|
||||||
|
- `Par bloc` (grid, group by `module_bloc`)
|
||||||
|
- `Realises` (grid, filtre `module_statut = realise`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Table `attribution`
|
||||||
|
|
||||||
|
**Primary field** : `attribution_titre` (formula : nom_module + " → " + nom_personne)
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `attribution_titre` | Formula | `concat(lookup('attribution_module', 'module_nom'), ' → ', lookup('attribution_personne', 'personne_prenom'), ' ', lookup('attribution_personne', 'personne_nom'))` | Titre auto (Primary) |
|
||||||
|
| 2 | `attribution_module` | Link to table | Lien vers `module` (single) | Module attribue |
|
||||||
|
| 3 | `attribution_personne` | Link to table | Lien vers `personne` (single) | Formateur (role formateur requis) |
|
||||||
|
| 4 | `attribution_heures_attribuees` | Number | Decimal places: 2 | Heures planifiees |
|
||||||
|
| 5 | `attribution_heures_realisees` | Number | Decimal places: 2, default 0 | Heures effectuees |
|
||||||
|
| 6 | `attribution_date_debut` | Date | — | Debut periode |
|
||||||
|
| 7 | `attribution_date_fin` | Date | — | Fin periode |
|
||||||
|
| 8 | `attribution_statut` | Single select | `planifie` (default), `en_cours`, `realise`, `annule` | Statut |
|
||||||
|
| 9 | `attribution_heures_attribuees_active` | Formula | `if(field('attribution_statut') = 'annule', 0, field('attribution_heures_attribuees'))` | Pour rollup module/personne |
|
||||||
|
|
||||||
|
**Validation cote bridge** :
|
||||||
|
- `attribution_personne.personne_roles` doit contenir `formateur`
|
||||||
|
- `sum(attribution_heures_attribuees) for module <= module_heures_prevues` (RG-01)
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- **`Mes attributions`** (grid, filtre `attribution_personne = current user`) — vue formateur
|
||||||
|
- `En cours` (grid, filtre `attribution_statut = en_cours`)
|
||||||
|
- `Calendrier` (calendar view sur `attribution_date_debut` ou `attribution_date_fin`)
|
||||||
|
- `Form public` (form view) — formateur saisit ses heures realisees rapide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Table `client`
|
||||||
|
|
||||||
|
**Primary field** : `client_nom`
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `client_nom` | Text | — | Nom (Primary) |
|
||||||
|
| 2 | `client_contact_principal` | Text | — | Nom + role du contact |
|
||||||
|
| 3 | `client_contact_email` | Email | — | Email contact |
|
||||||
|
| 4 | `client_contact_telephone` | Phone number | — | Telephone |
|
||||||
|
| 5 | `client_secteur` | Text | — | Secteur d'activite |
|
||||||
|
| 6 | `client_notes` | Long text | — | Notes libres |
|
||||||
|
| 7 | `client_statut` | Single select | `prospect` (default), `actif`, `inactif`, `archive` | Statut |
|
||||||
|
| 8 | `client_projets` | Link to table | Lien vers `projet` | Projets du client |
|
||||||
|
| 9 | `client_created_at` | Created on | — | Audit |
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- `Actifs` (grid, filtre `client_statut = actif`)
|
||||||
|
- **`Pipeline`** (kanban, group by `client_statut`) — vue commerciale
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Table `projet`
|
||||||
|
|
||||||
|
**Primary field** : `projet_nom`
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `projet_nom` | Text | — | Nom (Primary) |
|
||||||
|
| 2 | `projet_description` | Long text | — | Description |
|
||||||
|
| 3 | `projet_client` | Link to table | Lien vers `client` (single) | Client |
|
||||||
|
| 4 | `projet_type` | Single select | `site_web`, `app_mobile`, `api`, `infra`, `audit`, `support`, `autre` | Type |
|
||||||
|
| 5 | `projet_charge_heures` | Number | Decimal places: 2 | Charge estimee |
|
||||||
|
| 6 | `projet_date_debut` | Date | — | Date debut |
|
||||||
|
| 7 | `projet_date_fin_prevue` | Date | — | Date fin prevue |
|
||||||
|
| 8 | `projet_date_livraison` | Date | — | Date livraison effective |
|
||||||
|
| 9 | `projet_statut` | Single select | `devis` (default), `en_cours`, `livre`, `cloture`, `abandonne` | Statut |
|
||||||
|
| 10 | `projet_formation_pedagogique` | Link to table | Lien vers `formation` (single, optionnel) | Lien pedagogique |
|
||||||
|
| 11 | `projet_url` | URL | — | Site livraison |
|
||||||
|
| 12 | `projet_repository` | URL | — | Repo Git |
|
||||||
|
| 13 | `projet_taches` | Link to table | Lien vers `tache` | Taches du projet |
|
||||||
|
| 14 | `projet_heures_attribuees` | Formula | `sum(lookup('projet_taches', 'tache_charge_heures'))` | Rollup taches |
|
||||||
|
| 15 | `projet_heures_realisees` | Formula | `sum(lookup('projet_taches', 'tache_heures_realisees'))` | Rollup |
|
||||||
|
| 16 | `projet_heures_restantes` | Formula | `field('projet_charge_heures') - field('projet_heures_realisees')` | Reste a faire |
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- **`Pipeline`** (kanban, group by `projet_statut`) — vue principale
|
||||||
|
- `En cours` (grid, filtre `projet_statut = en_cours`)
|
||||||
|
- `Timeline` (timeline view sur date_debut → date_fin_prevue)
|
||||||
|
- `Par client` (grid, group by `projet_client`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Table `tache`
|
||||||
|
|
||||||
|
**Primary field** : `tache_titre`
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `tache_titre` | Text | — | Titre (Primary) |
|
||||||
|
| 2 | `tache_description` | Long text | — | Description |
|
||||||
|
| 3 | `tache_projet` | Link to table | Lien vers `projet` (single) | Projet parent |
|
||||||
|
| 4 | `tache_charge_heures` | Number | Decimal places: 2 | Charge estimee |
|
||||||
|
| 5 | `tache_priorite` | Single select | `faible`, `normale`, `haute`, `critique` | Priorite |
|
||||||
|
| 6 | `tache_statut` | Single select | `todo` (default), `in_progress`, `review`, `done`, `abandoned` | Statut |
|
||||||
|
| 7 | `tache_date_debut` | Date | — | Debut prevu |
|
||||||
|
| 8 | `tache_date_fin_prevue` | Date | — | Fin prevue |
|
||||||
|
| 9 | `tache_assignee` | Link to table | Lien vers `personne` (single, optionnel) | Dev assignee informellement |
|
||||||
|
| 10 | `tache_interventions` | Link to table | Lien vers `intervention` | Interventions sur cette tache |
|
||||||
|
| 11 | `tache_heures_realisees` | Formula | `sum(lookup('tache_interventions', 'intervention_heures_active'))` | Rollup |
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid)
|
||||||
|
- **`Kanban`** (kanban, group by `tache_statut`) — vue principale
|
||||||
|
- `Par priorite` (grid, group by `tache_priorite`, sort par priorite)
|
||||||
|
- `Mes taches` (grid, filtre `tache_assignee = current user`)
|
||||||
|
- `Done recentes` (grid, filtre `tache_statut = done`, sort par date desc)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Table `intervention`
|
||||||
|
|
||||||
|
**Primary field** : `intervention_titre` (formula auto)
|
||||||
|
|
||||||
|
| # | Field | Type Baserow | Parametres | Description |
|
||||||
|
|---|-------|-------------|------------|-------------|
|
||||||
|
| 1 | `intervention_titre` | Formula | `concat(lookup('intervention_tache', 'tache_titre'), ' - ', lookup('intervention_personne', 'personne_prenom'), ' (', totext(field('intervention_date')), ')')` | Titre auto (Primary) |
|
||||||
|
| 2 | `intervention_tache` | Link to table | Lien vers `tache` (single) | Tache concernee |
|
||||||
|
| 3 | `intervention_personne` | Link to table | Lien vers `personne` (single) | Developpeur (role developpeur requis) |
|
||||||
|
| 4 | `intervention_heures` | Number | Decimal places: 2 | Heures effectuees |
|
||||||
|
| 5 | `intervention_date` | Date | default today | Date intervention |
|
||||||
|
| 6 | `intervention_notes` | Long text | — | Notes / commit ref / lien PR |
|
||||||
|
| 7 | `intervention_statut` | Single select | `planifie`, `realise` (default), `annule` | Statut |
|
||||||
|
| 8 | `intervention_heures_active` | Formula | `if(field('intervention_statut') = 'annule', 0, field('intervention_heures'))` | Pour rollup tache/personne |
|
||||||
|
|
||||||
|
**Validation cote bridge** :
|
||||||
|
- `intervention_personne.personne_roles` doit contenir `developpeur`
|
||||||
|
- `intervention_heures > 0`
|
||||||
|
|
||||||
|
**Vues** :
|
||||||
|
- `Tous` (grid, sort `intervention_date desc`)
|
||||||
|
- **`Mes interventions`** (grid, filtre `intervention_personne = current user`) — vue dev
|
||||||
|
- **`Form rapide`** (form public) — saisie heures rapide mobile
|
||||||
|
- `Par projet` (grid, group by `intervention_tache.tache_projet`)
|
||||||
|
- `Cette semaine` (grid, filtre `intervention_date >= start_of_week`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Permissions et sharing
|
||||||
|
|
||||||
|
### 11.1 Roles Baserow
|
||||||
|
|
||||||
|
| Role | Membres | Capacites |
|
||||||
|
|------|---------|-----------|
|
||||||
|
| Admin workspace | Corentin, Yan, Ludo | Plein controle |
|
||||||
|
| Editor | Sophie + autres admins | Read/write toutes tables |
|
||||||
|
| Builder | Formateurs / Devs | Read/write **leur ligne** via vues filtrees + form rapide |
|
||||||
|
| Viewer | Stakeholders ponctuels | Read seul |
|
||||||
|
|
||||||
|
**Limitation** : Baserow native permissions sont au niveau database/table, pas row-level. Pour limiter formateur/dev a leurs propres rows :
|
||||||
|
- Soit **vues filtrees partagees publiquement** (form pour saisie + grid filtree pour lecture)
|
||||||
|
- Soit **bridge service** qui filtre cote API selon `current_user_id`
|
||||||
|
|
||||||
|
### 11.2 Forms publics pour saisie rapide
|
||||||
|
|
||||||
|
Plus simple que de gerer les permissions row-level :
|
||||||
|
- Form public sur `attribution` — formateur saisit ses heures via lien sans compte Baserow
|
||||||
|
- Form public sur `intervention` — dev saisit son intervention idem
|
||||||
|
|
||||||
|
Le user qui saisit n'a pas besoin de voir le reste des donnees, juste son formulaire.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Webhooks (Phase 2 — bridge integration)
|
||||||
|
|
||||||
|
Configurer dans Baserow → Database Settings → Webhooks :
|
||||||
|
|
||||||
|
| Evenement | URL cible | Usage bridge |
|
||||||
|
|-----------|-----------|--------------|
|
||||||
|
| `row.created` sur `attribution` | `https://bridge.acadenice.fr/webhooks/attribution-created` | Notif formateur, recalcul cache mention |
|
||||||
|
| `row.updated` sur `attribution` | `https://bridge.acadenice.fr/webhooks/attribution-updated` | Recalcul cache, notif si statut change |
|
||||||
|
| `row.created` sur `intervention` | `https://bridge.acadenice.fr/webhooks/intervention-created` | Notif admin si depassement capacite |
|
||||||
|
| `row.updated` sur `module` (si `module_statut` change) | `https://bridge.acadenice.fr/webhooks/module-status-changed` | Trigger cloturer formation auto |
|
||||||
|
|
||||||
|
Authentification webhook : header `X-Bridge-Token` avec un secret partage (`.env`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Seed data initial
|
||||||
|
|
||||||
|
Apres creation des 9 tables, seed avec :
|
||||||
|
- **personne** : equipe Acadenice (Yan, Corentin, Ludo, Sophie + formateurs intervenants)
|
||||||
|
- **client** : 1-2 clients existants (Centralis Europe + autre)
|
||||||
|
- **formation** : les 5 filieres en cours pour 2026-2027
|
||||||
|
- **bloc** : decoupage RNCP par filiere
|
||||||
|
- **module** : programme detaille
|
||||||
|
- **projet** : projets clients en cours
|
||||||
|
- Pas d'attribution / intervention seed — saisies par l'usage
|
||||||
|
|
||||||
|
Script de seed : `baserow/seed/seed.py` (a coder Phase 1).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Validation post-creation
|
||||||
|
|
||||||
|
Checklist apres creation des 9 tables :
|
||||||
|
|
||||||
|
- [ ] Les 9 tables existent dans la database `formation-hub`
|
||||||
|
- [ ] Toutes les FK sont liees correctement (verifier en cliquant sur un lien dans une row)
|
||||||
|
- [ ] Les rollups fonctionnent (creer une row test, verifier le calcul de `formation_heures_attribuees`)
|
||||||
|
- [ ] Les formulas s'evaluent sans erreur (regarder les rows test)
|
||||||
|
- [ ] Les Single Select / Multiple Select ont les bonnes options
|
||||||
|
- [ ] Au moins une vue par table est creee (grid `Tous` minimum)
|
||||||
|
- [ ] Les vues kanban (module, projet, tache, client) sont fonctionnelles
|
||||||
|
- [ ] Le form public pour saisie heures (attribution, intervention) marche
|
||||||
|
- [ ] L'API token est genere et fonctionne (test `curl`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test API token
|
||||||
|
curl -H "Authorization: Token $BASEROW_API_TOKEN" \
|
||||||
|
"$BASEROW_URL/api/database/rows/table/<TABLE_ID>/?user_field_names=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Notes d'implementation
|
||||||
|
|
||||||
|
### 15.1 Limitations Baserow connues
|
||||||
|
|
||||||
|
- **Pas de FK `ON DELETE` configurable** : c'est `SET NULL` par defaut quand le lien est rompu. Pour forcer un comportement CASCADE/RESTRICT, le bridge service doit l'implementer (ou un workflow Baserow).
|
||||||
|
- **Pas de CHECK constraint** : validation cote bridge ou cote UI.
|
||||||
|
- **Pas d'index custom** : Baserow indexe automatiquement les Link to table et les Primary fields.
|
||||||
|
- **Formules limitees** : pas de boucles ni de subqueries complexes. Pour calculs lourds, calcul cote bridge + ecriture en batch.
|
||||||
|
|
||||||
|
### 15.2 Alternatives si Baserow limite
|
||||||
|
|
||||||
|
Si une formule devient trop complexe ou si on a besoin de validation forte :
|
||||||
|
- Option A : **Bridge fait le calcul** et ecrit en Baserow via API
|
||||||
|
- Option B : **Vue filtree dediee** + formula simple
|
||||||
|
- Option C : **Migration vers Postgres direct** (futur — si on perd Baserow)
|
||||||
|
|
||||||
|
### 15.3 Migration data initiale
|
||||||
|
|
||||||
|
Si donnees existent dans Excel/Trello/autre :
|
||||||
|
1. Exporter en CSV
|
||||||
|
2. Mapper les colonnes vers les fields Baserow
|
||||||
|
3. Importer via Baserow UI (`Import data`)
|
||||||
|
4. Verifier les liens FK manuellement (Baserow ne mappe pas auto les liens via CSV)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Resume — checklist d'implementation Phase 1
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 1. Setup workspace + database
|
||||||
|
[ ] 2. Creer table 'personne' (sans liens encore)
|
||||||
|
[ ] 3. Creer table 'client' (sans liens encore)
|
||||||
|
[ ] 4. Creer table 'formation' (sans liens encore)
|
||||||
|
[ ] 5. Creer table 'bloc' + lien vers formation
|
||||||
|
[ ] 6. Creer table 'projet' + lien vers client (+ optionnel formation)
|
||||||
|
[ ] 7. Creer table 'module' + lien vers bloc
|
||||||
|
[ ] 8. Creer table 'tache' + lien vers projet (+ optionnel personne assignee)
|
||||||
|
[ ] 9. Creer table 'attribution' + liens vers module + personne
|
||||||
|
[ ] 10. Creer table 'intervention' + liens vers tache + personne
|
||||||
|
[ ] 11. Ajouter formulas et lookups (apres tous les liens crees)
|
||||||
|
[ ] 12. Creer vues recommandees par table
|
||||||
|
[ ] 13. Configurer permissions roles + sharing
|
||||||
|
[ ] 14. Seed data initial
|
||||||
|
[ ] 15. Generer API token + verifier
|
||||||
|
[ ] 16. Documenter exports JSON dans `baserow/schemas/`
|
||||||
|
```
|
||||||
|
|
||||||
|
Apres ca : la base structurelle est en place. La saisie metier peut commencer **immediat** — sans attendre Phase 2 / bridge.
|
||||||
259
docs/16-plan-tests.md
Normal file
259
docs/16-plan-tests.md
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
# Plan de tests
|
||||||
|
|
||||||
|
> Strategie de qualite : niveaux de tests, outils, coverage, criteres d'acceptance, regression.
|
||||||
|
> Audience : Corentin + freelance ponctuel (Phase 2 bridge).
|
||||||
|
|
||||||
|
## 1. Strategie globale — Pyramide
|
||||||
|
|
||||||
|
```
|
||||||
|
/\
|
||||||
|
/E2E\ peu nombreux, lents, fragiles, hauts dans la stack
|
||||||
|
/------\
|
||||||
|
/ INT \ middle — verifie les contrats Baserow/Docmost/bridge
|
||||||
|
/----------\
|
||||||
|
/ UNIT \ nombreux, rapides, isoles — bridge service uniquement
|
||||||
|
/--------------\
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Unit tests** : 70% du volume, sur le code custom (bridge)
|
||||||
|
- **Integration tests** : 25%, sur les vrais clients Baserow/Docmost (containers de test)
|
||||||
|
- **E2E tests** : 5%, parcours utilisateur complet sur staging
|
||||||
|
- **UX manuel + NFR** : checklist par release
|
||||||
|
|
||||||
|
## 2. Scope du test
|
||||||
|
|
||||||
|
Ce qui se teste :
|
||||||
|
- **Bridge service** (notre seul code custom) : 100% obligatoire
|
||||||
|
- **Configurations Baserow** (formules, vues) : tests manuels checklist
|
||||||
|
- **Configurations Docmost** (perms, share links) : tests manuels checklist
|
||||||
|
- **Workflows metier** : tests E2E des parcours principaux
|
||||||
|
|
||||||
|
Ce qui ne se teste pas :
|
||||||
|
- Code upstream Docmost/Baserow (deja teste par eux)
|
||||||
|
- Postgres/Redis (assume fonctionnel)
|
||||||
|
|
||||||
|
## 3. Niveaux de tests
|
||||||
|
|
||||||
|
### 3.1 Unit tests (bridge)
|
||||||
|
|
||||||
|
| Aspect | Spec |
|
||||||
|
|--------|------|
|
||||||
|
| Outil | Vitest |
|
||||||
|
| Cible | `bridge/src/**` — domain models, formulas, utils, validators |
|
||||||
|
| Mock | Pas de Baserow/Docmost reels, mocks via `vi.mock()` |
|
||||||
|
| Coverage minimum | 80% sur `bridge/src/domain/` et `bridge/src/lib/`, 70% global |
|
||||||
|
| Exemples a tester | `Personne.heuresRestantesTotal()`, `Module.creerAttribution()` validations RG, parsers d'entree |
|
||||||
|
| Run | `npm run test:unit` |
|
||||||
|
| CI | A chaque push + PR |
|
||||||
|
|
||||||
|
Pattern :
|
||||||
|
```typescript
|
||||||
|
// bridge/src/domain/personne.test.ts
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { Personne } from './personne';
|
||||||
|
import { Decimal } from 'decimal.js';
|
||||||
|
|
||||||
|
describe('Personne.heuresRestantesTotal', () => {
|
||||||
|
it('returns capacity - allocated', () => {
|
||||||
|
const p = new Personne({
|
||||||
|
capaciteAnnuelle: new Decimal(1500),
|
||||||
|
heuresAttribueesFormation: new Decimal(400),
|
||||||
|
heuresAttribueesAgence: new Decimal(600),
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
expect(p.heuresRestantesTotal().toNumber()).toBe(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles overflow (negative result)', () => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Integration tests (bridge ↔ services)
|
||||||
|
|
||||||
|
| Aspect | Spec |
|
||||||
|
|--------|------|
|
||||||
|
| Outil | Vitest + testcontainers (Postgres + Redis reels) |
|
||||||
|
| Cible | Adapters Baserow/Docmost, webhook handlers, cache Redis |
|
||||||
|
| Setup | Docker compose `compose.test.yml` lance Baserow et Docmost containers ephemeres |
|
||||||
|
| Coverage | Tous les endpoints du bridge |
|
||||||
|
| Run | `npm run test:integration` |
|
||||||
|
| CI | A chaque push (utilise services Postgres/Redis du runner GitHub Actions) |
|
||||||
|
| Duree max | 5 min (sinon a optimiser ou move vers nightly) |
|
||||||
|
|
||||||
|
Exemples :
|
||||||
|
- `POST /interventions` cree bien une row dans Baserow, recalcule rollups
|
||||||
|
- `Webhook row.created` declenche cache invalidation Redis
|
||||||
|
- Auth `Authorization: Bearer <token>` valide ou refuse correctement
|
||||||
|
|
||||||
|
### 3.3 E2E tests (workflows complets)
|
||||||
|
|
||||||
|
| Aspect | Spec |
|
||||||
|
|--------|------|
|
||||||
|
| Outil | Playwright |
|
||||||
|
| Cible | Parcours metier complets sur env staging |
|
||||||
|
| Browsers | Chromium + Firefox (mobile WebKit pour saisie heures) |
|
||||||
|
| Run | `npm run test:e2e` |
|
||||||
|
| CI | Apres deploy staging reussi (pas a chaque push) |
|
||||||
|
| Duree max | 15 min |
|
||||||
|
|
||||||
|
Parcours E2E a couvrir (top 5 priorite) :
|
||||||
|
1. **Admin cree formation → blocs → modules** (UC-01 + UC-02)
|
||||||
|
2. **Admin attribue module a un formateur** (UC-03)
|
||||||
|
3. **Formateur saisit heures realisees via mobile** (UC-13)
|
||||||
|
4. **Admin cree client → projet → taches** (UCA-01 + UCA-02 + UCA-03)
|
||||||
|
5. **Dev saisit intervention sur tache** (UCA-07)
|
||||||
|
|
||||||
|
### 3.4 UX manuel — checklist
|
||||||
|
|
||||||
|
Pas automatisable (parle de feel, intuitivite). Pour chaque release :
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] Login Docmost et Baserow OK
|
||||||
|
[ ] Saisie d'une formation prend < 30s
|
||||||
|
[ ] Saisie heures realisees mobile prend < 15s sur smartphone
|
||||||
|
[ ] Diagrammes Mermaid/Drawio/Excalidraw rendent OK dans une page Docmost
|
||||||
|
[ ] Share link client fonctionne sans compte
|
||||||
|
[ ] Recherche full-text Docmost trouve une page recente
|
||||||
|
[ ] Filtres Baserow sur les vues principales fonctionnent
|
||||||
|
[ ] Pas de regression visible sur les 3 vues kanban (modules / projets / taches)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 NFR — tests non-fonctionnels
|
||||||
|
|
||||||
|
| Categorie | Test | Cible |
|
||||||
|
|-----------|------|-------|
|
||||||
|
| **Performance** | Latence saisie intervention (UCA-07) | p95 < 2s |
|
||||||
|
| Performance | Recherche full-text Docmost | p95 < 500ms |
|
||||||
|
| Performance | Recalcul rollup Baserow apres saisie | < 5s |
|
||||||
|
| **Securite** | Secret scanning (TruffleHog) | Zero hit |
|
||||||
|
| Securite | SAST (Semgrep) | Zero finding `error` severity |
|
||||||
|
| Securite | Dependency CVE (npm audit) | Zero `high`/`critical` |
|
||||||
|
| Securite | Auth bypass tentative (pentest leger) | 401 sur endpoints proteges |
|
||||||
|
| **Accessibility** | Lighthouse a11y score | >= 90 sur pages publiques |
|
||||||
|
| **Charge** | 30 users simultanes (k6) | Latence p95 < 3s, error rate < 1% |
|
||||||
|
| **Backup** | Restore depuis backup recent | RTO < 4h, integrity 100% |
|
||||||
|
|
||||||
|
## 4. Donnees de test (fixtures)
|
||||||
|
|
||||||
|
### 4.1 Strategie
|
||||||
|
|
||||||
|
- **Unit tests** : objects in-memory crees ad-hoc dans le test
|
||||||
|
- **Integration tests** : seed Baserow ephemere via API a chaque test (fast)
|
||||||
|
- **E2E tests** : staging environment avec data realiste anonymisee + reset post-test
|
||||||
|
|
||||||
|
### 4.2 Fixtures versionnees
|
||||||
|
|
||||||
|
`bridge/tests/fixtures/`
|
||||||
|
- `personnes.json` : 5 personnes types (admin pur, formateur seul, dev seul, formateur+dev, inactif)
|
||||||
|
- `formations.json` : 2 formations (1 active, 1 archivee)
|
||||||
|
- `clients.json` : 3 clients (prospect, actif, archive)
|
||||||
|
- ...
|
||||||
|
|
||||||
|
Chargement : helper `loadFixture('personnes')` dans tests.
|
||||||
|
|
||||||
|
## 5. Test environments
|
||||||
|
|
||||||
|
| Env | Donnees | Usage |
|
||||||
|
|-----|---------|-------|
|
||||||
|
| `local` (dev) | Fixtures seedees a chaque `make up` | Dev quotidien |
|
||||||
|
| `test` (CI) | Containers ephemeres + fixtures | Integration tests CI |
|
||||||
|
| `staging` | Data realiste anonymisee | E2E + UX manuel |
|
||||||
|
| `prod` | Data reelle | Pas de tests destructifs |
|
||||||
|
|
||||||
|
## 6. Quality gates
|
||||||
|
|
||||||
|
A chaque PR, **bloquant** pour merge si rouge :
|
||||||
|
|
||||||
|
| Check | Tool | Critere |
|
||||||
|
|-------|------|---------|
|
||||||
|
| Lint | Biome | Zero error |
|
||||||
|
| Type check | tsc | Zero error |
|
||||||
|
| Unit tests | Vitest | 100% pass |
|
||||||
|
| Integration tests | Vitest + testcontainers | 100% pass |
|
||||||
|
| Coverage unit | Vitest | >= 70% global, 80% sur domain |
|
||||||
|
| Secret scan | TruffleHog | Zero hit |
|
||||||
|
| SAST | Semgrep | Zero `error` severity |
|
||||||
|
| Dep audit | npm audit | Zero `high`/`critical` |
|
||||||
|
| Docker build | docker compose | OK |
|
||||||
|
|
||||||
|
E2E tests **non bloquants pour merge** mais bloquants pour deploy prod (run apres deploy staging).
|
||||||
|
|
||||||
|
## 7. Acceptance criteria par feature
|
||||||
|
|
||||||
|
Format Gherkin pour les UC principaux :
|
||||||
|
|
||||||
|
```gherkin
|
||||||
|
Feature: Saisir heures realisees (UC-13)
|
||||||
|
As a Formateur
|
||||||
|
I want to log my actual hours per attribution
|
||||||
|
So that the rollups update and admin sees real progress
|
||||||
|
|
||||||
|
Scenario: Formateur saisit ses heures dans la limite
|
||||||
|
Given une attribution "Module JS / Pierre" en statut planifie avec 10h attribuees
|
||||||
|
When Pierre saisit 3h realisees
|
||||||
|
Then attribution.heures_realisees = 3h
|
||||||
|
And module.heures_realisees est recalcule
|
||||||
|
And personne.heures_attribuees_formation reste a 10h
|
||||||
|
And no warning displayed
|
||||||
|
|
||||||
|
Scenario: Formateur saisit en depassement
|
||||||
|
Given une attribution avec 10h attribuees, deja 8h realisees
|
||||||
|
When Pierre saisit 4h supplementaires (total 12h, depasse de 2h)
|
||||||
|
Then warning "Depassement detecte, justification requise"
|
||||||
|
And attribution.heures_realisees = 12h apres confirmation justification
|
||||||
|
```
|
||||||
|
|
||||||
|
A faire pour chaque UC critique (UC-01, UC-03, UC-13, UCA-02, UCA-07).
|
||||||
|
|
||||||
|
## 8. Plan de regression
|
||||||
|
|
||||||
|
Avant chaque release vers prod :
|
||||||
|
|
||||||
|
1. Run full test suite (unit + integration + E2E sur staging)
|
||||||
|
2. UX checklist manuel (cf section 3.4)
|
||||||
|
3. Smoke test post-deploy prod (verifier 3 endpoints critiques)
|
||||||
|
4. Verification monitoring (logs / metriques 30 min apres deploy)
|
||||||
|
|
||||||
|
Si une regression majeure est detectee : rollback (cf doc 14 section 12).
|
||||||
|
|
||||||
|
## 9. Test de migration data
|
||||||
|
|
||||||
|
Lors de l'import data initial (formations existantes / formateurs / clients) :
|
||||||
|
|
||||||
|
1. **Dry-run** : mapping CSV → Baserow rows en memoire, validation schema, rapport ecarts
|
||||||
|
2. **Test import** sur env staging avec subset (10 rows)
|
||||||
|
3. **Verification** integrite : rollups calcules correctement, FK liees
|
||||||
|
4. **Import prod** apres validation
|
||||||
|
5. **Reconciliation** : compare nb rows attendus vs imported
|
||||||
|
|
||||||
|
## 10. Outils — recap
|
||||||
|
|
||||||
|
| Outil | Role | Where |
|
||||||
|
|-------|------|-------|
|
||||||
|
| Vitest | Unit + integration tests | bridge/ |
|
||||||
|
| testcontainers | Postgres/Redis containers ephemeres | bridge/tests/integration |
|
||||||
|
| Playwright | E2E sur staging | bridge/tests/e2e |
|
||||||
|
| k6 | Load testing | scripts/load-test.js |
|
||||||
|
| Lighthouse | A11y + perf web | nightly via CI ou manuel |
|
||||||
|
| TruffleHog | Secret scanning | CI |
|
||||||
|
| Semgrep | SAST | CI |
|
||||||
|
| npm audit / Dependabot | Dep CVE | CI + auto |
|
||||||
|
| Biome | Lint + format | CI |
|
||||||
|
|
||||||
|
## 11. Roadmap tests
|
||||||
|
|
||||||
|
| Phase | Couverture |
|
||||||
|
|-------|-----------|
|
||||||
|
| Phase 1 (vanilla) | Tests manuels checklist UX, smoke tests, validation post-import data |
|
||||||
|
| Phase 2 (bridge code) | Unit + integration obligatoires sur le code bridge des le jour 1 |
|
||||||
|
| Phase 3 (maturite) | Ajouter E2E Playwright sur staging, NFR (perf + a11y), load tests |
|
||||||
|
| Phase 4 | Test de DR (restauration backup) mensuel |
|
||||||
|
|
||||||
|
## 12. Questions ouvertes
|
||||||
|
|
||||||
|
- [ ] Coverage minimum exacte ? Le doc 14 propose 70% — a confirmer avec Yan/Ludo
|
||||||
|
- [ ] Tests d'accessibilite obligatoires ou nice-to-have ? (RGAA conformance pour les pages publiques etudiants ?)
|
||||||
|
- [ ] Tests de charge : a partir de quelle Phase ? (proba pas avant Phase 3)
|
||||||
|
- [ ] Outils de monitoring synthetic (UptimeRobot pour healthchecks) — a definir doc 18
|
||||||
500
docs/17-plan-deployment.md
Normal file
500
docs/17-plan-deployment.md
Normal file
|
|
@ -0,0 +1,500 @@
|
||||||
|
# Plan de deployment
|
||||||
|
|
||||||
|
> Strategie de deploiement : provisionnement, CI/CD detaille, releases, migrations, rollback.
|
||||||
|
> Complete `14-repo-structure-gitops.md` (qui pose la structure CI/CD).
|
||||||
|
|
||||||
|
## 1. Vue d'ensemble — 3 environnements
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Dev[Dev local<br/>make up<br/>fixtures seed] --> Push[git push origin main]
|
||||||
|
Push -->|auto| Staging[Staging<br/>wiki.staging.acadenice.fr<br/>data anonymisee]
|
||||||
|
Staging --> Tag[git tag v1.X.Y]
|
||||||
|
Tag -->|approval review| Prod[Prod<br/>wiki.acadenice.fr<br/>data reelle]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Env | Trigger deploy | Approval | Data | Target |
|
||||||
|
|-----|----------------|----------|------|--------|
|
||||||
|
| local | `make up` | — | seed fixtures | dev quotidien |
|
||||||
|
| staging | push `main` | auto | anonymisee | qualif metier + E2E |
|
||||||
|
| prod | tag `v*` | manual reviewer | reelle | utilisateurs finaux |
|
||||||
|
|
||||||
|
## 2. Provisionnement infra
|
||||||
|
|
||||||
|
### 2.1 Hardware cible
|
||||||
|
|
||||||
|
| Env | Specs | Cout/mois | Provider candidat |
|
||||||
|
|-----|-------|-----------|-------------------|
|
||||||
|
| staging | 2 vCPU, 4 Go RAM, 40 Go SSD | ~7€ | Hetzner CX21 ou OVH equivalent |
|
||||||
|
| prod | 4 vCPU, 8 Go RAM, 80 Go SSD | ~15€ | Hetzner CPX31 |
|
||||||
|
| backup distant | 100 Go object storage | ~5€ | Backblaze B2 ou OVH Object Storage |
|
||||||
|
|
||||||
|
### 2.2 OS et stack base
|
||||||
|
|
||||||
|
- **OS** : Debian 12 (stable, support long, deja maitrise par Corentin)
|
||||||
|
- **Docker** : version 25+ via repo officiel
|
||||||
|
- **Docker Compose** : v2 (plugin standard)
|
||||||
|
- **Reverse proxy** : Traefik 3 (deja en place sur Acadenice)
|
||||||
|
- **Cron** : crond systeme pour backups nocturnes
|
||||||
|
|
||||||
|
### 2.3 DNS et TLS
|
||||||
|
|
||||||
|
| Sous-domaine | Pointe vers | TLS |
|
||||||
|
|--------------|-------------|-----|
|
||||||
|
| `wiki.acadenice.fr` | VPS prod | Let's Encrypt via Traefik |
|
||||||
|
| `baserow.acadenice.fr` | VPS prod | Let's Encrypt via Traefik |
|
||||||
|
| `bridge.acadenice.fr` | VPS prod | Let's Encrypt via Traefik (Phase 2+) |
|
||||||
|
| `wiki.staging.acadenice.fr` | VPS staging | Let's Encrypt |
|
||||||
|
| `baserow.staging.acadenice.fr` | VPS staging | Let's Encrypt |
|
||||||
|
|
||||||
|
Traefik genere les certificats automatiquement via ACME (HTTP-01 challenge).
|
||||||
|
|
||||||
|
### 2.4 Provisionnement initial (premiere fois)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. SSH sur le VPS frais
|
||||||
|
ssh root@<vps-ip>
|
||||||
|
|
||||||
|
# 2. Hardening de base
|
||||||
|
adduser corentin --gecos ""
|
||||||
|
usermod -aG sudo,docker corentin
|
||||||
|
ssh-copy-id corentin@<vps-ip>
|
||||||
|
# Editer /etc/ssh/sshd_config :
|
||||||
|
# PermitRootLogin no
|
||||||
|
# PasswordAuthentication no
|
||||||
|
systemctl restart sshd
|
||||||
|
|
||||||
|
# 3. Installer Docker
|
||||||
|
curl -fsSL https://get.docker.com | sh
|
||||||
|
|
||||||
|
# 4. Cloner le repo
|
||||||
|
mkdir -p /opt/formation-hub && cd /opt/formation-hub
|
||||||
|
git clone git@github.com:acadenice/formation-hub.git .
|
||||||
|
|
||||||
|
# 5. Configurer .env
|
||||||
|
cp .env.example .env.staging # ou .env.prod
|
||||||
|
nano .env.staging # remplir avec secrets reels
|
||||||
|
|
||||||
|
# 6. Lancer
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml up -d
|
||||||
|
|
||||||
|
# 7. Verifier
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Note : **a executer une seule fois** par environnement. Apres, c'est CI/CD qui prend le relais.
|
||||||
|
|
||||||
|
## 3. CI/CD detaille
|
||||||
|
|
||||||
|
### 3.1 Vue d'ensemble des workflows
|
||||||
|
|
||||||
|
| Workflow | Trigger | Duree max | Bloque sur echec |
|
||||||
|
|----------|---------|-----------|------------------|
|
||||||
|
| `ci.yml` | push + PR | 10 min | Merge bloque |
|
||||||
|
| `deploy-staging.yml` | push `main` (apres CI vert) | 5 min | Pas de deploy si CI rouge |
|
||||||
|
| `deploy-prod.yml` | tag `v*` | 5 min + approval | Pas de deploy sans approval |
|
||||||
|
| `nightly-backup-test.yml` | cron 03:00 mensuel | 30 min | Alerte slack si fail |
|
||||||
|
| `e2e.yml` | apres `deploy-staging` reussi | 15 min | Pas bloquant pour staging mais bloquant pour tag prod |
|
||||||
|
|
||||||
|
### 3.2 Workflow `ci.yml` (full)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with: { node-version: 22, cache: 'npm', cache-dependency-path: 'bridge/package-lock.json' }
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run lint
|
||||||
|
|
||||||
|
type-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with: { node-version: 22, cache: 'npm', cache-dependency-path: 'bridge/package-lock.json' }
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run typecheck
|
||||||
|
|
||||||
|
test-unit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: type-check
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with: { node-version: 22 }
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run test:unit -- --coverage
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-unit
|
||||||
|
path: bridge/coverage
|
||||||
|
|
||||||
|
test-integration:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: type-check
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
env: { POSTGRES_PASSWORD: test, POSTGRES_DB: testdb }
|
||||||
|
ports: ['5432:5432']
|
||||||
|
options: --health-cmd pg_isready --health-interval 5s --health-retries 10
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports: ['6379:6379']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with: { node-version: 22 }
|
||||||
|
- run: cd bridge && npm ci
|
||||||
|
- run: cd bridge && npm run test:integration
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://postgres:test@localhost:5432/testdb
|
||||||
|
REDIS_URL: redis://localhost:6379
|
||||||
|
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with: { fetch-depth: 0 }
|
||||||
|
- name: Secret scanning
|
||||||
|
uses: trufflesecurity/trufflehog@main
|
||||||
|
with:
|
||||||
|
path: ./
|
||||||
|
base: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||||
|
- name: SAST
|
||||||
|
uses: returntocorp/semgrep-action@v1
|
||||||
|
with: { config: 'p/javascript p/typescript p/security-audit' }
|
||||||
|
- name: Dep audit
|
||||||
|
run: cd bridge && npm audit --audit-level=high
|
||||||
|
- name: License check
|
||||||
|
run: cd bridge && npx license-checker --failOn 'GPL-3.0;AGPL-3.0' --excludePackages 'bridge'
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test-unit, test-integration, security]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: docker compose build
|
||||||
|
- run: docker compose up -d
|
||||||
|
- run: ./scripts/healthcheck.sh
|
||||||
|
- run: docker compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Workflow `deploy-staging.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy Staging
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['CI']
|
||||||
|
types: [completed]
|
||||||
|
branches: [main]
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'push' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: staging
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build & push image
|
||||||
|
run: |
|
||||||
|
docker build -t registry.acadenice.fr/formation-hub/bridge:${{ github.sha }} bridge/
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.acadenice.fr -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
docker push registry.acadenice.fr/formation-hub/bridge:${{ github.sha }}
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.STAGING_HOST }}
|
||||||
|
username: corentin
|
||||||
|
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
cd /opt/formation-hub
|
||||||
|
git fetch && git checkout ${{ github.sha }}
|
||||||
|
export BRIDGE_IMAGE=registry.acadenice.fr/formation-hub/bridge:${{ github.sha }}
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml pull
|
||||||
|
docker compose -f compose.yml -f compose.staging.yml up -d
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
- name: Notify Slack on failure
|
||||||
|
if: failure()
|
||||||
|
uses: slackapi/slack-github-action@v1
|
||||||
|
with:
|
||||||
|
payload: '{"text":"Deploy staging FAILED — sha ${{ github.sha }}"}'
|
||||||
|
env: { SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Workflow `deploy-prod.yml`
|
||||||
|
|
||||||
|
Identique mais cible prod, avec `environment: production` qui active les **required reviewers** (Yan ou Corentin doivent approuver dans GitHub UI).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy Production
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: production # required reviewers: Yan, Corentin
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with: { ref: ${{ github.ref_name }} }
|
||||||
|
- name: Tag image as prod
|
||||||
|
run: |
|
||||||
|
docker pull registry.acadenice.fr/formation-hub/bridge:${{ github.sha }}
|
||||||
|
docker tag registry.acadenice.fr/formation-hub/bridge:${{ github.sha }} registry.acadenice.fr/formation-hub/bridge:${{ github.ref_name }}
|
||||||
|
docker push registry.acadenice.fr/formation-hub/bridge:${{ github.ref_name }}
|
||||||
|
- name: Deploy via SSH
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.PROD_HOST }}
|
||||||
|
username: corentin
|
||||||
|
key: ${{ secrets.PROD_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
cd /opt/formation-hub
|
||||||
|
git fetch --tags && git checkout ${{ github.ref_name }}
|
||||||
|
export BRIDGE_IMAGE=registry.acadenice.fr/formation-hub/bridge:${{ github.ref_name }}
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml pull
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml up -d
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
- name: Update CHANGELOG
|
||||||
|
run: # commit et push CHANGELOG dans une PR auto si pas deja fait
|
||||||
|
- name: Notify Slack
|
||||||
|
uses: slackapi/slack-github-action@v1
|
||||||
|
with:
|
||||||
|
payload: '{"text":"PROD deployed: ${{ github.ref_name }}"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Strategie de release
|
||||||
|
|
||||||
|
### 4.1 Convention semver
|
||||||
|
|
||||||
|
| Type | Quand | Exemple |
|
||||||
|
|------|-------|---------|
|
||||||
|
| MAJOR | Breaking change | v1.x.x → v2.0.0 (changement schema Baserow incompatible) |
|
||||||
|
| MINOR | Nouvelle feature backward-compatible | v1.2.x → v1.3.0 (ajout endpoint bridge) |
|
||||||
|
| PATCH | Bug fix / security fix | v1.2.3 → v1.2.4 |
|
||||||
|
|
||||||
|
### 4.2 Process de release
|
||||||
|
|
||||||
|
```
|
||||||
|
1. PRs mergees sur main → deploy staging auto
|
||||||
|
2. QA staging (UX checklist + E2E run)
|
||||||
|
3. Si OK :
|
||||||
|
- Update CHANGELOG.md (deplacer "Unreleased" → version)
|
||||||
|
- git tag -a v1.2.3 -m "Release v1.2.3 — features:..."
|
||||||
|
- git push origin v1.2.3
|
||||||
|
4. GitHub Action deploy-prod se declenche
|
||||||
|
5. Approval review (Yan ou Corentin)
|
||||||
|
6. Deploy execute
|
||||||
|
7. Post-deploy : monitoring 30 min
|
||||||
|
8. Si issue : rollback (cf section 6)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Format CHANGELOG
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Feature X
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Y
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Z
|
||||||
|
|
||||||
|
## [1.2.3] - 2026-06-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Endpoint /personnes/:id/timeline
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Recalcul rollup formation depuis attribution annulee
|
||||||
|
```
|
||||||
|
|
||||||
|
Convention : [Keep a Changelog](https://keepachangelog.com).
|
||||||
|
|
||||||
|
## 5. Migration database
|
||||||
|
|
||||||
|
### 5.1 Strategie
|
||||||
|
|
||||||
|
- **Baserow** : modifications de schema via UI Baserow OU API. Versionner les schemas dans `baserow/schemas/*.json` (export periodique).
|
||||||
|
- **Bridge service** : pas de DB propre en Phase 2 (stateless). Si plus tard on ajoute Postgres dedie : utiliser Drizzle migrations versionnees dans `bridge/migrations/`.
|
||||||
|
|
||||||
|
### 5.2 Pre-migration checklist
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] Backup Baserow + Postgres docmost FRESH avant migration
|
||||||
|
[ ] Test migration sur staging avec data realiste anonymisee
|
||||||
|
[ ] Verification integrite post-migration sur staging
|
||||||
|
[ ] Plan rollback documente (revert schema OU restore backup)
|
||||||
|
[ ] Annonce equipe : window de maintenance prevue
|
||||||
|
[ ] Migration prod sur creneau low-traffic (early morning ou weekend)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Pendant la migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Stop services (eviter ecritures concurrentes)
|
||||||
|
docker compose stop docmost baserow
|
||||||
|
|
||||||
|
# 2. Backup explicite
|
||||||
|
make backup
|
||||||
|
|
||||||
|
# 3. Run migration script (manuel ou via bridge command)
|
||||||
|
./scripts/migrate.sh v1.2.3
|
||||||
|
|
||||||
|
# 4. Verification integrite
|
||||||
|
./scripts/healthcheck.sh
|
||||||
|
./scripts/verify-rollups.sh
|
||||||
|
|
||||||
|
# 5. Restart services
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml up -d
|
||||||
|
|
||||||
|
# 6. Smoke test post-migration
|
||||||
|
curl -fsS https://wiki.acadenice.fr/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Communication metier
|
||||||
|
|
||||||
|
Avant migration affectant prod :
|
||||||
|
- Email aux admins 48h avant
|
||||||
|
- Banner Docmost "maintenance prevue le X de Yh a Zh"
|
||||||
|
- Slack #internal au debut et fin
|
||||||
|
|
||||||
|
## 6. Rollback strategy detaillee
|
||||||
|
|
||||||
|
| Scenario | Action | Duree estimee |
|
||||||
|
|----------|--------|---------------|
|
||||||
|
| **Bug critique post-deploy prod** (regression majeure) | Re-deploy version precedente : `git tag v1.2.3-rollback v1.2.2 && git push --tags` → trigger deploy-prod sur la version stable | 5-10 min |
|
||||||
|
| **Migration schema casse rollups** | 1) `docker compose stop` 2) Restore Postgres docmost depuis backup 3) Restore Baserow data 4) Redeploy version stable | 30-60 min |
|
||||||
|
| **Compromission credentials** | 1) Revoke tokens API 2) Rotate secrets `.env.prod` 3) Redeploy 4) Audit logs 5) Communiquer si data leak | 1-4h |
|
||||||
|
| **VPS down** (provider issue) | Failover manuel vers VPS backup OU attendre provider | depend incident |
|
||||||
|
| **Bug minor en staging** | Hotfix sur main + redeploy staging. Pas de tag prod. | 10-20 min |
|
||||||
|
|
||||||
|
### 6.1 Pre-prod rollback test
|
||||||
|
|
||||||
|
Mensuel, sur staging :
|
||||||
|
1. Deploy une version
|
||||||
|
2. Simuler bug (fail healthcheck volontaire)
|
||||||
|
3. Re-deploy version precedente
|
||||||
|
4. Verifier que tout fonctionne
|
||||||
|
5. Logger le test dans le journal ops
|
||||||
|
|
||||||
|
## 7. Configuration env-specific
|
||||||
|
|
||||||
|
### 7.1 Variables par env
|
||||||
|
|
||||||
|
| Variable | local | staging | prod |
|
||||||
|
|----------|-------|---------|------|
|
||||||
|
| `DOCMOST_URL` | http://localhost:3000 | https://wiki.staging.acadenice.fr | https://wiki.acadenice.fr |
|
||||||
|
| `BASEROW_URL` | http://localhost:8080 | https://baserow.staging.acadenice.fr | https://baserow.acadenice.fr |
|
||||||
|
| `BRIDGE_URL` (Phase 2) | http://localhost:4000 | https://bridge.staging.acadenice.fr | https://bridge.acadenice.fr |
|
||||||
|
| `LOG_LEVEL` | debug | info | warn |
|
||||||
|
| `BACKUP_S3_BUCKET` | (none) | (none) | s3://acadenice-formation-hub-backup |
|
||||||
|
| `SENTRY_DSN` (Phase 3+) | (none) | https://...sentry.io/staging | https://...sentry.io/prod |
|
||||||
|
|
||||||
|
### 7.2 Secret management workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Generer un nouveau secret (random 32+ chars)
|
||||||
|
2. Stocker dans pass / 1Password / Vault interne
|
||||||
|
3. Set en GitHub Secret (env-scoped)
|
||||||
|
4. Set en .env.staging / .env.prod sur le serveur (perms 600, owner root)
|
||||||
|
5. Test deploy
|
||||||
|
6. Rotater l'ancien secret (revoque cote service)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Frequence rotation
|
||||||
|
|
||||||
|
| Type secret | Frequence rotation |
|
||||||
|
|-------------|-------------------|
|
||||||
|
| API tokens externes (Outline, etc.) | Annuelle |
|
||||||
|
| DB passwords | Trimestrielle |
|
||||||
|
| JWT signing keys | Trimestrielle |
|
||||||
|
| SSH keys deploy | Annuelle ou sur depart |
|
||||||
|
| Backup encryption keys | Conserve hors-bande, rotate seulement si compromise |
|
||||||
|
|
||||||
|
## 8. Pre-deploy checklist (par release)
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] CI vert sur la PR
|
||||||
|
[ ] Tests E2E staging passent
|
||||||
|
[ ] CHANGELOG.md a jour
|
||||||
|
[ ] Migration data documentee si schema change
|
||||||
|
[ ] Pre-prod rollback test recent (< 1 mois)
|
||||||
|
[ ] Pas de PR open critique
|
||||||
|
[ ] Backup recent (< 24h) verifie
|
||||||
|
[ ] Approval reviewer disponible (Yan ou Corentin)
|
||||||
|
[ ] Pas de creneau metier critique (cours en cours / saisie deadline)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Post-deploy validation
|
||||||
|
|
||||||
|
### 9.1 Smoke tests automatiques (script)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# scripts/smoke-test.sh
|
||||||
|
ENV_URL=$1 # https://wiki.acadenice.fr ou staging
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 1. Healthcheck
|
||||||
|
curl -fsS "$ENV_URL/api/health" || exit 1
|
||||||
|
|
||||||
|
# 2. Login admin (test creds)
|
||||||
|
curl -fsS -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"email":"smoke@acadenice.fr","password":"..."}' \
|
||||||
|
"$ENV_URL/api/auth.email" > /dev/null
|
||||||
|
|
||||||
|
# 3. Lecture page test
|
||||||
|
curl -fsS "$ENV_URL/api/documents.info" -H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{"id":"smoke-test-page"}' > /dev/null
|
||||||
|
|
||||||
|
# 4. Test recherche
|
||||||
|
curl -fsS "$ENV_URL/api/documents.search" -H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{"query":"smoke"}' > /dev/null
|
||||||
|
|
||||||
|
echo "Smoke tests OK"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Manual checklist post-deploy
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] Smoke tests automatiques verts
|
||||||
|
[ ] Login Docmost web OK
|
||||||
|
[ ] Login Baserow web OK
|
||||||
|
[ ] Pages wiki recentes accessibles
|
||||||
|
[ ] Saisie test sur Baserow OK (heures realisees ou intervention)
|
||||||
|
[ ] Diagrammes Mermaid rendent OK sur une page test
|
||||||
|
[ ] Logs containers : pas d'erreurs dans les 5 dernieres minutes
|
||||||
|
[ ] Metriques system : CPU < 50%, RAM < 70% (apres charge initiale)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Watch period
|
||||||
|
|
||||||
|
30 minutes apres deploy prod :
|
||||||
|
- Logs surveilles activement
|
||||||
|
- Metriques uptime monitoring
|
||||||
|
- Si anomalie : trigger rollback
|
||||||
|
|
||||||
|
## 10. Questions ouvertes
|
||||||
|
|
||||||
|
- [ ] Registry images : GitHub Container Registry, registry.acadenice.fr (a deployer), ou Harbor self-host ?
|
||||||
|
- [ ] Backup distant : OVH Object Storage / Backblaze / S3 ? Choix selon prix + souverainete
|
||||||
|
- [ ] Sentry pour error tracking (Phase 3+) ? Self-host ou SaaS ?
|
||||||
|
- [ ] CI runner : GitHub-hosted (cout) ou self-hosted runner sur VPS Acadenice ?
|
||||||
|
- [ ] Notification deploy : Slack, Teams, email ? Tous les 3 ?
|
||||||
459
docs/18-plan-operations.md
Normal file
459
docs/18-plan-operations.md
Normal file
|
|
@ -0,0 +1,459 @@
|
||||||
|
# Plan d'operations (RUN)
|
||||||
|
|
||||||
|
> Strategie d'operations post-launch : monitoring, alerting, backups, DR, incident response, runbooks.
|
||||||
|
> Audience : Corentin (owner ops), Yan (backup), futur freelance.
|
||||||
|
|
||||||
|
## 1. Vue d'ensemble — RUN responsibilities
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph "Daily"
|
||||||
|
D1[Check uptime monitoring]
|
||||||
|
D2[Verifier logs erreurs]
|
||||||
|
D3[Review backups quotidiens]
|
||||||
|
end
|
||||||
|
subgraph "Weekly"
|
||||||
|
W1[Audit dependabot bumps]
|
||||||
|
W2[Check capacite disque/CPU]
|
||||||
|
W3[Review issues / PR ops]
|
||||||
|
end
|
||||||
|
subgraph "Monthly"
|
||||||
|
M1[Test restauration backup]
|
||||||
|
M2[Review security alerts]
|
||||||
|
M3[Audit access list]
|
||||||
|
M4[Capacity planning review]
|
||||||
|
end
|
||||||
|
subgraph "On Incident"
|
||||||
|
I1[Detect / Page]
|
||||||
|
I2[Triage]
|
||||||
|
I3[Mitigate / Restore]
|
||||||
|
I4[Post-mortem]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Monitoring
|
||||||
|
|
||||||
|
### 2.1 Stack de monitoring (Phase 1 minimal → Phase 3 complet)
|
||||||
|
|
||||||
|
| Phase | Tool | Role | Cout |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| **Phase 1** | UptimeRobot (free) | Healthcheck HTTP toutes 5 min sur wiki + baserow | 0€ |
|
||||||
|
| **Phase 2** | + Uptime Kuma self-host | Plus de granularite, dashboards perso | 0€ (sur prod VPS ou VPS dedie) |
|
||||||
|
| **Phase 3** | + Prometheus + Grafana | Metriques system + app, alerting fin | ~5€/mois (extra resources) |
|
||||||
|
| **Phase 3** | + Loki | Centralisation logs containers | ~5€/mois |
|
||||||
|
| **Phase 4** | + Sentry self-host ou SaaS | Error tracking app, stack traces | 0€-25€/mois |
|
||||||
|
|
||||||
|
### 2.2 Endpoints surveilles (Phase 1)
|
||||||
|
|
||||||
|
| Endpoint | Frequence | SLA cible |
|
||||||
|
|----------|-----------|-----------|
|
||||||
|
| `https://wiki.acadenice.fr` (HTTP 200) | 5 min | uptime >= 99% |
|
||||||
|
| `https://baserow.acadenice.fr/api/_health/` | 5 min | uptime >= 99% |
|
||||||
|
| `https://bridge.acadenice.fr/api/health` (Phase 2+) | 5 min | uptime >= 99% |
|
||||||
|
|
||||||
|
### 2.3 Metriques cles (Phase 3+)
|
||||||
|
|
||||||
|
System :
|
||||||
|
- CPU usage (alerte > 80% sustained 5 min)
|
||||||
|
- Memoire (alerte > 85%)
|
||||||
|
- Disque (alerte > 80%)
|
||||||
|
- Network in/out
|
||||||
|
|
||||||
|
Application :
|
||||||
|
- Latence p95 par endpoint (bridge)
|
||||||
|
- Taux d'erreurs HTTP 5xx (alerte > 1%)
|
||||||
|
- Throughput requests/sec
|
||||||
|
- Queue Redis depth (Baserow celery)
|
||||||
|
- Postgres connections actives (alerte > 80% pool size)
|
||||||
|
|
||||||
|
Business (custom) :
|
||||||
|
- Nb saisies heures/jour (sentinel : si chute brutale = bug saisie)
|
||||||
|
- Nb attributions creees/semaine
|
||||||
|
- Nb projets en cours
|
||||||
|
- Capacite formateurs depassee (alerte si > 0)
|
||||||
|
|
||||||
|
## 3. Alerting
|
||||||
|
|
||||||
|
### 3.1 Channels
|
||||||
|
|
||||||
|
| Channel | Severite | Cible |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| Email Corentin + Yan | Tous niveaux | corentin@acadenice.fr, yan@acadenice.fr |
|
||||||
|
| Slack/Teams #ops | warning + critical | Canal interne |
|
||||||
|
| SMS (Twilio ou OVH) | critical seulement | Corentin (oncall principal) |
|
||||||
|
|
||||||
|
### 3.2 Severites
|
||||||
|
|
||||||
|
| Niveau | Definition | Reponse attendue |
|
||||||
|
|--------|-----------|------------------|
|
||||||
|
| **CRITICAL** | Service down / data loss en cours | < 15 min |
|
||||||
|
| **WARNING** | Degradation perf / capacite proche limit | < 4h ouvrees |
|
||||||
|
| **INFO** | Audit, releases, backups OK | revue hebdo |
|
||||||
|
|
||||||
|
### 3.3 Alertes initiales (Phase 1)
|
||||||
|
|
||||||
|
```
|
||||||
|
[CRITICAL] HTTP 5xx > 5% en 5 min → page Corentin
|
||||||
|
[CRITICAL] Service down (uptime check fail 3x) → page Corentin + Yan
|
||||||
|
[CRITICAL] Disque > 95% → page
|
||||||
|
[WARNING] CPU > 80% sustained 10 min → email
|
||||||
|
[WARNING] Memoire > 85% → email
|
||||||
|
[WARNING] Capacite formateur depassee → email admin pedagogique
|
||||||
|
[INFO] Backup quotidien execute (succes/fail) → log + email si fail
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Backups — strategie 3-2-1
|
||||||
|
|
||||||
|
**3** copies des donnees, sur **2** supports differents, dont **1** offsite.
|
||||||
|
|
||||||
|
### 4.1 Targets backup
|
||||||
|
|
||||||
|
| Quoi | Frequence | Outil | Local | Distant |
|
||||||
|
|------|-----------|-------|-------|---------|
|
||||||
|
| Postgres docmost | Quotidien 03:00 | `pg_dump.gz` | `/opt/formation-hub/backups/local/` | S3-compatible (OVH/Backblaze) |
|
||||||
|
| Postgres baserow embedded | Quotidien 03:00 | `pg_dump.gz` | idem | idem |
|
||||||
|
| Docmost files (uploads) | Quotidien 03:00 | `tar.gz` | idem | idem |
|
||||||
|
| Baserow data dir | Quotidien 03:00 | `tar.gz` | idem | idem |
|
||||||
|
| `.env.prod` (encrypted) | Sur changement | gpg + push to vault | (none) | Vault hors bande |
|
||||||
|
|
||||||
|
### 4.2 Retention
|
||||||
|
|
||||||
|
| Type | Local | Distant |
|
||||||
|
|------|-------|---------|
|
||||||
|
| Quotidien | 30 jours rolling | 90 jours rolling |
|
||||||
|
| Hebdo (vendredi) | 12 semaines | 12 mois |
|
||||||
|
| Mensuel (1er) | 12 mois | 5 ans |
|
||||||
|
|
||||||
|
### 4.3 Scripts backup
|
||||||
|
|
||||||
|
`scripts/backup.sh` :
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
DATE=$(date +%Y%m%d-%H%M%S)
|
||||||
|
BACKUP_DIR=/opt/formation-hub/backups/local
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
cd /opt/formation-hub
|
||||||
|
|
||||||
|
# Postgres docmost
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml exec -T docmost-db \
|
||||||
|
pg_dump -U docmost docmost | gzip > "$BACKUP_DIR/docmost-db-$DATE.sql.gz"
|
||||||
|
|
||||||
|
# Postgres baserow (embedded — exec dans le container baserow)
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml exec -T baserow \
|
||||||
|
pg_dumpall -U postgres | gzip > "$BACKUP_DIR/baserow-db-$DATE.sql.gz"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml exec -T docmost \
|
||||||
|
tar czf - /app/data/storage > "$BACKUP_DIR/docmost-files-$DATE.tar.gz"
|
||||||
|
|
||||||
|
docker compose -f compose.yml -f compose.prod.yml exec -T baserow \
|
||||||
|
tar czf - /baserow/data > "$BACKUP_DIR/baserow-data-$DATE.tar.gz"
|
||||||
|
|
||||||
|
# Sync distant via rclone (configure separement)
|
||||||
|
rclone copy "$BACKUP_DIR/" s3:acadenice-formation-hub-backup/ --include "*-$DATE.*"
|
||||||
|
|
||||||
|
# Retention locale (supprime > 30 jours)
|
||||||
|
find "$BACKUP_DIR" -type f -mtime +30 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
`/etc/cron.d/formation-hub-backup` :
|
||||||
|
```
|
||||||
|
0 3 * * * corentin /opt/formation-hub/scripts/backup.sh >> /var/log/formation-hub-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Test restauration mensuel
|
||||||
|
|
||||||
|
`scripts/restore-test.sh` execute le 1er du mois sur env isole :
|
||||||
|
1. Provisionne un VPS test ephemere
|
||||||
|
2. Restore le backup le plus recent
|
||||||
|
3. Lance smoke tests
|
||||||
|
4. Verifie integrite (checksum, nb rows)
|
||||||
|
5. Si fail : alerte CRITICAL + log
|
||||||
|
6. Detruit le VPS test
|
||||||
|
|
||||||
|
## 5. Disaster recovery
|
||||||
|
|
||||||
|
### 5.1 Scenarios DR
|
||||||
|
|
||||||
|
| Scenario | Probabilite | Impact | Plan |
|
||||||
|
|----------|-------------|--------|------|
|
||||||
|
| VPS down (provider issue) | Faible | Service down 0-4h | Attendre provider OU failover manuel vers VPS backup |
|
||||||
|
| Corruption Postgres | Faible | Data loss < 24h | Restore depuis backup quotidien |
|
||||||
|
| Compromission complete (rootkit) | Tres faible | Vol de donnees | Wipe + reinstall + restore data + audit complet + RGPD declaration |
|
||||||
|
| Provider abandonne service | Tres faible | Service migre | Migration vers autre provider, jusqu'a 1 semaine downtime acceptable |
|
||||||
|
| Erreur humaine (rm -rf) | Moyenne | Variable | Backup quotidien + soft delete in DB |
|
||||||
|
|
||||||
|
### 5.2 RTO / RPO targets (rappel CDC)
|
||||||
|
|
||||||
|
- **RTO** (Recovery Time Objective) : 4h max
|
||||||
|
- **RPO** (Recovery Point Objective) : 24h max (backup quotidien)
|
||||||
|
|
||||||
|
### 5.3 Plan de DR — etape par etape
|
||||||
|
|
||||||
|
```
|
||||||
|
1. DETECT
|
||||||
|
- Alerte automatique OU report utilisateur
|
||||||
|
- Confirmer le scope (qui est down ? quoi est perdu ?)
|
||||||
|
|
||||||
|
2. TRIAGE (15 min)
|
||||||
|
- Severite (CRITICAL / WARNING)
|
||||||
|
- Notifier Yan + Ludo si CRITICAL
|
||||||
|
- Annoncer canal #ops + banner status si user-facing
|
||||||
|
|
||||||
|
3. MITIGATE (selon scenario)
|
||||||
|
- Restore backup
|
||||||
|
- Failover
|
||||||
|
- Hotfix
|
||||||
|
- Rollback
|
||||||
|
|
||||||
|
4. RESTORE
|
||||||
|
- Verifier integrite donnees (rollups, FK, nb rows)
|
||||||
|
- Smoke tests
|
||||||
|
- Notification "back online"
|
||||||
|
|
||||||
|
5. POST-MORTEM (sous 7 jours)
|
||||||
|
- Timeline
|
||||||
|
- Root cause
|
||||||
|
- Action items
|
||||||
|
- Ajouter au runbook si pattern recurrent
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Runbooks
|
||||||
|
|
||||||
|
Documentation par incident type. Format standardise :
|
||||||
|
|
||||||
|
```
|
||||||
|
# Runbook : <INCIDENT_TYPE>
|
||||||
|
|
||||||
|
## Symptomes
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## Diagnostic
|
||||||
|
1. Verifier ...
|
||||||
|
2. Verifier ...
|
||||||
|
|
||||||
|
## Resolution
|
||||||
|
1. Step
|
||||||
|
2. Step
|
||||||
|
|
||||||
|
## Prevention future
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## Rollback / escalade
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.1 Runbooks Phase 1 (a creer)
|
||||||
|
|
||||||
|
| Runbook | Priorite |
|
||||||
|
|---------|----------|
|
||||||
|
| `runbook-docmost-down.md` | Haute |
|
||||||
|
| `runbook-baserow-down.md` | Haute |
|
||||||
|
| `runbook-disk-full.md` | Haute |
|
||||||
|
| `runbook-postgres-corrupted.md` | Haute |
|
||||||
|
| `runbook-restore-from-backup.md` | Haute |
|
||||||
|
| `runbook-rotate-secrets.md` | Moyenne |
|
||||||
|
| `runbook-bump-docmost-version.md` | Moyenne |
|
||||||
|
| `runbook-bump-baserow-version.md` | Moyenne |
|
||||||
|
| `runbook-add-new-user.md` | Faible |
|
||||||
|
| `runbook-renewal-tls.md` | Faible (auto via Traefik) |
|
||||||
|
|
||||||
|
A stocker dans `docs/runbooks/` ou directement sur Outline pour acces rapide en incident.
|
||||||
|
|
||||||
|
## 7. Maintenance
|
||||||
|
|
||||||
|
### 7.1 Bumps dependances
|
||||||
|
|
||||||
|
| Type | Frequence | Process |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| Auto via Dependabot (security) | Hebdo | Auto-PR + CI + merge si vert |
|
||||||
|
| Auto via Dependabot (minor/patch) | Hebdo | Auto-PR + review humaine |
|
||||||
|
| Major bumps | Manuel | PR dediee + tests E2E + decision business |
|
||||||
|
| Docmost upstream | Decision manuelle (testing staging) | PR change image tag + test E2E |
|
||||||
|
| Baserow upstream | idem | idem |
|
||||||
|
| Postgres major | Annuel max, planifie | Backup + migration + restore + verification |
|
||||||
|
|
||||||
|
### 7.2 OS patches
|
||||||
|
|
||||||
|
| Type | Frequence |
|
||||||
|
|------|-----------|
|
||||||
|
| Security patches Debian | Auto via `unattended-upgrades` |
|
||||||
|
| Major Debian release | Tous les 2-3 ans, planifie |
|
||||||
|
| Reboot apres kernel patch | Mensuel max, fenetre maintenance |
|
||||||
|
|
||||||
|
### 7.3 Window de maintenance
|
||||||
|
|
||||||
|
Communiquer 48h avant si downtime > 5 min :
|
||||||
|
- Email a tous les utilisateurs Acadenice
|
||||||
|
- Banner Docmost / Baserow
|
||||||
|
- Slack #internal
|
||||||
|
|
||||||
|
Creneau prefere : **dimanche 06:00-08:00 UTC** (zero usage probable).
|
||||||
|
|
||||||
|
## 8. Capacity planning
|
||||||
|
|
||||||
|
### 8.1 Indicateurs a surveiller
|
||||||
|
|
||||||
|
- Nb users actifs (mensuel)
|
||||||
|
- Volume rows Baserow (par table)
|
||||||
|
- Volume documents Docmost
|
||||||
|
- Storage uploads
|
||||||
|
- CPU/RAM moyenne sur 7 jours
|
||||||
|
|
||||||
|
### 8.2 Triggers d'upsizing
|
||||||
|
|
||||||
|
| Indicateur | Seuil | Action |
|
||||||
|
|-----------|-------|--------|
|
||||||
|
| CPU moyen > 60% sur 1 semaine | Trigger | Upsize VPS (4 → 8 vCPU) |
|
||||||
|
| RAM moyen > 75% sur 1 semaine | Trigger | Upsize RAM (8 → 16 Go) |
|
||||||
|
| Disque > 70% | Trigger | Upsize storage OU clean old backups |
|
||||||
|
| Nb users simultanes peak > 50 | Trigger | Considerer 2 replicas + load balancer |
|
||||||
|
|
||||||
|
### 8.3 Review trimestrielle
|
||||||
|
|
||||||
|
Tous les 3 mois, Corentin review :
|
||||||
|
- Couts infra
|
||||||
|
- Adequation specs
|
||||||
|
- Croissance attendue prochain trimestre
|
||||||
|
- Decision upsize/downsize/migrate
|
||||||
|
|
||||||
|
## 9. Incident response
|
||||||
|
|
||||||
|
### 9.1 Severites (rappel)
|
||||||
|
|
||||||
|
- **SEV1** : Service down complet (CRITICAL)
|
||||||
|
- **SEV2** : Degradation majeure (WARNING)
|
||||||
|
- **SEV3** : Bug isole, workaround possible (INFO)
|
||||||
|
|
||||||
|
### 9.2 Comm template
|
||||||
|
|
||||||
|
Pendant incident :
|
||||||
|
```
|
||||||
|
[SEV1] formation-hub - Service degraded
|
||||||
|
Symptom: <quoi>
|
||||||
|
Started: <quand>
|
||||||
|
Investigation: <where we are>
|
||||||
|
ETA: <estimate restore>
|
||||||
|
Channel: #ops
|
||||||
|
```
|
||||||
|
|
||||||
|
Mise a jour toutes les 30 min.
|
||||||
|
|
||||||
|
### 9.3 Post-mortem template
|
||||||
|
|
||||||
|
`docs/post-mortems/YYYY-MM-DD-titre.md` :
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Post-mortem : <titre incident>
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
- HH:MM detection
|
||||||
|
- HH:MM triage
|
||||||
|
- HH:MM mitigation start
|
||||||
|
- HH:MM service restored
|
||||||
|
- HH:MM root cause confirmed
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Duree downtime : Xh
|
||||||
|
- Users impactes : Y
|
||||||
|
- Data loss : oui/non, si oui : combien
|
||||||
|
|
||||||
|
## Root cause
|
||||||
|
<...>
|
||||||
|
|
||||||
|
## Pourquoi notre monitoring n'a pas alerte plus tot ?
|
||||||
|
<...>
|
||||||
|
|
||||||
|
## Action items
|
||||||
|
- [ ] AI 1 : ... (owner @who, due date)
|
||||||
|
- [ ] AI 2 : ...
|
||||||
|
|
||||||
|
## Lessons learned
|
||||||
|
<...>
|
||||||
|
```
|
||||||
|
|
||||||
|
Post-mortem **blameless** : focus sur le systeme, pas la personne.
|
||||||
|
|
||||||
|
## 10. Daily / Weekly / Monthly tasks
|
||||||
|
|
||||||
|
### 10.1 Daily (5 min, matin)
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] Check uptime monitoring (vert ?)
|
||||||
|
[ ] Verifier logs containers (pas d'erreur recurrente ?)
|
||||||
|
[ ] Verifier backup quotidien execute (status email ou log)
|
||||||
|
[ ] Check Slack #ops (rien d'urgent ?)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Weekly (30 min, lundi matin)
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] Review Dependabot PRs
|
||||||
|
[ ] Check disque/CPU graphs (anomalies ?)
|
||||||
|
[ ] Review issues GitHub ops/sec
|
||||||
|
[ ] Update CHANGELOG si releases passees
|
||||||
|
[ ] Plan release prochaine si features pretes
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 Monthly (2h, 1er du mois)
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] Test restauration backup (DR exercice)
|
||||||
|
[ ] Audit access list (qui a acces a quoi ?)
|
||||||
|
[ ] Review security alerts (CVE, audits)
|
||||||
|
[ ] Capacity planning review
|
||||||
|
[ ] Review couts infra (vs budget)
|
||||||
|
[ ] Update runbooks si nouveaux patterns
|
||||||
|
[ ] Review monitoring : alertes sur-bruyantes ? sous-detectes ?
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. On-call rotation (futur)
|
||||||
|
|
||||||
|
Pour l'instant : **Corentin = oncall principal**, Yan = backup.
|
||||||
|
|
||||||
|
Si plus d'admin technique embauches plus tard :
|
||||||
|
- Rotation hebdo Corentin / Yan / N
|
||||||
|
- Handoff weekly avec recap
|
||||||
|
- Compensation oncall (jour off ou prime)
|
||||||
|
|
||||||
|
## 12. Communication metier
|
||||||
|
|
||||||
|
Channels :
|
||||||
|
- **#ops** Slack/Teams : equipe technique
|
||||||
|
- **#internal** : tous les salaries Acadenice
|
||||||
|
- **Email all** : announcements majeurs (releases breaking, maintenance)
|
||||||
|
- **Banner Docmost** : info live downtime / maintenance
|
||||||
|
|
||||||
|
## 13. Documentation des operations
|
||||||
|
|
||||||
|
Tout doit etre dans `docs/runbooks/` (ou Outline `[INTERNE] Runbooks`) :
|
||||||
|
- Comment faire un backup manuel
|
||||||
|
- Comment restorer
|
||||||
|
- Comment ajouter un user
|
||||||
|
- Comment rotate les secrets
|
||||||
|
- Comment bump une version Docmost ou Baserow
|
||||||
|
- Comment investiguer un alert
|
||||||
|
- Comment escalader un incident
|
||||||
|
|
||||||
|
## 14. Outils ops — recap
|
||||||
|
|
||||||
|
| Outil | Phase | Cout/mois |
|
||||||
|
|-------|-------|-----------|
|
||||||
|
| UptimeRobot free | Phase 1+ | 0€ |
|
||||||
|
| Uptime Kuma self-host | Phase 2+ | 0€ |
|
||||||
|
| Prometheus + Grafana | Phase 3+ | ~5€ resources |
|
||||||
|
| Loki | Phase 3+ | ~5€ resources |
|
||||||
|
| Sentry | Phase 4+ | 0-25€ |
|
||||||
|
| pg_dump + tar + rclone | Phase 1+ | 0€ |
|
||||||
|
| OVH Object Storage / Backblaze | Phase 1+ | ~5-10€ |
|
||||||
|
| Slack / Teams webhook | Phase 1+ | 0€ (existant) |
|
||||||
|
|
||||||
|
## 15. Questions ouvertes
|
||||||
|
|
||||||
|
- [ ] Self-host Uptime Kuma vs SaaS UptimeRobot pour Phase 1 ?
|
||||||
|
- [ ] Backup distant : OVH (souverainete FR) vs Backblaze (cout) ?
|
||||||
|
- [ ] On-call rotation et compensation a definir si embauche
|
||||||
|
- [ ] Runbook execution automatique (Rundeck ?) ou pure markdown ?
|
||||||
|
- [ ] Status page publique (Statuspage.io / self-host) pour transparence vers users ?
|
||||||
536
docs/19-bridge-api-design.md
Normal file
536
docs/19-bridge-api-design.md
Normal file
|
|
@ -0,0 +1,536 @@
|
||||||
|
# Bridge API Design
|
||||||
|
|
||||||
|
> Specification du **bridge service** : architecture, endpoints, auth, contrats, integration patterns.
|
||||||
|
> Service Node TS qui expose Baserow comme nodes Tiptap custom dans Docmost et orchestre les rollups cross-zone.
|
||||||
|
> Statut : design doc avant code Phase 2.
|
||||||
|
|
||||||
|
## 1. Mission du bridge
|
||||||
|
|
||||||
|
Le bridge est notre **seul code custom**. Il a 4 missions :
|
||||||
|
|
||||||
|
1. **Exposer les rows Baserow comme objets typed** au reste de l'ecosysteme (typing strict, validation, cache)
|
||||||
|
2. **Recevoir les webhooks Baserow** pour invalider caches et declencher actions (notifications, recalculs cross-zone)
|
||||||
|
3. **Servir les Tiptap node-views custom** dans Docmost (mention `@formateur`, embed `[projet]`, etc.) avec donnees fraiches
|
||||||
|
4. **Orchestrer les workflows metier** que ni Docmost ni Baserow ne savent faire seuls (validation RG, notifications croisees, capacite Personne unifiee)
|
||||||
|
|
||||||
|
Le bridge **ne stocke pas d'etat metier** (Phase 2). Source of truth = Baserow. Le bridge est stateless avec cache Redis.
|
||||||
|
|
||||||
|
## 2. Tech stack
|
||||||
|
|
||||||
|
| Composant | Choix | Justification |
|
||||||
|
|-----------|-------|---------------|
|
||||||
|
| Runtime | Node 22 LTS | Stable, ecosysteme TS mature |
|
||||||
|
| Framework HTTP | Hono | Leger, performant, TypeScript-first, edge-ready si futur |
|
||||||
|
| Validation | Zod | Schemas TS-typed, runtime validation |
|
||||||
|
| HTTP client | ofetch | Wrapper fetch avec retry, timeout, JSON typing |
|
||||||
|
| Cache | Redis 7 | Partage avec Docmost ou dedie (decision Phase 2) |
|
||||||
|
| Tests | Vitest + testcontainers | Cf doc 16 |
|
||||||
|
| Logger | Pino | Structured JSON logs, perf |
|
||||||
|
| Config | dotenv + zod | `.env` parse + valide au boot |
|
||||||
|
| Build | TypeScript native + bundling esbuild | Pas Webpack, simple |
|
||||||
|
| Deploy | Docker image multi-stage | Image < 100 Mo |
|
||||||
|
|
||||||
|
## 3. Architecture interne
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph "Bridge service (Hono)"
|
||||||
|
Routes[Routes layer<br/>endpoints REST + webhooks]
|
||||||
|
Middleware[Middleware<br/>auth, logging, rate-limit, error]
|
||||||
|
Services[Services layer<br/>PersonneService, ProjetService, etc.]
|
||||||
|
Adapters[Adapters layer<br/>BaserowClient, DocmostClient, RedisCache]
|
||||||
|
Domain[Domain layer<br/>Personne, Module, Tache classes pures]
|
||||||
|
end
|
||||||
|
|
||||||
|
Routes --> Middleware
|
||||||
|
Middleware --> Services
|
||||||
|
Services --> Domain
|
||||||
|
Services --> Adapters
|
||||||
|
|
||||||
|
Adapters -->|HTTP| Baserow[(Baserow API)]
|
||||||
|
Adapters -->|HTTP| Docmost[(Docmost API)]
|
||||||
|
Adapters -->|TCP| Redis[(Redis)]
|
||||||
|
```
|
||||||
|
|
||||||
|
Layers :
|
||||||
|
- **Routes** : declaration endpoints + Zod schemas + delegation services
|
||||||
|
- **Middleware** : transverse (auth, logs, rate-limiting, error handling)
|
||||||
|
- **Services** : logique metier orchestree (Use Cases level)
|
||||||
|
- **Domain** : classes pures (Personne, Module, Attribution... cf doc 12)
|
||||||
|
- **Adapters** : isolation IO (Baserow API, Docmost API, Redis)
|
||||||
|
|
||||||
|
## 4. Conventions API REST
|
||||||
|
|
||||||
|
### 4.1 Style
|
||||||
|
|
||||||
|
- **REST-ish** : endpoints orientes resources, verbes HTTP standards (GET, POST, PUT, PATCH, DELETE)
|
||||||
|
- **JSON** uniquement (request + response)
|
||||||
|
- **Response shape standard** :
|
||||||
|
```typescript
|
||||||
|
// Success
|
||||||
|
{ "data": <payload>, "meta"?: { ... } }
|
||||||
|
|
||||||
|
// Error
|
||||||
|
{ "error": { "code": "ERR_CODE", "message": "Human readable", "details"?: {...} } }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Naming
|
||||||
|
|
||||||
|
- Plural noms : `/personnes`, `/projets`, `/attributions`
|
||||||
|
- IDs en path : `/personnes/:id`
|
||||||
|
- Sub-resources : `/projets/:id/taches`
|
||||||
|
- Actions : verb-style en POST si non-CRUD : `/attributions/:id/cloturer`
|
||||||
|
|
||||||
|
### 4.3 Versioning
|
||||||
|
|
||||||
|
- Prefix `/api/v1/` sur toutes les routes
|
||||||
|
- Breaking change → nouvelle version `/api/v2/` (en parallele pendant transition)
|
||||||
|
- Deprecations annoncees minimum 3 mois avant retrait
|
||||||
|
|
||||||
|
### 4.4 Pagination, filtre, tri (pour les list endpoints)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/personnes?
|
||||||
|
page=1& # default 1
|
||||||
|
per_page=50& # default 50, max 200
|
||||||
|
sort=nom& # default id desc
|
||||||
|
filter[role]=formateur&
|
||||||
|
filter[statut]=actif
|
||||||
|
```
|
||||||
|
|
||||||
|
Response :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [...],
|
||||||
|
"meta": {
|
||||||
|
"page": 1,
|
||||||
|
"per_page": 50,
|
||||||
|
"total": 127,
|
||||||
|
"total_pages": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Authentification
|
||||||
|
|
||||||
|
### 5.1 Strategies
|
||||||
|
|
||||||
|
| Type | Usage | Header |
|
||||||
|
|------|-------|--------|
|
||||||
|
| **API Token longue duree** | Service-to-service (Docmost ↔ Bridge, Cron ↔ Bridge) | `Authorization: Bearer brg_<token>` |
|
||||||
|
| **JWT court** (Phase 3+) | User authentifie via Docmost SSO | `Authorization: Bearer <jwt>` |
|
||||||
|
| **Webhook signature** | Verification webhook Baserow | `X-Baserow-Signature: <hmac-sha256>` |
|
||||||
|
|
||||||
|
### 5.2 Generation tokens
|
||||||
|
|
||||||
|
API tokens generes via CLI bridge :
|
||||||
|
```bash
|
||||||
|
npm run --prefix bridge token:create -- --name "docmost-prod" --scopes "read:* write:attributions"
|
||||||
|
# → "brg_a1b2c3d4..." stocke en .env.prod cote Docmost
|
||||||
|
```
|
||||||
|
|
||||||
|
Tokens stockes en clair dans une table `api_tokens` Postgres (Phase 3+) ou en memoire au boot via `.env` (Phase 2 simple).
|
||||||
|
|
||||||
|
### 5.3 Scopes
|
||||||
|
|
||||||
|
| Scope | Permissions |
|
||||||
|
|-------|-------------|
|
||||||
|
| `read:personnes` | GET /personnes/* |
|
||||||
|
| `read:projets` | GET /projets/* |
|
||||||
|
| `write:attributions` | POST/PATCH /attributions |
|
||||||
|
| `write:interventions` | POST/PATCH /interventions |
|
||||||
|
| `webhook:baserow` | POST /webhooks/baserow/* |
|
||||||
|
| `admin:*` | Tout (Corentin/Yan tokens) |
|
||||||
|
|
||||||
|
## 6. Endpoints REST
|
||||||
|
|
||||||
|
### 6.1 Personnes
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| GET | `/api/v1/personnes` | `read:personnes` | Liste paginee, filtrable par role/statut |
|
||||||
|
| GET | `/api/v1/personnes/:id` | `read:personnes` | Fiche detail avec heures restantes (formation + agence + total) |
|
||||||
|
| GET | `/api/v1/personnes/:id/attributions` | `read:personnes` | Attributions actives + historiques |
|
||||||
|
| GET | `/api/v1/personnes/:id/interventions` | `read:personnes` | Interventions sur taches (paginees) |
|
||||||
|
| GET | `/api/v1/personnes/:id/dashboard` | `read:personnes` | Vue 360 : capacite, attributions, interventions, projets en cours |
|
||||||
|
|
||||||
|
### 6.2 Formations / Blocs / Modules
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| GET | `/api/v1/formations` | `read:formations` | Liste paginee |
|
||||||
|
| GET | `/api/v1/formations/:id` | `read:formations` | Detail avec blocs/modules + rollups |
|
||||||
|
| GET | `/api/v1/blocs/:id` | `read:formations` | Detail bloc + modules |
|
||||||
|
| GET | `/api/v1/modules/:id` | `read:formations` | Detail module + attributions actives |
|
||||||
|
| POST | `/api/v1/modules/:id/attribuer` | `write:attributions` | Cree une attribution avec validation RG |
|
||||||
|
|
||||||
|
### 6.3 Attributions
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| GET | `/api/v1/attributions/:id` | `read:attributions` | Detail |
|
||||||
|
| PATCH | `/api/v1/attributions/:id/heures-realisees` | `write:attributions` | Saisir heures realisees (UC-13) |
|
||||||
|
| POST | `/api/v1/attributions/:id/cloturer` | `write:attributions` | Statut → realise |
|
||||||
|
| POST | `/api/v1/attributions/:id/annuler` | `write:attributions` | Statut → annule (justification requise) |
|
||||||
|
|
||||||
|
### 6.4 Clients / Projets / Taches
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| GET | `/api/v1/clients` | `read:projets` | Liste |
|
||||||
|
| GET | `/api/v1/clients/:id` | `read:projets` | Detail + projets |
|
||||||
|
| GET | `/api/v1/projets` | `read:projets` | Liste filtrable par statut/client |
|
||||||
|
| GET | `/api/v1/projets/:id` | `read:projets` | Detail + taches + heures realisees rollup |
|
||||||
|
| GET | `/api/v1/projets/:id/timeline` | `read:projets` | Vue chronologique interventions |
|
||||||
|
| GET | `/api/v1/taches/:id` | `read:projets` | Detail + interventions |
|
||||||
|
|
||||||
|
### 6.5 Interventions
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| POST | `/api/v1/interventions` | `write:interventions` | Saisir intervention (UCA-07) |
|
||||||
|
| PATCH | `/api/v1/interventions/:id` | `write:interventions` | Edit (heures, notes) |
|
||||||
|
| POST | `/api/v1/interventions/:id/annuler` | `write:interventions` | Annulation |
|
||||||
|
|
||||||
|
### 6.6 Rapports
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| GET | `/api/v1/rapports/formation/:id?format=pdf` | `read:formations` | PDF rapport formation |
|
||||||
|
| GET | `/api/v1/rapports/personne/:id?format=pdf` | `read:personnes` | PDF rapport personne (heures + attributions) |
|
||||||
|
| GET | `/api/v1/rapports/projet/:id?format=pdf` | `read:projets` | PDF rapport projet |
|
||||||
|
|
||||||
|
### 6.7 Health & metrics
|
||||||
|
|
||||||
|
| Method | Path | Scope | Description |
|
||||||
|
|--------|------|-------|-------------|
|
||||||
|
| GET | `/api/health` | (none) | Healthcheck (200 si OK, 503 si degraded) |
|
||||||
|
| GET | `/api/ready` | (none) | Readiness (Baserow + Redis joignables) |
|
||||||
|
| GET | `/api/metrics` | `admin:*` | Prometheus metrics format |
|
||||||
|
|
||||||
|
## 7. Webhooks Baserow
|
||||||
|
|
||||||
|
Baserow envoie des webhooks sur les changements de rows. Bridge traite et reagit.
|
||||||
|
|
||||||
|
### 7.1 Endpoints webhook
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| POST | `/api/webhooks/baserow/attribution-changed` | Row created/updated/deleted sur table `attribution` |
|
||||||
|
| POST | `/api/webhooks/baserow/intervention-changed` | Idem `intervention` |
|
||||||
|
| POST | `/api/webhooks/baserow/module-status-changed` | Quand `module_statut` change |
|
||||||
|
| POST | `/api/webhooks/baserow/projet-status-changed` | Quand `projet_statut` change |
|
||||||
|
|
||||||
|
### 7.2 Format payload Baserow (extrait)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"table_id": 123,
|
||||||
|
"database_id": 1,
|
||||||
|
"event_type": "rows.created",
|
||||||
|
"items": [
|
||||||
|
{ "id": 42, "field_X": "...", ... }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Verification signature
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// middleware/webhook-baserow.ts
|
||||||
|
const expected = hmacSha256(rawBody, env.BASEROW_WEBHOOK_SECRET);
|
||||||
|
const provided = req.headers['X-Baserow-Signature'];
|
||||||
|
if (!constantTimeEqual(expected, provided)) {
|
||||||
|
throw new Error('Invalid signature');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Actions par webhook
|
||||||
|
|
||||||
|
| Webhook | Actions |
|
||||||
|
|---------|---------|
|
||||||
|
| attribution-changed | Invalide cache Redis personne/module concernes ; si statut change vers `realise` ou `annule` → recalcul rollup module ; notif email formateur si nouvelle attribution |
|
||||||
|
| intervention-changed | Invalide cache personne/tache ; check capacite Personne, alerte si depassement |
|
||||||
|
| module-status-changed | Si tous modules d'une formation `realise` → declenche cloture formation auto (OP-07) |
|
||||||
|
| projet-status-changed | Si `livre` → notif admin pour facturation |
|
||||||
|
|
||||||
|
### 7.5 Idempotence
|
||||||
|
|
||||||
|
Chaque webhook a un `event_id`. Le bridge stocke en Redis avec TTL 24h les events deja traites :
|
||||||
|
```typescript
|
||||||
|
const seen = await redis.get(`webhook:event:${event_id}`);
|
||||||
|
if (seen) return; // skip duplicate
|
||||||
|
await redis.set(`webhook:event:${event_id}`, '1', 'EX', 86400);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Cache strategy
|
||||||
|
|
||||||
|
### 8.1 Cles Redis
|
||||||
|
|
||||||
|
| Cle | TTL | Contenu |
|
||||||
|
|-----|-----|---------|
|
||||||
|
| `bridge:personne:<id>` | 5 min | JSON full Personne (avec rollups calcules) |
|
||||||
|
| `bridge:projet:<id>` | 5 min | JSON full Projet |
|
||||||
|
| `bridge:formation:<id>` | 10 min | JSON Formation (change moins souvent) |
|
||||||
|
| `bridge:webhook:event:<event_id>` | 24h | Idempotence webhook |
|
||||||
|
| `bridge:rate-limit:<token>:<endpoint>` | 1 min | Rate limit counter |
|
||||||
|
|
||||||
|
### 8.2 Invalidation
|
||||||
|
|
||||||
|
- **Webhook Baserow** invalide les cles concernees
|
||||||
|
- **TTL** comme fallback (5-10 min)
|
||||||
|
- Pattern : `cache.invalidate('bridge:personne:42')` apres write
|
||||||
|
|
||||||
|
### 8.3 Cache aside pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async getPersonne(id: number): Promise<Personne> {
|
||||||
|
const cached = await cache.get(`bridge:personne:${id}`);
|
||||||
|
if (cached) return Personne.fromJSON(cached);
|
||||||
|
|
||||||
|
const fresh = await baserow.fetchPersonne(id);
|
||||||
|
await cache.set(`bridge:personne:${id}`, fresh.toJSON(), 'EX', 300);
|
||||||
|
return fresh;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Rate limiting
|
||||||
|
|
||||||
|
Par token + endpoint (sliding window 1 min) :
|
||||||
|
|
||||||
|
| Endpoint | Limite |
|
||||||
|
|----------|--------|
|
||||||
|
| Read endpoints | 600 req/min |
|
||||||
|
| Write endpoints | 60 req/min |
|
||||||
|
| Webhooks | 1000 req/min |
|
||||||
|
| Rapports PDF | 10 req/min |
|
||||||
|
|
||||||
|
Reponse 429 si depasse :
|
||||||
|
```json
|
||||||
|
{ "error": { "code": "RATE_LIMITED", "message": "Too many requests", "retry_after": 30 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Error handling
|
||||||
|
|
||||||
|
### 10.1 Codes d'erreur
|
||||||
|
|
||||||
|
| Code | HTTP | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `AUTH_REQUIRED` | 401 | Token absent |
|
||||||
|
| `AUTH_INVALID` | 401 | Token invalide |
|
||||||
|
| `FORBIDDEN_SCOPE` | 403 | Token n'a pas le scope requis |
|
||||||
|
| `NOT_FOUND` | 404 | Ressource inexistante |
|
||||||
|
| `VALIDATION_ERROR` | 400 | Body invalide (Zod errors) |
|
||||||
|
| `RG_VIOLATION` | 422 | Regle de gestion violee (ex RG-01 depassement heures module) |
|
||||||
|
| `CONFLICT` | 409 | Etat incoherent (ex annuler une attribution deja annulee) |
|
||||||
|
| `RATE_LIMITED` | 429 | Trop de requetes |
|
||||||
|
| `BASEROW_UNAVAILABLE` | 502 | Baserow API down |
|
||||||
|
| `INTERNAL` | 500 | Bug bridge |
|
||||||
|
|
||||||
|
### 10.2 Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "RG_VIOLATION",
|
||||||
|
"message": "Heures attribuees depassent la capacite du module",
|
||||||
|
"details": {
|
||||||
|
"rule": "RG-01",
|
||||||
|
"module_id": 42,
|
||||||
|
"heures_module": 30,
|
||||||
|
"heures_deja_attribuees": 28,
|
||||||
|
"heures_demandees": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Integration patterns Docmost
|
||||||
|
|
||||||
|
### 11.1 Tiptap node-view custom
|
||||||
|
|
||||||
|
Phase 2+ : on developpe (ou on commande a un freelance) des extensions Tiptap pour Docmost qui appellent le bridge.
|
||||||
|
|
||||||
|
Patterns :
|
||||||
|
- **Mention** `@formateur:Pierre` → render carte avec capacite restante via GET /personnes/:id (slug → resolution)
|
||||||
|
- **Embed** `[projet:projet-alpha]` → render card avec status + heures realisees
|
||||||
|
- **Database view** `[modules-a-attribuer]` → embed kanban filtré
|
||||||
|
|
||||||
|
Ces nodes appellent le bridge via fetch + cache cote client (5 min).
|
||||||
|
|
||||||
|
### 11.2 Routes pages full
|
||||||
|
|
||||||
|
Phase 2+ : le bridge sert aussi des **pages full** /personne/:id, /projet/:id, /formation/:id qui ressemblent a des pages Docmost (header layout + content).
|
||||||
|
|
||||||
|
Implementation : Hono cote backend rend HTML avec layout Docmost mimique + content custom. Le user clique sur une mention dans Docmost, ouvre la page bridge, voit le meme look.
|
||||||
|
|
||||||
|
Ou en Phase 3 : on contribue au repo Docmost upstream pour ajouter ces nodes nativement.
|
||||||
|
|
||||||
|
## 12. Sample request/response
|
||||||
|
|
||||||
|
### Saisir heures realisees
|
||||||
|
|
||||||
|
```http
|
||||||
|
PATCH /api/v1/attributions/42/heures-realisees HTTP/1.1
|
||||||
|
Host: bridge.acadenice.fr
|
||||||
|
Authorization: Bearer brg_xxxxx
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"heures_realisees": 3.5,
|
||||||
|
"comment": "Cours JS du 2026-05-07 OK"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response 200 :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"attribution_id": 42,
|
||||||
|
"heures_attribuees": 10,
|
||||||
|
"heures_realisees": 3.5,
|
||||||
|
"statut": "en_cours",
|
||||||
|
"module": {
|
||||||
|
"module_id": 17,
|
||||||
|
"module_nom": "JS Fondamentaux",
|
||||||
|
"heures_realisees_total": 3.5
|
||||||
|
},
|
||||||
|
"personne": {
|
||||||
|
"personne_id": 5,
|
||||||
|
"nom_prenom": "Pierre Dupont",
|
||||||
|
"heures_attribuees_formation": 80,
|
||||||
|
"heures_restantes_formation": 670
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur RG violation
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/modules/17/attribuer HTTP/1.1
|
||||||
|
Authorization: Bearer brg_xxxxx
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"personne_id": 5,
|
||||||
|
"heures_attribuees": 50,
|
||||||
|
"date_debut": "2026-09-01"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response 422 :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "RG_VIOLATION",
|
||||||
|
"message": "Heures attribuees depassent la capacite du module",
|
||||||
|
"details": {
|
||||||
|
"rule": "RG-01",
|
||||||
|
"module_id": 17,
|
||||||
|
"heures_module": 30,
|
||||||
|
"heures_deja_attribuees": 0,
|
||||||
|
"heures_demandees": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Observabilite
|
||||||
|
|
||||||
|
### 13.1 Logs (Pino structured JSON)
|
||||||
|
|
||||||
|
Niveau `info` par defaut, `debug` en local. Format :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"time": "2026-05-07T10:23:45.123Z",
|
||||||
|
"msg": "PATCH /api/v1/attributions/42/heures-realisees",
|
||||||
|
"method": "PATCH",
|
||||||
|
"path": "/api/v1/attributions/42/heures-realisees",
|
||||||
|
"status": 200,
|
||||||
|
"duration_ms": 142,
|
||||||
|
"user_token_id": "tok_abc123",
|
||||||
|
"request_id": "req_xyz789"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Champs sensibles redactes : pas de body en logs, pas de token en clair.
|
||||||
|
|
||||||
|
### 13.2 Metrics Prometheus
|
||||||
|
|
||||||
|
Exposees sur `/api/metrics` :
|
||||||
|
- `http_requests_total{method,path,status}` counter
|
||||||
|
- `http_request_duration_seconds{method,path}` histogram
|
||||||
|
- `baserow_api_calls_total{endpoint,status}` counter
|
||||||
|
- `cache_hits_total` / `cache_misses_total`
|
||||||
|
- `webhook_events_processed_total{type,outcome}`
|
||||||
|
|
||||||
|
## 14. Tests
|
||||||
|
|
||||||
|
Cf doc 16 plan-de-tests :
|
||||||
|
- Unit Vitest 80% coverage minimum sur domain
|
||||||
|
- Integration tests avec testcontainers Baserow + Redis
|
||||||
|
- E2E playwright sur staging
|
||||||
|
|
||||||
|
## 15. Roadmap implementation
|
||||||
|
|
||||||
|
### Phase 2.0 — Bootstrap (semaine 1-2)
|
||||||
|
|
||||||
|
- [ ] Setup Hono + zod + ofetch + pino
|
||||||
|
- [ ] BaserowClient avec tests integration
|
||||||
|
- [ ] DocmostClient skeleton
|
||||||
|
- [ ] Healthcheck endpoint
|
||||||
|
- [ ] Auth middleware basique (API token)
|
||||||
|
- [ ] CI/CD complet (cf doc 17)
|
||||||
|
- [ ] Deploy staging
|
||||||
|
|
||||||
|
### Phase 2.1 — Read endpoints (semaine 3-4)
|
||||||
|
|
||||||
|
- [ ] GET /personnes/:id avec rollups calcules
|
||||||
|
- [ ] GET /projets/:id
|
||||||
|
- [ ] GET /formations/:id
|
||||||
|
- [ ] Cache Redis pattern cache-aside
|
||||||
|
- [ ] Tests integration sur les endpoints
|
||||||
|
|
||||||
|
### Phase 2.2 — Write endpoints + webhooks (semaine 5-7)
|
||||||
|
|
||||||
|
- [ ] POST /interventions
|
||||||
|
- [ ] PATCH /attributions/:id/heures-realisees
|
||||||
|
- [ ] Webhooks Baserow handlers
|
||||||
|
- [ ] Validation RG-01 a RG-06
|
||||||
|
- [ ] Tests integration write
|
||||||
|
|
||||||
|
### Phase 2.3 — Tiptap nodes (semaine 8-10)
|
||||||
|
|
||||||
|
- [ ] Premier node Tiptap custom (mention `@formateur`)
|
||||||
|
- [ ] Integration Docmost (fork ou plugin)
|
||||||
|
- [ ] E2E playwright
|
||||||
|
|
||||||
|
### Phase 2.4 — Pages full + rapports (semaine 11-12)
|
||||||
|
|
||||||
|
- [ ] Routes /personne/:id, /projet/:id en page Docmost-style
|
||||||
|
- [ ] Endpoint /rapports/* PDF generation
|
||||||
|
- [ ] Stabilisation, fix bugs, doc utilisateur
|
||||||
|
|
||||||
|
## 16. Decisions a prendre
|
||||||
|
|
||||||
|
- [ ] **Source of truth tokens** : .env (simple) vs Postgres dedie (rotation a chaud) ? Mon vote : .env Phase 2, Postgres Phase 3
|
||||||
|
- [ ] **Cache Redis partage Docmost ou dedie** ? Partage Phase 2 (simple, sa marche), dedie Phase 3 si charge ou conflits
|
||||||
|
- [ ] **PDF generation** : Puppeteer (lourd) vs PDFKit (manuel) vs service externe (gotenberg) ? Recommande PDFKit ou gotenberg self-host
|
||||||
|
- [ ] **OpenAPI 3 doc auto** : generee depuis Zod schemas ? Lib `@asteasolutions/zod-to-openapi`. A faire Phase 2.1.
|
||||||
|
- [ ] **GraphQL au lieu de REST ?** Pas pertinent pour notre scope (peu de endpoints, peu de variation queries). REST est plus simple.
|
||||||
|
- [ ] **Multi-tenant** ? Pour l'instant non — Acadenice mono-instance. Si rachat / scaling : ajouter `tenant_id` partout. Pas avant Phase 4.
|
||||||
|
|
||||||
|
## 17. Glossaire
|
||||||
|
|
||||||
|
| Terme | Definition |
|
||||||
|
|-------|------------|
|
||||||
|
| Bridge | Service custom qui se sert d'intermediaire entre Docmost (UI) et Baserow (data) |
|
||||||
|
| Tiptap node-view | Composant React custom integre dans editeur Tiptap pour rendre un block specifique |
|
||||||
|
| Cache aside | Pattern : check cache → if miss, fetch source + populate cache |
|
||||||
|
| Idempotence | Une requete repetee a le meme effet qu'une requete unique (anti-doublon) |
|
||||||
|
| HMAC signature | Hash crypto pour verifier l'authenticite d'un payload (webhook) |
|
||||||
|
| Sliding window rate limit | Compteur sur fenetre glissante (ex: derniere minute) |
|
||||||
|
| RG | Regle de Gestion (Merise) |
|
||||||
|
| Scope (token) | Permission specifique (read:X, write:Y, admin:*) |
|
||||||
20
docs/diagrams/README.md
Normal file
20
docs/diagrams/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Diagrammes drawIO
|
||||||
|
|
||||||
|
Fichiers `.drawio` (XML). Ouvrir dans :
|
||||||
|
- [app.diagrams.net](https://app.diagrams.net) (web)
|
||||||
|
- Docmost natif (block drawIO, depuis v0.3.0)
|
||||||
|
- VS Code extension `Drawio Integration` ([Henning Dieterichs](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio))
|
||||||
|
|
||||||
|
## Liste
|
||||||
|
|
||||||
|
| Fichier | Description | Lien Outline (XML a importer) |
|
||||||
|
|---------|-------------|-------------------------------|
|
||||||
|
| `architecture-infra.drawio` | Vue archi infra complete (Traefik + Docmost + Baserow + Bridge + storage + ops) | A pousser dans Outline |
|
||||||
|
|
||||||
|
## Import dans diagrams.net
|
||||||
|
|
||||||
|
1. Aller sur https://app.diagrams.net
|
||||||
|
2. Choisir "Open Existing Diagram"
|
||||||
|
3. Selectionner le fichier `.drawio` local OU coller le XML via `Extras → Edit Diagram (XML)`
|
||||||
|
4. Polir le layout si besoin (`Layout → Vertical Tree Layout` peut aider)
|
||||||
|
5. Sauver, exporter en SVG/PNG si besoin
|
||||||
110
docs/diagrams/architecture-infra.drawio
Normal file
110
docs/diagrams/architecture-infra.drawio
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<mxfile host="app.diagrams.net" version="24.0.0">
|
||||||
|
<diagram name="Architecture formation-hub" id="archi-infra">
|
||||||
|
<mxGraphModel dx="1200" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="826" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
|
||||||
|
<mxCell id="user" value="Utilisateur final (Admin / Formateur / Dev / Etudiant / Client)" style="ellipse;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="430" y="40" width="320" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="traefik-group" value="Edge / Reverse Proxy" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#d79b00;dashed=1;verticalAlign=top;fontStyle=1;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380" y="150" width="420" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="traefik" value="Traefik TLS Let's Encrypt routing par sous-domaine" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="490" y="180" width="200" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="app-group" value="Application services" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#82b366;dashed=1;verticalAlign=top;fontStyle=1;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="280" width="1060" height="120" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="docmost" value="Docmost NestJS + React + Tiptap" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="120" y="320" width="220" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="baserow" value="Baserow Django + Caddy interne" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="480" y="320" width="220" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="bridge" value="Bridge service Node 22 + Hono (Phase 2)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;dashed=1;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="840" y="310" width="220" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="storage-group" value="Storage" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#9673a6;dashed=1;verticalAlign=top;fontStyle=1;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="450" width="1060" height="130" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="docmost-db" value="Postgres docmost" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="490" width="120" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="docmost-redis" value="Redis docmost" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="260" y="490" width="120" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="baserow-db" value="Postgres baserow (embedded)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="420" y="490" width="170" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="baserow-redis" value="Redis baserow (embedded)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="630" y="490" width="170" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="fs" value="Local FS / MinIO docmost files" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="850" y="490" width="160" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="ops-group" value="Infra ops" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#b85450;dashed=1;verticalAlign=top;fontStyle=1;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="620" width="600" height="120" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="cron" value="Cron host backups quotidiens 03:00 pg_dump + tar" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="660" width="240" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="monitoring" value="Uptime monitoring (a definir)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;dashed=1;fontSize=11;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380" y="660" width="240" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
<mxCell id="e-user-traefik" value="HTTPS" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontSize=10;" edge="1" parent="1" source="user" target="traefik">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-traefik-docmost" value="wiki.acadenice.fr" style="endArrow=classic;html=1;fontSize=10;" edge="1" parent="1" source="traefik" target="docmost">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-traefik-baserow" value="baserow.acadenice.fr" style="endArrow=classic;html=1;fontSize=10;" edge="1" parent="1" source="traefik" target="baserow">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-traefik-bridge" value="bridge.acadenice.fr" style="endArrow=classic;html=1;fontSize=10;" edge="1" parent="1" source="traefik" target="bridge">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-docmost-db" style="endArrow=classic;html=1;" edge="1" parent="1" source="docmost" target="docmost-db">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-docmost-redis" style="endArrow=classic;html=1;" edge="1" parent="1" source="docmost" target="docmost-redis">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-docmost-fs" style="endArrow=classic;html=1;" edge="1" parent="1" source="docmost" target="fs">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-baserow-db" style="endArrow=classic;html=1;" edge="1" parent="1" source="baserow" target="baserow-db">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-baserow-redis" style="endArrow=classic;html=1;" edge="1" parent="1" source="baserow" target="baserow-redis">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-bridge-baserow" value="API REST" style="endArrow=classic;html=1;fontSize=10;" edge="1" parent="1" source="bridge" target="baserow">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-bridge-docmost" value="API REST" style="endArrow=classic;html=1;fontSize=10;" edge="1" parent="1" source="bridge" target="docmost">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-bridge-redis" value="cache" style="endArrow=classic;html=1;dashed=1;fontSize=10;" edge="1" parent="1" source="bridge" target="docmost-redis">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-cron-docmost-db" value="pg_dump" style="endArrow=classic;html=1;dashed=1;fontSize=10;" edge="1" parent="1" source="cron" target="docmost-db">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-cron-baserow-db" value="pg_dump" style="endArrow=classic;html=1;dashed=1;fontSize=10;" edge="1" parent="1" source="cron" target="baserow-db">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="e-cron-fs" value="tar" style="endArrow=classic;html=1;dashed=1;fontSize=10;" edge="1" parent="1" source="cron" target="fs">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
39
scripts/backup.sh
Executable file
39
scripts/backup.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/backup.sh — backup quotidien Postgres + files (a appeler par cron)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DATE=$(date +%Y%m%d-%H%M%S)
|
||||||
|
BACKUP_DIR="${BACKUP_DIR:-/opt/formation-hub/backups/local}"
|
||||||
|
COMPOSE_FILES="${COMPOSE_FILES:--f compose.yml -f compose.prod.yml}"
|
||||||
|
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Backup start — DATE=$DATE"
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo " Postgres docmost..."
|
||||||
|
docker compose $COMPOSE_FILES exec -T docmost-db \
|
||||||
|
pg_dump -U docmost docmost | gzip > "$BACKUP_DIR/docmost-db-$DATE.sql.gz"
|
||||||
|
|
||||||
|
echo " Baserow data..."
|
||||||
|
docker compose $COMPOSE_FILES exec -T baserow \
|
||||||
|
tar czf - /baserow/data > "$BACKUP_DIR/baserow-data-$DATE.tar.gz"
|
||||||
|
|
||||||
|
echo " Docmost files..."
|
||||||
|
docker compose $COMPOSE_FILES exec -T docmost \
|
||||||
|
tar czf - /app/data/storage > "$BACKUP_DIR/docmost-files-$DATE.tar.gz"
|
||||||
|
|
||||||
|
echo " Sync distant (rclone) — si configure..."
|
||||||
|
if command -v rclone >/dev/null 2>&1 && [ -n "${RCLONE_REMOTE:-}" ]; then
|
||||||
|
rclone copy "$BACKUP_DIR/" "$RCLONE_REMOTE:" --include "*-$DATE.*"
|
||||||
|
else
|
||||||
|
echo " (rclone non configure — backup distant skip)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Cleanup local > ${RETENTION_DAYS}j..."
|
||||||
|
find "$BACKUP_DIR" -type f -mtime "+$RETENTION_DAYS" -delete
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Backup OK"
|
||||||
|
ls -lh "$BACKUP_DIR/"*-$DATE.*
|
||||||
48
scripts/healthcheck.sh
Executable file
48
scripts/healthcheck.sh
Executable file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/healthcheck.sh — verifie que la stack repond
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DOCMOST_URL="${DOCMOST_URL:-http://localhost:3000}"
|
||||||
|
BASEROW_URL="${BASEROW_URL:-http://localhost:8080}"
|
||||||
|
BRIDGE_URL="${BRIDGE_URL:-}"
|
||||||
|
TIMEOUT="${HEALTHCHECK_TIMEOUT:-10}"
|
||||||
|
|
||||||
|
red() { printf '\033[31m%s\033[0m\n' "$1"; }
|
||||||
|
green() { printf '\033[32m%s\033[0m\n' "$1"; }
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local name="$1"
|
||||||
|
local url="$2"
|
||||||
|
if curl -sf --max-time "$TIMEOUT" -o /dev/null "$url"; then
|
||||||
|
green " OK $name : $url"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
red " KO $name : $url"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Healthcheck (timeout ${TIMEOUT}s)..."
|
||||||
|
|
||||||
|
ok=0
|
||||||
|
total=0
|
||||||
|
|
||||||
|
((++total)) || true
|
||||||
|
check "Docmost " "$DOCMOST_URL" && ((++ok)) || true
|
||||||
|
|
||||||
|
((++total)) || true
|
||||||
|
check "Baserow " "$BASEROW_URL" && ((++ok)) || true
|
||||||
|
|
||||||
|
if [ -n "$BRIDGE_URL" ]; then
|
||||||
|
((++total)) || true
|
||||||
|
check "Bridge " "$BRIDGE_URL/api/health" && ((++ok)) || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ "$ok" -eq "$total" ]; then
|
||||||
|
green "Healthcheck : $ok/$total OK"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
red "Healthcheck : $ok/$total OK"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
34
scripts/smoke-test.sh
Executable file
34
scripts/smoke-test.sh
Executable file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/smoke-test.sh — test post-deploy minimal
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ENV_URL="${1:-${SMOKE_URL:-http://localhost:3000}}"
|
||||||
|
TIMEOUT=10
|
||||||
|
|
||||||
|
echo "Smoke test against $ENV_URL"
|
||||||
|
|
||||||
|
# 1. Healthcheck
|
||||||
|
echo " [1/3] HEAD $ENV_URL"
|
||||||
|
curl -sfI --max-time $TIMEOUT "$ENV_URL" > /dev/null
|
||||||
|
|
||||||
|
# 2. Resolve auth.info (assumes Outline-style API)
|
||||||
|
if [ -n "${SMOKE_AUTH_TOKEN:-}" ]; then
|
||||||
|
echo " [2/3] auth.info"
|
||||||
|
curl -sf -X POST --max-time $TIMEOUT \
|
||||||
|
-H "Authorization: Bearer $SMOKE_AUTH_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}' "$ENV_URL/api/auth.info" > /dev/null
|
||||||
|
else
|
||||||
|
echo " [2/3] auth.info — SKIP (SMOKE_AUTH_TOKEN absent)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Search
|
||||||
|
if [ -n "${SMOKE_AUTH_TOKEN:-}" ]; then
|
||||||
|
echo " [3/3] documents.search"
|
||||||
|
curl -sf -X POST --max-time $TIMEOUT \
|
||||||
|
-H "Authorization: Bearer $SMOKE_AUTH_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"query":"smoke"}' "$ENV_URL/api/documents.search" > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Smoke test OK"
|
||||||
Loading…
Add table
Reference in a new issue