Compare commits

...

32 Commits

Author SHA1 Message Date
gitea-actions
34adb01cbb chore: bump version to v0.1.20
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 51s
2026-04-07 12:40:39 +00:00
Matthieu
212a37f8dc fix(infra) : hardcode prod port 8086 like other apps
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:40:30 +02:00
gitea-actions
5cd7fc305f chore: bump version to v0.1.19
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 52s
2026-04-07 12:33:31 +00:00
Matthieu
9109e387b9 fix(ci) : set APP_ENV=prod in production Dockerfile
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Without APP_ENV=prod, Symfony defaults to dev and tries to load
DoctrineFixturesBundle which is excluded by --no-dev.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:32:53 +02:00
gitea-actions
0d87574ea2 chore: bump version to v0.1.18
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 13s
2026-04-07 12:31:13 +00:00
Matthieu
957e05342d chore : bump @malio/layer-ui to 1.2.2 and update config reference
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:31:04 +02:00
gitea-actions
e4f00c322d chore: bump version to v0.1.17
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-07 10:09:05 +00:00
Matthieu
0cb063cdd7 fix : add Tailwind theme colors and maintenance.html
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- tailwind.config.ts: full theme with primary/secondary/tertiary + m-* CSS vars
- infra/prod/maintenance.html: maintenance page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:08:58 +02:00
gitea-actions
282e2d3381 chore: bump version to v0.1.16
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-07 10:06:06 +00:00
Matthieu
c471b7993f fix : add missing UI components, maintenance page, fix useRoute warning
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- components/ui/SidebarLink.vue and AppTopNav.vue
- infra/prod/maintenance.html
- Remove useRoute() call in useApi onResponseError (fixes middleware warning)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:05:58 +02:00
gitea-actions
b1487c54d3 chore: bump version to v0.1.15
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 11s
2026-04-07 10:04:22 +00:00
Matthieu
778a0a16e8 fix(auth) : add login_check and logout routes
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:04:15 +02:00
gitea-actions
8fce19e3d4 chore: bump version to v0.1.14
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 11s
2026-04-07 10:01:52 +00:00
Matthieu
74d87126ea fix : add missing auth layout for login page
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:01:17 +02:00
gitea-actions
4effafe3a1 chore: bump version to v0.1.13
Some checks failed
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Failing after 13s
2026-04-07 10:00:31 +00:00
Matthieu
cbe6326284 feat(infra) : add nginx-proxy.conf with maintenance mode
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- infra/prod/nginx-proxy.conf: reverse proxy with maintenance file check
- Updated deployment doc with maintenance mode instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:00:22 +02:00
gitea-actions
3a792c1a56 chore: bump version to v0.1.12
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-07 09:56:09 +00:00
Matthieu
a14da5113f feat : add initial migration (User table)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:56:00 +02:00
gitea-actions
12e9326ccd chore: bump version to v0.1.11
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-07 09:53:46 +00:00
Matthieu
39b462e274 docs : add deployment-docker guide and fix missing assets
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- doc/deployment-docker.md: full Docker deployment guide (same pattern as Lesstime)
- frontend/public/coltura.png: placeholder logo (fixes build error)
- frontend/public/favicon.ico, robots.txt
- frontend/package-lock.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:53:38 +02:00
gitea-actions
cd51f3f945 chore: bump version to v0.1.10
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-07 09:46:04 +00:00
Matthieu
2649e02f7b chore : add .npmrc for @malio private registry
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:45:57 +02:00
gitea-actions
d33928b5f0 chore: bump version to v0.1.9
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 11s
2026-04-07 09:43:02 +00:00
Matthieu
582339ca99 chore : add .nvmrc (node 24)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:42:56 +02:00
gitea-actions
20e8382ae0 chore: bump version to v0.1.8
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 11s
2026-04-07 09:42:14 +00:00
Matthieu
224df3a4b7 chore : add composer.lock and reference.php
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:42:07 +02:00
gitea-actions
0282a21298 chore: bump version to v0.1.7
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 16s
2026-04-07 09:41:46 +00:00
Matthieu
adf007b379 fix : autowire persist processor in UserPasswordHasherProcessor
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:41:38 +02:00
gitea-actions
65c680da5b chore: bump version to v0.1.6
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 17s
2026-04-07 09:37:17 +00:00
Matthieu
85a6c0d795 refactor : reorganize codebase to DDD architecture
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Backend:
- src/Api/Auth/State/ — MeProvider, UserPasswordHasherProcessor
- src/Api/Shared/Resource/ — AppVersion
- src/Api/Shared/State/ — AppVersionProvider
- src/Domain/, src/Application/, src/Infrastructure/ — skeleton ready
- User entity stays in src/Entity/ (framework, outside DDD)

