Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68c1c98b27 |
@@ -13,7 +13,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.REGISTRY_TOKEN }}
|
token: ${{ secrets.RELEASE_TOKEN || gitea.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- name: Create next tag from config/version.yaml
|
- name: Create next tag from config/version.yaml
|
||||||
|
|||||||
116
CLAUDE.md
116
CLAUDE.md
@@ -1,69 +1,6 @@
|
|||||||
# Coltura
|
# Coltura
|
||||||
|
|
||||||
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. **Architecture DDD (Domain-Driven Design).**
|
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4.
|
||||||
|
|
||||||
## 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
|
## Stack
|
||||||
|
|
||||||
@@ -75,37 +12,26 @@ frontend/
|
|||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/Entity/ # Entites Doctrine (User)
|
||||||
Domain/{Context}/Entity/ # Entites domaine
|
src/ApiResource/ # Ressources API Platform decouples (AppVersion)
|
||||||
Domain/{Context}/ValueObject/ # Value Objects
|
src/State/ # Providers et Processors API Platform (MeProvider, AppVersionProvider, UserPasswordHasherProcessor)
|
||||||
Domain/{Context}/Repository/ # Interfaces repositories
|
src/Service/ # Services metier
|
||||||
Domain/{Context}/Service/ # Services domaine
|
src/Repository/ # Repositories Doctrine
|
||||||
Domain/{Context}/Event/ # Domain Events
|
src/DataFixtures/ # Fixtures
|
||||||
Application/{Context}/Command/ # Commands + Handlers
|
config/ # Config Symfony (security, api_platform, lexik_jwt, nelmio_cors, doctrine)
|
||||||
Application/{Context}/Query/ # Queries + Handlers
|
config/jwt/ # Cles JWT (private.pem, public.pem)
|
||||||
Application/{Context}/DTO/ # Data Transfer Objects
|
migrations/ # Migrations Doctrine
|
||||||
Infrastructure/{Context}/Repository/ # Implementations Doctrine
|
infra/dev/ # Config Docker dev (Dockerfile, nginx, php.ini, xdebug)
|
||||||
Api/{Context}/Resource/ # ApiResource API Platform
|
infra/prod/ # Config Docker prod (Dockerfile multi-stage, nginx, php-prod.ini)
|
||||||
Api/{Context}/State/ # Providers & Processors
|
frontend/ # App Nuxt 4
|
||||||
Entity/ # Entites framework (User)
|
frontend/pages/ # Pages (index, login)
|
||||||
DataFixtures/ # Fixtures
|
frontend/layouts/ # Layouts (default)
|
||||||
config/ # Config Symfony
|
frontend/components/ # Composants Vue
|
||||||
config/jwt/ # Cles JWT
|
frontend/composables/# Composables (useApi, useAppVersion)
|
||||||
migrations/ # Migrations Doctrine
|
frontend/stores/ # Stores Pinia (auth, ui)
|
||||||
infra/dev/ # Docker dev
|
frontend/services/ # Services API (auth)
|
||||||
infra/prod/ # Docker prod (multi-stage)
|
frontend/services/dto/ # Types TypeScript
|
||||||
frontend/
|
frontend/i18n/locales/ # Fichiers de traduction
|
||||||
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
|
## Commandes
|
||||||
|
|||||||
11149
composer.lock
generated
11149
composer.lock
generated
File diff suppressed because it is too large
Load Diff
1911
config/reference.php
1911
config/reference.php
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,3 @@
|
|||||||
|
|
||||||
controllers:
|
controllers:
|
||||||
resource: routing.controllers
|
resource: routing.controllers
|
||||||
|
|
||||||
login_check:
|
|
||||||
path: /login_check
|
|
||||||
|
|
||||||
api_logout:
|
|
||||||
path: /api/logout
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.24'
|
app.version: '0.1.2'
|
||||||
|
|||||||
@@ -1,314 +0,0 @@
|
|||||||
# 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 +0,0 @@
|
|||||||
@malio:registry=https://gitea.malio.fr/api/packages/MALIO-DEV/npm/
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -126,7 +126,10 @@ export function useApi(): ApiClient {
|
|||||||
if (!isHandlingUnauthorized) {
|
if (!isHandlingUnauthorized) {
|
||||||
isHandlingUnauthorized = true
|
isHandlingUnauthorized = true
|
||||||
auth.clearSession()
|
auth.clearSession()
|
||||||
await navigateTo('/login')
|
const route = useRoute()
|
||||||
|
if (route.path !== '/login') {
|
||||||
|
await navigateTo('/login')
|
||||||
|
}
|
||||||
isHandlingUnauthorized = false
|
isHandlingUnauthorized = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
||||||
11658
frontend/package-lock.json
generated
11658
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "coltura-frontend",
|
"name": "coltura-frontend",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare",
|
"postinstall": "nuxt prepare",
|
||||||
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
|
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@malio/layer-ui": "^1.2.2",
|
"@malio/layer-ui": "^1.2.0",
|
||||||
"@nuxt/icon": "^2.2.1",
|
"@nuxt/icon": "^2.2.1",
|
||||||
"@nuxtjs/i18n": "^10.2.3",
|
"@nuxtjs/i18n": "^10.2.3",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"nuxt": "^4.3.1",
|
"nuxt": "^4.3.1",
|
||||||
"nuxt-toast": "^1.4.0",
|
"nuxt-toast": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.29",
|
"vue": "^3.5.29",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,2 +0,0 @@
|
|||||||
User-Agent: *
|
|
||||||
Disallow:
|
|
||||||
@@ -1,48 +1,12 @@
|
|||||||
import type {Config} from 'tailwindcss'
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
export default <Partial<Config>>{
|
export default <Partial<Config>>{
|
||||||
darkMode: 'class',
|
content: [
|
||||||
theme: {
|
'./components/**/*.{vue,js,ts}',
|
||||||
extend: {
|
'./layouts/**/*.vue',
|
||||||
fontFamily: {
|
'./pages/**/*.vue',
|
||||||
sans: ['"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif']
|
'./composables/**/*.{js,ts}',
|
||||||
},
|
'./plugins/**/*.{js,ts}',
|
||||||
colors: {
|
'./app.vue',
|
||||||
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>)',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ APP_ENV=prod
|
|||||||
APP_DEBUG=0
|
APP_DEBUG=0
|
||||||
APP_SECRET=CHANGE_ME_IN_PRODUCTION
|
APP_SECRET=CHANGE_ME_IN_PRODUCTION
|
||||||
|
|
||||||
DATABASE_URL="postgresql://coltura:CHANGE_ME@host.docker.internal:5432/coltura?serverVersion=16&charset=utf8"
|
POSTGRES_DB=coltura
|
||||||
|
POSTGRES_USER=coltura
|
||||||
|
POSTGRES_PASSWORD=CHANGE_ME_IN_PRODUCTION
|
||||||
|
|
||||||
|
APP_PORT=80
|
||||||
|
|
||||||
JWT_PASSPHRASE=CHANGE_ME_IN_PRODUCTION
|
JWT_PASSPHRASE=CHANGE_ME_IN_PRODUCTION
|
||||||
JWT_COOKIE_SECURE=1
|
JWT_COOKIE_SECURE=1
|
||||||
|
|||||||
@@ -1,81 +1,86 @@
|
|||||||
# --- Stage 1: Build backend ---
|
ARG DOCKER_PHP_VERSION=8.4.6
|
||||||
FROM php:8.4-cli AS backend-build
|
|
||||||
|
|
||||||
|
FROM php:${DOCKER_PHP_VERSION}-fpm-bullseye AS php-base
|
||||||
|
|
||||||
|
ARG DOCKER_NODE_VERSION=24.12.0
|
||||||
|
ENV DOCKER_NODE_VERSION="${DOCKER_NODE_VERSION}"
|
||||||
|
|
||||||
|
# Installer les dépendances et extensions PHP nécessaires
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \
|
libicu-dev \
|
||||||
unzip curl git \
|
libpq-dev \
|
||||||
&& docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \
|
libpng-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
libzip-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
ca-certificates \
|
||||||
|
gnupg \
|
||||||
|
libbz2-dev \
|
||||||
|
libgmp-dev \
|
||||||
|
libldap2-dev \
|
||||||
|
libonig-dev \
|
||||||
|
libsodium-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
libssl-dev \
|
||||||
|
wget \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
&& docker-php-ext-install -j$(nproc) \
|
||||||
|
intl \
|
||||||
|
zip \
|
||||||
|
bcmath \
|
||||||
|
bz2 \
|
||||||
|
calendar \
|
||||||
|
exif \
|
||||||
|
gd \
|
||||||
|
gettext \
|
||||||
|
gmp \
|
||||||
|
ldap \
|
||||||
|
pcntl \
|
||||||
|
pdo_pgsql \
|
||||||
|
soap \
|
||||||
|
sockets \
|
||||||
|
sysvsem \
|
||||||
|
xsl \
|
||||||
|
&& docker-php-ext-enable opcache \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/*
|
||||||
|
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
# Installation de node
|
||||||
|
RUN wget -qO- "https://nodejs.org/dist/v${DOCKER_NODE_VERSION}/node-v${DOCKER_NODE_VERSION}-linux-x64.tar.xz" | tar xJC /tmp/ && \
|
||||||
|
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/bin /usr/ && \
|
||||||
|
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/include /usr/ && \
|
||||||
|
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/lib /usr/ && \
|
||||||
|
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/share /usr/ && \
|
||||||
|
rm -rf /tmp/*
|
||||||
|
|
||||||
WORKDIR /app
|
# Installation de composer
|
||||||
COPY composer.json composer.lock ./
|
RUN curl --insecure https://getcomposer.org/composer.phar -o /usr/bin/composer && chmod +x /usr/bin/composer
|
||||||
RUN APP_ENV=prod APP_DEBUG=0 composer install --no-dev --no-scripts --no-interaction
|
|
||||||
|
|
||||||
COPY bin bin/
|
|
||||||
COPY config config/
|
|
||||||
COPY migrations migrations/
|
|
||||||
COPY public public/
|
|
||||||
COPY src src/
|
|
||||||
|
|
||||||
RUN composer dump-autoload --optimize --no-dev
|
|
||||||
|
|
||||||
# --- Stage 2: Build frontend ---
|
|
||||||
FROM node:22-alpine AS frontend-build
|
|
||||||
|
|
||||||
WORKDIR /app/frontend
|
|
||||||
COPY frontend/package.json frontend/package-lock.json ./
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
COPY frontend/ ./
|
|
||||||
ENV CI=1 \
|
|
||||||
NUXT_TELEMETRY_DISABLED=1 \
|
|
||||||
NUXT_PUBLIC_API_BASE=/api \
|
|
||||||
NUXT_PUBLIC_APP_BASE=/
|
|
||||||
RUN npm run generate
|
|
||||||
|
|
||||||
# --- Stage 3: Production image ---
|
|
||||||
FROM php:8.4-fpm AS production
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \
|
|
||||||
nginx supervisor \
|
|
||||||
&& docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# PHP production config
|
|
||||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
|
||||||
|
|
||||||
# PHP-FPM: forward worker output to stderr for docker logs
|
|
||||||
RUN echo "catch_workers_output = yes" >> /usr/local/etc/php-fpm.d/www.conf \
|
|
||||||
&& echo "decorate_workers_output = no" >> /usr/local/etc/php-fpm.d/www.conf
|
|
||||||
|
|
||||||
# Nginx: log to stdout/stderr
|
|
||||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
|
|
||||||
&& ln -sf /dev/stderr /var/log/nginx/error.log
|
|
||||||
|
|
||||||
# Remove default nginx site
|
|
||||||
RUN rm -f /etc/nginx/sites-enabled/default
|
|
||||||
|
|
||||||
# Configs
|
|
||||||
COPY infra/prod/supervisord.conf /etc/supervisor/conf.d/app.conf
|
|
||||||
COPY infra/prod/nginx.conf /etc/nginx/sites-enabled/coltura.conf
|
|
||||||
|
|
||||||
# Backend from stage 1
|
|
||||||
COPY --from=backend-build /app /var/www/html
|
|
||||||
|
|
||||||
# Frontend from stage 2
|
|
||||||
COPY --from=frontend-build /app/frontend/.output/public /var/www/html/frontend/.output/public
|
|
||||||
|
|
||||||
# Symfony needs a .env file to boot (variables are overridden by env_file in docker-compose)
|
|
||||||
RUN echo "APP_ENV=prod" > /var/www/html/.env
|
|
||||||
|
|
||||||
# Permissions
|
|
||||||
RUN mkdir -p /var/www/html/var /var/www/html/config/jwt \
|
|
||||||
&& chown -R www-data:www-data /var/www/html/var
|
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/app.conf"]
|
# Copier les fichiers projet
|
||||||
|
COPY . /var/www/html
|
||||||
|
|
||||||
|
# Installation des dépendances PHP (prod)
|
||||||
|
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
|
||||||
|
# Génération des clés JWT si absentes
|
||||||
|
RUN php bin/console lexik:jwt:generate-keypair --skip-if-exists
|
||||||
|
|
||||||
|
# Build du frontend
|
||||||
|
RUN cd frontend && npm ci && npm run build:dist && rm -rf node_modules
|
||||||
|
|
||||||
|
# Permissions
|
||||||
|
RUN chown -R www-data:www-data /var/www/html/var /var/www/html/frontend/dist
|
||||||
|
|
||||||
|
# PHP prod config
|
||||||
|
COPY infra/deploy/php-prod.ini /usr/local/etc/php/php.ini
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
# ── Nginx stage ──
|
||||||
|
FROM nginx:1.27-alpine AS nginx
|
||||||
|
|
||||||
|
COPY infra/deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=php-base /var/www/html/public /var/www/html/public
|
||||||
|
COPY --from=php-base /var/www/html/frontend/dist /var/www/html/frontend/dist
|
||||||
|
|||||||
@@ -1,12 +1,42 @@
|
|||||||
services:
|
services:
|
||||||
app:
|
php:
|
||||||
image: gitea.malio.fr/malio-dev/coltura:${COLTURA_IMAGE_TAG:-latest}
|
container_name: php-coltura-fpm
|
||||||
container_name: coltura-app
|
build:
|
||||||
env_file: .env
|
context: ../../
|
||||||
ports:
|
dockerfile: infra/deploy/Dockerfile
|
||||||
- "8086:80"
|
target: php-base
|
||||||
|
environment:
|
||||||
|
APP_ENV: prod
|
||||||
|
APP_DEBUG: 0
|
||||||
|
DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=16&charset=utf8"
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/jwt:/var/www/html/config/jwt:ro
|
- uploads_data:/var/www/html/var/uploads
|
||||||
extra_hosts:
|
depends_on:
|
||||||
- "host.docker.internal:host-gateway"
|
- db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
container_name: nginx-coltura
|
||||||
|
build:
|
||||||
|
context: ../../
|
||||||
|
dockerfile: infra/deploy/Dockerfile
|
||||||
|
target: nginx
|
||||||
|
depends_on:
|
||||||
|
- php
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT:-80}:80"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- pg_data:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pg_data:
|
||||||
|
uploads_data:
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<!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">🛠</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>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,24 +2,16 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
root /var/www/html/frontend/.output/public;
|
root /var/www/html/frontend/dist;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
client_max_body_size 55m;
|
client_max_body_size 55m;
|
||||||
|
|
||||||
access_log /dev/stdout;
|
|
||||||
error_log /dev/stderr;
|
|
||||||
|
|
||||||
location ^~ /api/ {
|
location ^~ /api/ {
|
||||||
root /var/www/html/public;
|
root /var/www/html/public;
|
||||||
try_files $uri /index.php?$query_string;
|
try_files $uri /index.php?$query_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ^~ /bundles/ {
|
|
||||||
root /var/www/html/public;
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /api/login_check {
|
location = /api/login_check {
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
|
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
|
||||||
@@ -27,15 +19,19 @@ server {
|
|||||||
fastcgi_param SCRIPT_NAME /index.php;
|
fastcgi_param SCRIPT_NAME /index.php;
|
||||||
fastcgi_param PATH_INFO /login_check;
|
fastcgi_param PATH_INFO /login_check;
|
||||||
fastcgi_param REQUEST_URI /login_check;
|
fastcgi_param REQUEST_URI /login_check;
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
fastcgi_pass php:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /bundles/ {
|
||||||
|
root /var/www/html/public;
|
||||||
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/index\.php(/|$) {
|
location ~ ^/index\.php(/|$) {
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
|
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
|
||||||
fastcgi_param DOCUMENT_ROOT /var/www/html/public;
|
fastcgi_param DOCUMENT_ROOT /var/www/html/public;
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
fastcgi_pass php:9000;
|
||||||
internal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
[supervisord]
|
|
||||||
nodaemon=true
|
|
||||||
user=root
|
|
||||||
logfile=/dev/null
|
|
||||||
logfile_maxbytes=0
|
|
||||||
pidfile=/var/run/supervisord.pid
|
|
||||||
|
|
||||||
[program:php-fpm]
|
|
||||||
command=php-fpm -F
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
stopasgroup=true
|
|
||||||
stopsignal=QUIT
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
command=nginx -g "daemon off;"
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
stopasgroup=true
|
|
||||||
stopsignal=QUIT
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<?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"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\Shared\Resource;
|
namespace App\ApiResource;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use App\Api\Shared\State\AppVersionProvider;
|
use App\State\AppVersionProvider;
|
||||||
|
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Command;
|
|
||||||
|
|
||||||
use App\Entity\User;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
||||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
|
||||||
|
|
||||||
#[AsCommand(
|
|
||||||
name: 'app:create-user',
|
|
||||||
description: 'Create a new user',
|
|
||||||
)]
|
|
||||||
class CreateUserCommand extends Command
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly EntityManagerInterface $em,
|
|
||||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
|
||||||
) {
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this
|
|
||||||
->addArgument('username', InputArgument::REQUIRED, 'Username')
|
|
||||||
->addArgument('password', InputArgument::REQUIRED, 'Plain password')
|
|
||||||
->addOption('admin', null, InputOption::VALUE_NONE, 'Grant ROLE_ADMIN')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
||||||
{
|
|
||||||
$io = new SymfonyStyle($input, $output);
|
|
||||||
|
|
||||||
$username = $input->getArgument('username');
|
|
||||||
$plainPassword = $input->getArgument('password');
|
|
||||||
|
|
||||||
$user = new User();
|
|
||||||
$user->setUsername($username);
|
|
||||||
$user->setPassword($this->passwordHasher->hashPassword($user, $plainPassword));
|
|
||||||
|
|
||||||
if ($input->getOption('admin')) {
|
|
||||||
$user->setRoles(['ROLE_ADMIN']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->em->persist($user);
|
|
||||||
$this->em->flush();
|
|
||||||
|
|
||||||
$io->success(sprintf('User "%s" created%s.', $username, $input->getOption('admin') ? ' with ROLE_ADMIN' : ''));
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,9 @@ use ApiPlatform\Metadata\Get;
|
|||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
use ApiPlatform\Metadata\Patch;
|
||||||
use ApiPlatform\Metadata\Post;
|
use ApiPlatform\Metadata\Post;
|
||||||
use App\Api\Auth\State\MeProvider;
|
|
||||||
use App\Api\Auth\State\UserPasswordHasherProcessor;
|
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
|
use App\State\MeProvider;
|
||||||
|
use App\State\UserPasswordHasherProcessor;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\Shared\State;
|
namespace App\State;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
use App\Api\Shared\Resource\AppVersion;
|
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +20,6 @@ class AppVersionProvider implements ProviderInterface
|
|||||||
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object
|
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object
|
||||||
{
|
{
|
||||||
return new AppVersion($this->appVersion);
|
return new \App\ApiResource\AppVersion($this->appVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\Auth\State;
|
namespace App\State;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProviderInterface;
|
use ApiPlatform\State\ProviderInterface;
|
||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Api\Auth\State;
|
namespace App\State;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
use ApiPlatform\Metadata\Operation;
|
||||||
use ApiPlatform\State\ProcessorInterface;
|
use ApiPlatform\State\ProcessorInterface;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
||||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,7 +15,7 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
|||||||
class UserPasswordHasherProcessor implements ProcessorInterface
|
class UserPasswordHasherProcessor implements ProcessorInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
/** @var ProcessorInterface<User, User> */
|
||||||
private readonly ProcessorInterface $persistProcessor,
|
private readonly ProcessorInterface $persistProcessor,
|
||||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
) {}
|
) {}
|
||||||
Reference in New Issue
Block a user