diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..aed2bc5 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +--- + +name: "Merge Request" +about: "Template de MR" +title: "[#NUMERO_TICKET] TITRE TICKET" +ref: "main" + +--- + +| Numéro du ticket | Titre du ticket | +|------------------|-----------------| +| | | + +## Description de la PR + +## Modification du .env + +## Check list + +- [ ] Pas de régression +- [ ] TU/TI/TF rédigée +- [ ] TU/TI/TF OK +- [ ] CHANGELOG modifié diff --git a/.gitea/workflows/auto-tag-develop.yml b/.gitea/workflows/auto-tag-develop.yml new file mode 100644 index 0000000..48f28d5 --- /dev/null +++ b/.gitea/workflows/auto-tag-develop.yml @@ -0,0 +1,65 @@ +name: Auto Tag Develop + +on: + push: + branches: + - develop + +jobs: + tag: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN }} + persist-credentials: true + + - name: Create next tag from config/version.yaml + shell: bash + run: | + set -euo pipefail + + # Skip if current commit already has a vX.Y.Z tag + if git tag --points-at HEAD | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Tag already exists on this commit. Skipping." + exit 0 + fi + + changed_version=false + if git diff --name-only "${{ gitea.event.before }}" "${{ gitea.event.after }}" | grep -q '^config/version\.yaml$'; then + changed_version=true + fi + + read_version() { + awk -F': *' '/app\.version:/{print $2}' config/version.yaml | tr -d '[:space:]' | tr -d "'\"" + } + + if $changed_version; then + version="$(read_version)" + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version in version.yaml: $version" >&2 + exit 1 + fi + else + last_tag="$(git tag -l 'v*' --sort=-v:refname | head -n1 || true)" + if [ -z "$last_tag" ]; then + version="0.1.0" + else + base="${last_tag#v}" + IFS='.' read -r major minor patch <<< "$base" + version="${major}.${minor}.$((patch + 1))" + fi + + printf "parameters:\\n app.version: '%s'\\n" "$version" > config/version.yaml + git config user.name "gitea-actions" + git config user.email "gitea-actions@local" + git add config/version.yaml + git commit -m "chore: bump version to v$version" || true + git push origin develop || true + fi + + tag="v$version" + git tag "$tag" + git push origin "$tag" diff --git a/.gitea/workflows/release-artefact.yml b/.gitea/workflows/release-artefact.yml new file mode 100644 index 0000000..faae28c --- /dev/null +++ b/.gitea/workflows/release-artefact.yml @@ -0,0 +1,65 @@ +name: Build Release Artefact + +on: + push: + tags: + - "v*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.4" + extensions: mbstring, intl, pdo_pgsql, xml, curl, zip, gd + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install backend deps (prod) + env: + APP_ENV: prod + APP_DEBUG: "0" + run: composer install --no-dev --optimize-autoloader --no-interaction --no-scripts + + - name: Build frontend (static) + run: | + cd frontend + npm ci + CI=1 NUXT_TELEMETRY_DISABLED=1 NUXT_PUBLIC_API_BASE=/api NUXT_PUBLIC_APP_BASE=/ npm run generate + test -f .output/public/index.html + + - name: Build artefact + shell: bash + run: | + set -euo pipefail + mkdir -p release + tar -czf "release/lesstime-${GITHUB_REF_NAME}.tar.gz" \ + bin \ + config \ + migrations \ + public \ + src \ + templates \ + vendor \ + composer.json \ + composer.lock \ + symfony.lock \ + frontend/.output + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + files: release/lesstime-${{ github.ref_name }}.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..91bbed8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,93 @@ +# Lesstime + +Application de gestion de projet. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. + +## Stack + +- **Backend** : PHP 8.4, Symfony 8.0, API Platform 4, Doctrine ORM, PostgreSQL 16 +- **Frontend** : Nuxt 4 (SSR off / SPA), Vue 3, Pinia, Tailwind CSS, nuxt-toast, @nuxtjs/i18n, @nuxt/icon +- **Auth** : JWT HTTP-only cookie (lexik/jwt-authentication-bundle), login à `/login_check`, cookie `BEARER` +- **Docker** : PHP-FPM + Node 24, Nginx (port 8082), PostgreSQL (port 5435) + +## Structure + +``` +src/Entity/ # Entités Doctrine +src/ApiResource/ # Ressources API Platform (si découplées des entités) +src/State/ # Providers et Processors API Platform +src/Repository/ # Repositories Doctrine +src/DataFixtures/ # Fixtures +config/ # Config Symfony (security, api_platform, lexik_jwt, nelmio_cors, doctrine) +config/jwt/ # Clés JWT (private.pem, public.pem) +migrations/ # Migrations Doctrine +frontend/ # App Nuxt 4 +frontend/pages/ # Pages +frontend/layouts/ # Layouts (pas "layout") +frontend/components/ # Composants Vue +frontend/composables/# Composables (useApi, etc.) +frontend/stores/ # Stores Pinia +frontend/services/ # Services API (auth, etc.) +frontend/services/dto/ # Types TypeScript +frontend/i18n/locales/ # Fichiers de traduction (langDir résolu depuis i18n/) +``` + +## Commandes + +```bash +make start # Démarrer les containers +make install # Install complet (composer, migrations, fixtures, build Nuxt) +make reset # Tout supprimer et réinstaller (supprime la BDD) +make dev-nuxt # Dev server Nuxt (hot reload, port 3002) +make shell # Shell dans le container PHP +make migration-migrate # Lancer les migrations +make fixtures # Charger les fixtures +make db-reset # Reset BDD + migrations + fixtures +make test # PHPUnit +make php-cs-fixer-allow-risky # Fix code style PHP +make logs-dev # Tail logs Symfony +``` + +## Conventions + +### Commits + +Format : `() : ` (espace avant et après `:`) + +Types autorisés (minuscules) : `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test` + +Exemples : `feat : add login page`, `fix(auth) : prevent null token crash` + +### Backend + +- Toujours `declare(strict_types=1)` en haut des fichiers PHP +- API Platform : utiliser ApiResource, Providers (`src/State/`), Processors — pas de controllers +- Routes API préfixées `/api` (via `config/routes/api_platform.yaml`) +- Le login (`/login_check`) est hors prefix `/api`, nginx réécrit `REQUEST_URI` vers `/login_check` +- PHP CS Fixer : règles Symfony + PSR-12 + strict types + +### Frontend + +- TypeScript strict +- Composable `useApi()` pour tous les appels API (gère cookies, erreurs, toasts, i18n) +- Store Pinia pour l'auth (`useAuthStore`) +- Middleware global `auth.global.ts` protège les routes +- Traductions dans `frontend/i18n/locales/` (le module résout `langDir` depuis `i18n/`) +- 4 espaces d'indentation + +### Nginx + +- `/api/*` → Symfony (via try_files + index.php) +- `/api/login_check` → location exact match, fastcgi direct avec REQUEST_URI réécrit en `/login_check` +- `/` → SPA frontend (`frontend/dist/`) + +## Docker + +- Container PHP : `php-lesstime-fpm` +- Container Nginx : `nginx-lesstime` +- Container DB : PostgreSQL sur port **5435** (interne et externe) +- Config Docker : `docker/.env.docker` (override local : `docker/.env.docker.local`) +- Après modif nginx : `docker restart nginx-lesstime` + +## Fixtures + +- User admin : `admin` / `admin` (ROLE_ADMIN) diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..303a6d3 --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1 @@ +@malio:registry=https://gitea.malio.fr/api/packages/MALIO-DEV/npm/ diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index 16fe618..d7aed0e 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -7,12 +7,13 @@ export default defineNuxtConfig({ ? (process.env.NUXT_PUBLIC_APP_BASE || '/') : '/' }, + extends: ['@malio/layer-ui'], modules: [ '@nuxtjs/tailwindcss', '@pinia/nuxt', 'nuxt-toast', '@nuxtjs/i18n', - '@nuxt/icon' + '@nuxt/icon', ], runtimeConfig: { public: { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 95da8cb..f3f4a95 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,6 +7,7 @@ "name": "nuxt-app", "hasInstallScript": true, "dependencies": { + "@malio/layer-ui": "^1.1.0", "@nuxt/icon": "^2.2.1", "@nuxtjs/i18n": "^10.2.3", "@nuxtjs/tailwindcss": "^6.14.0", @@ -2123,6 +2124,20 @@ "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "license": "MIT" }, + "node_modules/@malio/layer-ui": { + "version": "1.1.0", + "resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.1.0/layer-ui-1.1.0.tgz", + "integrity": "sha512-mc+kOK+EDfo6ZZcE0/FaVnvDyIDJrigkgOzvL8rxnpljXEiRlKj5673e5e6ZIoOyKFqktzbJXzFr4V6UBD0wPg==", + "dependencies": { + "@nuxt/icon": "^2.2.1", + "@nuxtjs/tailwindcss": "^6.14.0", + "maska": "^3.2.0", + "tailwind-merge": "^3.3.1" + }, + "peerDependencies": { + "nuxt": "^4.0.0" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", @@ -9483,6 +9498,12 @@ "source-map-js": "^1.2.1" } }, + "node_modules/maska": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/maska/-/maska-3.2.0.tgz", + "integrity": "sha512-zSmSgs5/q9vMSmrdZT3rKOv9uLznNWR/niuuAdBZDTvB3SMKOX9vhMtDijFyExz+B4UClu2rvksylUh/ea1bLA==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -12424,6 +12445,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", diff --git a/frontend/package.json b/frontend/package.json index 88a47b8..ed797d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist" }, "dependencies": { + "@malio/layer-ui": "^1.1.0", "@nuxt/icon": "^2.2.1", "@nuxtjs/i18n": "^10.2.3", "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/frontend/pages/login.vue b/frontend/pages/login.vue index 6aa78fa..a87da44 100644 --- a/frontend/pages/login.vue +++ b/frontend/pages/login.vue @@ -9,18 +9,13 @@ class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm" @submit.prevent="handleSubmit" > -
- - -
+