Frontend:
- frontend/domains/ — skeleton ready for bounded contexts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:37:10 +02:00
gitea-actions
a119950806 chore: bump version to v0.1.5
Some checks failed
Auto Tag Develop / tag (push) Successful in 4s
Build & Push Docker Image / build (push) Failing after 16s
2026-04-07 09:32:49 +00:00
Matthieu
2fe1062106 docs : add DDD architecture guidelines to CLAUDE.md
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Backend: Domain/Application/Infrastructure/Api layers per bounded context.
Frontend: domains/{context}/ modules with isolated components/services/stores.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:32:42 +02:00
33 changed files with 28635 additions and 72 deletions

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
24

116
CLAUDE.md
View File

@@ -1,6 +1,69 @@
# Coltura
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4.
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. **Architecture DDD (Domain-Driven Design).**
## Architecture DDD
Le projet suit une architecture DDD cote backend ET frontend. Le code est organise par **domaine metier** (Bounded Context), pas par type technique.
### Backend — Organisation par domaine
```
src/
Domain/ # Couche domaine (logique metier pure, aucune dependance framework)
{BoundedContext}/ # Ex: Customer, Sales, Catalog, Invoice...
Entity/ # Entites et Aggregates du domaine
ValueObject/ # Value Objects (Money, Address, Email...)
Repository/ # Interfaces des repositories (ports)
Service/ # Services domaine (logique metier)
Event/ # Domain Events
Exception/ # Exceptions metier
Application/ # Couche application (cas d'usage, orchestration)
{BoundedContext}/
Command/ # Commands (write) + Handlers
Query/ # Queries (read) + Handlers
DTO/ # Data Transfer Objects
Infrastructure/ # Couche infrastructure (implementations techniques)
{BoundedContext}/
Repository/ # Implementations Doctrine des repositories
Persistence/ # Mapping Doctrine (si XML/YAML)
Shared/ # Services techniques partages (mail, storage, etc.)
Api/ # Couche API (exposition HTTP)
{BoundedContext}/
Resource/ # ApiResource API Platform
State/ # Providers & Processors API Platform
```
**Regles DDD backend :**
- Le domaine (`Domain/`) ne depend de RIEN (pas de Doctrine, pas de Symfony, pas d'API Platform)
- Les repositories dans `Domain/` sont des **interfaces** ; les implementations Doctrine sont dans `Infrastructure/`
- Les entites API Platform (`Api/Resource/`) sont decouples des entites domaine si necessaire
- Chaque Bounded Context est autonome — pas d'import croise entre contextes (communiquer via events ou services application)
- `User` et `Auth` restent dans `src/` (hors DDD) car c'est du framework pur (Security Bundle)
### Frontend — Organisation par domaine
```
frontend/
domains/ # Modules metier
{bounded-context}/ # Ex: customer, sales, catalog, invoice...
components/ # Composants Vue specifiques au domaine
composables/ # Composables specifiques au domaine
services/ # Services API du domaine
dto/ # Types TypeScript du domaine
pages/ # Pages du domaine (optionnel, ou dans pages/)
stores/ # Store Pinia du domaine (si necessaire)
components/ # Composants UI partages (non lies a un domaine)
composables/ # Composables partages (useApi, useAppVersion)
stores/ # Stores globaux (auth, ui)
services/ # Services partages
```
**Regles DDD frontend :**
- Chaque domaine est un dossier autonome dans `frontend/domains/`
- Un domaine ne doit pas importer depuis un autre domaine — utiliser les composables/stores partages
- Les composants, services et types partages restent a la racine (`components/`, `composables/`, etc.)
- Les pages peuvent etre dans `frontend/pages/` (routing Nuxt) et importer les composants du domaine
## Stack
@@ -12,26 +75,37 @@ CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4.
## Structure
```
src/Entity/ # Entites Doctrine (User)
src/ApiResource/ # Ressources API Platform decouples (AppVersion)
src/State/ # Providers et Processors API Platform (MeProvider, AppVersionProvider, UserPasswordHasherProcessor)
src/Service/ # Services metier
src/Repository/ # Repositories Doctrine
src/DataFixtures/ # Fixtures
config/ # Config Symfony (security, api_platform, lexik_jwt, nelmio_cors, doctrine)
config/jwt/ # Cles JWT (private.pem, public.pem)
migrations/ # Migrations Doctrine
infra/dev/ # Config Docker dev (Dockerfile, nginx, php.ini, xdebug)
infra/prod/ # Config Docker prod (Dockerfile multi-stage, nginx, php-prod.ini)
frontend/ # App Nuxt 4
frontend/pages/ # Pages (index, login)
frontend/layouts/ # Layouts (default)
frontend/components/ # Composants Vue
frontend/composables/# Composables (useApi, useAppVersion)
frontend/stores/ # Stores Pinia (auth, ui)
frontend/services/ # Services API (auth)
frontend/services/dto/ # Types TypeScript
frontend/i18n/locales/ # Fichiers de traduction
src/
Domain/{Context}/Entity/ # Entites domaine
Domain/{Context}/ValueObject/ # Value Objects
Domain/{Context}/Repository/ # Interfaces repositories
Domain/{Context}/Service/ # Services domaine
Domain/{Context}/Event/ # Domain Events
Application/{Context}/Command/ # Commands + Handlers
Application/{Context}/Query/ # Queries + Handlers
Application/{Context}/DTO/ # Data Transfer Objects
Infrastructure/{Context}/Repository/ # Implementations Doctrine
Api/{Context}/Resource/ # ApiResource API Platform
Api/{Context}/State/ # Providers & Processors
Entity/ # Entites framework (User)
DataFixtures/ # Fixtures
config/ # Config Symfony
config/jwt/ # Cles JWT
migrations/ # Migrations Doctrine
infra/dev/ # Docker dev
infra/prod/ # Docker prod (multi-stage)
frontend/
domains/{context}/components/ # Composants du domaine
domains/{context}/composables/ # Composables du domaine
domains/{context}/services/ # Services API du domaine
domains/{context}/dto/ # Types TS du domaine
domains/{context}/stores/ # Store Pinia du domaine
components/ # Composants UI partages
composables/ # Composables partages (useApi, useAppVersion)
stores/ # Stores globaux (auth, ui)
pages/ # Pages (routing Nuxt)
layouts/ # Layouts
i18n/locales/ # Traductions
```
## Commandes

11149
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

1911
config/reference.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,3 +2,9 @@
controllers:
resource: routing.controllers
login_check:
path: /login_check
api_logout:
path: /api/logout

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.4'
app.version: '0.1.20'

314
doc/deployment-docker.md Normal file
View File

@@ -0,0 +1,314 @@
# Deploiement Docker — Coltura
## Pre-requis
### Docker
```bash
# Ubuntu
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER
```
Se deconnecter/reconnecter pour que le groupe `docker` prenne effet.
### Nginx
```bash
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
```
### PostgreSQL
PostgreSQL tourne dans un conteneur Docker separe (voir le repo `infra-postgres`).
Il doit etre installe et accessible avant de deployer Coltura.
Creer la base de donnees pour Coltura :
```bash
cd /var/www/postgres
docker compose exec postgres psql -U admin
```
```sql
-- Si le user n'existe pas encore
CREATE USER malio WITH PASSWORD 'motdepasse';
-- Creer la base
CREATE DATABASE coltura_prod OWNER malio;
\q
```
---
## Premiere installation (nouvelle machine)
Guide complet pour mettre en ligne Coltura sur une machine vierge. Inclut les pre-requis, la BDD et l'app.
### 1. Installer les pre-requis
Installer Docker, Nginx et PostgreSQL (voir section Pre-requis ci-dessus).
### 2. Creer le dossier de deploiement
```bash
sudo mkdir -p /var/www/coltura
sudo chown -R $(whoami):$(whoami) /var/www/coltura
cd /var/www/coltura
```
### 3. Se connecter au registry Docker de Gitea
```bash
docker login gitea.malio.fr
```
- **Username** : le nom d'utilisateur du compte organisation Gitea `MALIO`
- **Password** : le token REGISTRY_TOKEN dispo dans le bitwarden
Le login est sauvegarde dans `~/.docker/config.json`, pas besoin de le refaire a chaque deploiement.
### 4. Creer les fichiers de deploiement
Creer `docker-compose.yml` :
```yaml
services:
app:
image: gitea.malio.fr/malio-dev/coltura:${COLTURA_IMAGE_TAG:-latest}
container_name: coltura-app
env_file: .env
ports:
- "8083:80"
volumes:
- ./config/jwt:/var/www/html/config/jwt:ro
- ./uploads:/var/www/html/var/uploads
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
```
Creer `deploy.sh` :
```bash
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
TAG="${1:-latest}"
export COLTURA_IMAGE_TAG="$TAG"
echo "==> Deploying coltura:${TAG}..."
echo "==> Pulling image..."
docker compose pull
echo "==> Starting container..."
docker compose up -d
echo "==> Waiting for container to be ready..."
sleep 3
echo "==> Running migrations..."
docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction
echo "==> Clearing cache..."
docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
VERSION=$(docker compose exec -T app cat config/version.yaml | grep 'app.version' | awk -F"'" '{print $2}')
echo "==> Deployed v${VERSION}"
```
Rendre executable :
```bash
chmod +x deploy.sh
```
### 5. Configurer l'environnement
Creer `.env` avec les variables suivantes :
```env
# Symfony
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=<generer avec: openssl rand -hex 32>
# Database (host.docker.internal = la machine hote, ou le PG tourne en Docker)
DATABASE_URL="postgresql://malio:password@host.docker.internal:5432/coltura_prod?serverVersion=16&charset=utf8"
# JWT
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=<generer avec: openssl rand -hex 32>
JWT_COOKIE_SECURE=1
JWT_COOKIE_SAMESITE=lax
JWT_TOKEN_TTL=86400
JWT_COOKIE_TTL=86400
# CORS
CORS_ALLOW_ORIGIN='^https?://coltura\.malio-dev\.fr$'
# App
DEFAULT_URI=https://coltura.malio-dev.fr
```
### 6. Generer les cles JWT
```bash
mkdir -p config/jwt
openssl genpkey -algorithm RSA -out config/jwt/private.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem
```
Rendre les cles lisibles par le conteneur (www-data = uid 33) :
```bash
sudo chown 33:33 config/jwt/private.pem config/jwt/public.pem
sudo chmod 644 config/jwt/private.pem config/jwt/public.pem
```
### 7. Creer le dossier uploads
```bash
mkdir -p uploads
```
### 8. Configurer Nginx systeme (reverse proxy + maintenance)
Copier la config reverse proxy depuis le repo :
```bash
sudo cp infra/prod/nginx-proxy.conf /etc/nginx/sites-available/coltura.conf
```
Ou creer `/etc/nginx/sites-available/coltura.conf` manuellement (voir `infra/prod/nginx-proxy.conf`).
La config inclut le **mode maintenance** : si le fichier `/var/www/coltura/maintenance.on` existe, Nginx renvoie une 503 avec `maintenance.html`.
Activer le site :
```bash
sudo ln -sf /etc/nginx/sites-available/coltura.conf /etc/nginx/sites-enabled/coltura.conf
sudo nginx -t && sudo systemctl reload nginx
```
### Mode maintenance
```bash
# Activer la maintenance
touch /var/www/coltura/maintenance.on
# Desactiver la maintenance
rm /var/www/coltura/maintenance.on
```
Optionnel : creer une page `/var/www/coltura/public/maintenance.html` personnalisee.
### 9. Deployer
```bash
./deploy.sh
```
### 10. Creer le premier user admin
```bash
docker compose exec -T -u www-data app php bin/console security:hash-password --env=prod
```
Choisir `App\Entity\User`, taper le mdp, copier le hash. Puis :
```bash
cd /var/www/postgres
docker compose exec -T postgres psql -U malio coltura_prod -c "INSERT INTO \"user\" (username, roles, password, created_at) VALUES ('admin', '[\"ROLE_ADMIN\"]', '<le-hash>', NOW());"
```
Ou charger les fixtures (dev uniquement) :
```bash
docker compose exec -T -u www-data app php bin/console doctrine:fixtures:load --no-interaction --env=prod
```
### Structure finale du dossier
```
/var/www/coltura/
├── docker-compose.yml
├── deploy.sh
├── .env
├── config/jwt/
│ ├── private.pem
│ └── public.pem
└── uploads/
```
---
## Deployer une nouvelle version
Quand l'app est deja installee, deployer une mise a jour :
```bash
cd /var/www/coltura
./deploy.sh # deploie la derniere version (latest)
./deploy.sh v0.2.0 # deploie une version specifique
```
C'est tout. Le script pull l'image, redemarre le conteneur, lance les migrations et vide le cache.
---
## Rollback
### Image seule (pas de changement de schema BDD)
```bash
./deploy.sh v0.1.9
```
### Avec rollback de migration
```bash
# 1. Rollback schema (pendant que la version actuelle tourne encore)
docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate prev --no-interaction
# 2. Deployer l'ancienne version
./deploy.sh v0.1.9
```
---
## CI/CD
Le workflow `.gitea/workflows/build-docker.yml` se declenche automatiquement sur push de tag `v*` :
1. Build l'image multi-stage
2. Push vers `gitea.malio.fr/malio-dev/coltura:<tag>` et `:latest`
Combine avec `auto-tag-develop.yml`, chaque push sur `develop` cree automatiquement un tag → build → image disponible.
---
## Voir les logs
```bash
cd /var/www/coltura
docker compose logs -f # tous les logs
docker compose logs -f --tail=100 # 100 dernieres lignes
```
Logs Symfony :
```bash
docker compose exec app cat var/log/prod.log
```

1
frontend/.npmrc Normal file
View File

@@ -0,0 +1 @@
@malio:registry=https://gitea.malio.fr/api/packages/MALIO-DEV/npm/

View File

@@ -0,0 +1,48 @@
<template>
<header class="border-b border-neutral-200 bg-primary-500 px-3 py-2 text-white sm:px-5 sm:py-2 max-h-[60px]">
<div class="flex h-full items-center justify-between">
<MalioButtonIcon
icon="mdi:menu"
aria-label="Menu"
variant="ghost"
icon-size="24"
button-class="lg:hidden text-white hover:bg-primary-600"
@click="ui.openMobileSidebar()"
/>
<div class="hidden items-center gap-2 lg:flex">
<h1 class="text-lg font-bold tracking-tight">Coltura</h1>
</div>
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
<div class="group relative flex gap-2 sm:gap-4">
<Icon name="mdi:account-circle-outline" class="self-center cursor-pointer" size="36" />
<p class="hidden self-center cursor-pointer sm:block">{{ user?.username }}</p>
<div class="invisible absolute right-0 top-full z-50 mt-2 w-44 rounded-md border border-neutral-200 bg-white py-1 text-sm text-neutral-800 opacity-0 shadow-lg transition-all group-hover:visible group-hover:opacity-100">
<button
type="button"
class="block w-full px-3 py-2 text-left hover:bg-neutral-100"
@click="handleLogout"
>
Deconnexion
</button>
</div>
</div>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import type { UserData } from '~/services/dto/user-data'
defineProps<{
user?: UserData | null
}>()
const auth = useAuthStore()
const ui = useUiStore()
async function handleLogout() {
await auth.logout()
await navigateTo('/login')
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<NuxtLink
:to="to"
class="group/link relative flex items-center transition-colors hover:text-primary-500"
:class="linkClasses"
:active-class="exact ? '' : activeClass"
:exact-active-class="exact ? activeClass : ''"
>
<Icon :name="icon" :size="sub ? '20' : '24'" class="flex-shrink-0" />
<span
v-if="!collapsed"
class="self-baseline whitespace-nowrap overflow-hidden transition-opacity duration-300"
:class="sub ? 'text-sm' : 'text-md'"
>
{{ label }}
</span>
<div
v-if="collapsed"
class="pointer-events-none absolute left-full z-50 ml-2 rounded-md bg-neutral-800 px-2 py-1 text-xs text-white opacity-0 shadow-lg transition-opacity group-hover/link:pointer-events-auto group-hover/link:opacity-100 whitespace-nowrap"
>
{{ label }}
</div>
</NuxtLink>
</template>
<script setup lang="ts">
const props = defineProps<{
to: string
icon: string
label: string
collapsed: boolean
sub?: boolean
exact?: boolean
}>()
const activeClass = computed(() => {
if (props.collapsed) {
return '!text-primary-500 bg-primary-500/10'
}
return '!text-primary-500 bg-tertiary-500'
})
const linkClasses = computed(() => {
if (props.collapsed) {
return 'justify-center w-10 h-10 mx-auto my-1 p-2 rounded-lg text-neutral-600 hover:text-primary-500 hover:bg-primary-500/10'
}
if (props.sub) {
return 'gap-3 px-4 py-2 pl-12 text-sm font-semibold text-neutral-700'
}
return 'gap-3 px-4 py-3 text-md font-semibold text-neutral-700'
})
</script>

View File

@@ -126,10 +126,7 @@ export function useApi(): ApiClient {
if (!isHandlingUnauthorized) {
isHandlingUnauthorized = true
auth.clearSession()
const route = useRoute()
if (route.path !== '/login') {
await navigateTo('/login')
}
await navigateTo('/login')
isHandlingUnauthorized = false
}
}

View File

View File

@@ -0,0 +1,7 @@
<template>
<div class="min-h-screen bg-tertiary-500 from-tertiary-500 via-white to-neutral-100 text-neutral-900">
<main class="mx-auto flex min-h-screen w-full max-w-[720px] items-center px-6 py-12">
<slot />
</main>
</div>
</template>

14851
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,25 @@
{
"name": "coltura-frontend",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@malio/layer-ui": "^1.2.0",
"@nuxt/icon": "^2.2.1",
"@nuxtjs/i18n": "^10.2.3",
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.3",
"nuxt": "^4.3.1",
"nuxt-toast": "^1.4.0",
"pinia": "^3.0.4",
"vue": "^3.5.29",
"vue-router": "^4.6.4"
}
"name": "coltura-frontend",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@malio/layer-ui": "^1.2.2",
"@nuxt/icon": "^2.2.1",
"@nuxtjs/i18n": "^10.2.3",
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.3",
"nuxt": "^4.3.1",
"nuxt-toast": "^1.4.0",
"pinia": "^3.0.4",
"vue": "^3.5.29",
"vue-router": "^4.6.4"
}
}

BIN
frontend/public/coltura.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

View File

@@ -1,12 +1,48 @@
import type { Config } from 'tailwindcss'
import type {Config} from 'tailwindcss'
export default <Partial<Config>>{
content: [
'./components/**/*.{vue,js,ts}',
'./layouts/**/*.vue',
'./pages/**/*.vue',
'./composables/**/*.{js,ts}',
'./plugins/**/*.{js,ts}',
'./app.vue',
],
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif']
},
colors: {
primary: {
500: '#222783',
},
secondary: {
500: '#304998'
},
tertiary: {
500: '#F3F4F8'
},
blue: {
500: '#056CF2'
},
m: {
primary: 'rgb(var(--m-primary) / <alpha-value>)',
secondary: 'rgb(var(--m-secondary, 75 77 237) / <alpha-value>)',
tertiary: 'rgb(var(--m-tertiary, 243 244 248) / <alpha-value>)',
border: 'rgb(var(--m-border) / <alpha-value>)',
text: 'rgb(var(--m-text) / <alpha-value>)',
muted: 'rgb(var(--m-muted) / <alpha-value>)',
bg: 'rgb(var(--m-bg) / <alpha-value>)',
surface: 'rgb(var(--m-surface) / <alpha-value>)',
disabled: 'rgb(var(--m-disabled) / <alpha-value>)',
danger: 'rgb(var(--m-danger) / <alpha-value>)',
success: 'rgb(var(--m-success) / <alpha-value>)',
'btn-primary': 'rgb(var(--m-btn-primary) / <alpha-value>)',
'btn-primary-hover': 'rgb(var(--m-btn-primary-hover) / <alpha-value>)',
'btn-primary-active': 'rgb(var(--m-btn-primary-active) / <alpha-value>)',
'btn-secondary': 'rgb(var(--m-btn-secondary) / <alpha-value>)',
'btn-secondary-hover': 'rgb(var(--m-btn-secondary-hover) / <alpha-value>)',
'btn-secondary-active': 'rgb(var(--m-btn-secondary-active) / <alpha-value>)',
'btn-danger': 'rgb(var(--m-btn-danger) / <alpha-value>)',
'btn-danger-hover': 'rgb(var(--m-btn-danger-hover) / <alpha-value>)',
'btn-danger-active': 'rgb(var(--m-btn-danger-active) / <alpha-value>)',
}
}
}
}
}

View File

@@ -6,8 +6,6 @@ POSTGRES_DB=coltura
POSTGRES_USER=coltura
POSTGRES_PASSWORD=CHANGE_ME_IN_PRODUCTION
APP_PORT=80
JWT_PASSPHRASE=CHANGE_ME_IN_PRODUCTION
JWT_COOKIE_SECURE=1
JWT_TOKEN_TTL=86400

View File

@@ -58,6 +58,8 @@ RUN curl --insecure https://getcomposer.org/composer.phar -o /usr/bin/composer &
WORKDIR /var/www/html
ENV APP_ENV=prod
# Copier les fichiers projet
COPY . /var/www/html

View File

@@ -3,7 +3,7 @@ services:
container_name: php-coltura-fpm
build:
context: ../../
dockerfile: infra/deploy/Dockerfile
dockerfile: infra/prod/Dockerfile
target: php-base
environment:
APP_ENV: prod
@@ -19,12 +19,12 @@ services:
container_name: nginx-coltura
build:
context: ../../
dockerfile: infra/deploy/Dockerfile
dockerfile: infra/prod/Dockerfile
target: nginx
depends_on:
- php
ports:
- "${APP_PORT:-80}:80"
- "8086:80"
restart: unless-stopped
db:

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance en cours</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
.container {
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0,0,0,0.10);
padding: 48px 40px;
max-width: 480px;
text-align: center;
}
.icon {
font-size: 48px;
margin-bottom: 16px;
}
h1 {
font-size: 24px;
color: #111827;
margin: 0 0 12px;
}
p {
font-size: 16px;
color: #6b7280;
margin: 0;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">&#128736;</div>
<h1>Maintenance en cours</h1>
<p>L'application est temporairement indisponible pour mise a jour. Elle sera de retour dans quelques instants.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
server {
listen 80;
listen [::]:80;
server_name coltura.malio-dev.fr;
root /var/www/coltura/public;
# Maintenance mode
if (-f /var/www/coltura/maintenance.on) {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
rewrite ^(.*)$ /maintenance.html break;
}
location = /maintenance.html {
internal;
}
location / {
proxy_pass http://127.0.0.1:8083;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 55m;
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260407095546 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE "user" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, username VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON "user" (username)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE "user"');
}
}

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\State;
namespace App\Api\Auth\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;

View File

@@ -2,11 +2,12 @@
declare(strict_types=1);
namespace App\State;
namespace App\Api\Auth\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\User;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
/**
@@ -15,7 +16,7 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserPasswordHasherProcessor implements ProcessorInterface
{
public function __construct(
/** @var ProcessorInterface<User, User> */
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
private readonly ProcessorInterface $persistProcessor,
private readonly UserPasswordHasherInterface $passwordHasher,
) {}

View File

@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\ApiResource;
namespace App\Api\Shared\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use App\State\AppVersionProvider;
use App\Api\Shared\State\AppVersionProvider;
#[ApiResource(
operations: [

View File

@@ -2,10 +2,11 @@
declare(strict_types=1);
namespace App\State;
namespace App\Api\Shared\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Api\Shared\Resource\AppVersion;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
@@ -20,6 +21,6 @@ class AppVersionProvider implements ProviderInterface
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object
{
return new \App\ApiResource\AppVersion($this->appVersion);
return new AppVersion($this->appVersion);
}
}

0
src/Application/.gitkeep Normal file
View File

0
src/Domain/.gitkeep Normal file
View File

View File

@@ -10,9 +10,9 @@ use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use App\Api\Auth\State\MeProvider;
use App\Api\Auth\State\UserPasswordHasherProcessor;
use App\Repository\UserRepository;
use App\State\MeProvider;
use App\State\UserPasswordHasherProcessor;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;

View File