From 4d925ccb0ba07f7501279f9d107f2d1ffaf6a633 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Sun, 11 May 2025 21:45:07 +0100 Subject: [PATCH 01/22] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Update=20.gitigno?= =?UTF-8?q?re=20to=20exclude=20memory-bank=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4e9b445..4e1ef74 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ yarn-error.log # Platform specific .DS_Store +memory-bank/ \ No newline at end of file From e9d0afadd7add223c1bf332725f1d7b5c5ae0fee Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Sun, 27 Apr 2025 16:20:40 +0100 Subject: [PATCH 02/22] =?UTF-8?q?=E2=9C=A8=20Update=20Environment=20Config?= =?UTF-8?q?uration=20and=20Logger=20Setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add GitHub OAuth credentials to .env.example - Introduce MySQL connection settings in .env.example - Update logger configuration by removing unnecessary transport settings for improved clarity - Exclude memory_bank/ directory from .gitignore --- .env.example | 12 +++-- .github/workflows/deploy.dev.yml | 69 +++++++++++++++++++++++++++++ .gitignore | 2 +- Dockerfile.dev | 36 +++++++++++++++ compose.dev.yaml | 75 ++++++++++++++++++++++++++++++++ config/logger.ts | 9 +--- memory-bank/.cursorrules | 25 +++++++++++ memory-bank/activeContext.md | 17 ++++++++ memory-bank/productContext.md | 17 ++++++++ memory-bank/progress.md | 17 ++++++++ memory-bank/projectbrief.md | 13 ++++++ memory-bank/systemPatterns.md | 17 ++++++++ memory-bank/techContext.md | 17 ++++++++ 13 files changed, 314 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/deploy.dev.yml create mode 100644 Dockerfile.dev create mode 100644 compose.dev.yaml create mode 100644 memory-bank/.cursorrules create mode 100644 memory-bank/activeContext.md create mode 100644 memory-bank/productContext.md create mode 100644 memory-bank/progress.md create mode 100644 memory-bank/projectbrief.md create mode 100644 memory-bank/systemPatterns.md create mode 100644 memory-bank/techContext.md diff --git a/.env.example b/.env.example index 25b1a45..768bd8f 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,12 @@ DB_PORT=3306 DB_USER=root DB_PASSWORD=root DB_DATABASE=app -GITHUB_CLIENT_ID= -GITHUB_CLIENT_SECRET= -GITHUB_CALLBACK_URL= \ No newline at end of file +GITHUB_CLIENT_ID=e +GITHUB_CLIENT_SECRET=e +GITHUB_CALLBACK_URL=e +DB_CONNECTION=mysqle +MYSQL_HOST=localhost +MYSQL_PORT=3306 +MYSQL_USER=root +MYSQL_PASSWORD= +MYSQL_DB_NAME=javascript_cm \ No newline at end of file diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml new file mode 100644 index 0000000..59630af --- /dev/null +++ b/.github/workflows/deploy.dev.yml @@ -0,0 +1,69 @@ +name: Déploiement sur VPS (dev) + +on: + push: + branches: [main] + workflow_dispatch: + +env: + APP_NAME: ${{ github.event.repository.name }} + APP_PATH: /home/apps/${{ github.event.repository.name }} + NODE_ENV: dev + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Vérifier le code + uses: actions/checkout@v4 + + - name: Créer le dossier de l'application sur le VPS + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USERNAME }} + key: ${{ secrets.VPS_SSH_KEY }} + port: ${{ secrets.VPS_PORT }} + script: | + mkdir -p ${{ env.APP_PATH }} + cd ${{ env.APP_PATH }} + if [ -d "app" ]; then + rm -rf app + fi + mkdir -p app + + - name: Copier les fichiers de l'application + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USERNAME }} + key: ${{ secrets.VPS_SSH_KEY }} + port: ${{ secrets.VPS_PORT }} + source: "." + target: "${{ env.APP_PATH }}/app" + strip_components: 0 + + - name: Mettre à jour le docker-compose avec le nom de l'application + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USERNAME }} + key: ${{ secrets.VPS_SSH_KEY }} + port: ${{ secrets.VPS_PORT }} + script: | + cd "${{ env.APP_PATH }}/app" + sed -i "1s/^/name: ${{ env.APP_NAME }}\n/" compose.${{ env.NODE_ENV }}.yaml + + - name: Construire et démarrer les conteneurs Docker + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USERNAME }} + key: ${{ secrets.VPS_SSH_KEY }} + port: ${{ secrets.VPS_PORT }} + script: | + cd "${{ env.APP_PATH }}/app" + docker compose -f compose.${{ env.NODE_ENV }}.yaml down || true + docker compose -f compose.${{ env.NODE_ENV }}.yaml build --no-cache + docker compose -f compose.${{ env.NODE_ENV }}.yaml up -d + docker system prune -f diff --git a/.gitignore b/.gitignore index 4e1ef74..e0d6a14 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ yarn-error.log # Platform specific .DS_Store -memory-bank/ \ No newline at end of file +memory-bank/ diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..452e38c --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,36 @@ +ARG NODE_IMAGE=node:20.14.0-bookworm-slim + +FROM $NODE_IMAGE AS base + +# All deps stage +FROM base AS deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +# Production only deps stage +FROM base AS production-deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci --omit=dev + +# Build stage +FROM base AS build +WORKDIR /app +COPY --from=deps /app/node_modules /app/node_modules +COPY . . +RUN node ace build + +# Production stage +FROM base +ENV NODE_ENV=production +WORKDIR /app +COPY --from=production-deps /app/node_modules /app/node_modules +COPY --from=build /app/build /app + + +RUN npm i -g @redocly/cli@latest + +RUN apt-get update && apt-get install -y default-mysql-client && rm -rf /var/lib/apt/lists/* + +EXPOSE $PORT diff --git a/compose.dev.yaml b/compose.dev.yaml new file mode 100644 index 0000000..5f28110 --- /dev/null +++ b/compose.dev.yaml @@ -0,0 +1,75 @@ +services: + mysql: + image: mysql:8 + container_name: jscm_mysql + restart: always + ports: + - "${MYSQL_PORT:-3306}:3306" + env_file: + - .env + volumes: + - jscm_mysql_volume:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} + - MYSQL_DATABASE=${MYSQL_DB_NAME} + - MYSQL_USER=${MYSQL_USER} + # - MYSQL_PASSWORD=${MYSQL_PASSWORD} + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + retries: 3 + timeout: 5s + networks: + - adonis_jscm + + api: + container_name: jscm_api + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile.dev + ports: + - ${PORT:-3333}:3333 + env_file: + - .env + environment: + - TZ=${TZ} + - PORT=${PORT} + - HOST=${HOST} + - LOG_LEVEL=${LOG_LEVEL} + - APP_KEY=${APP_KEY} + - SESSION_DRIVER=${SESSION_DRIVER} + - DB_CONNECTION=${DB_CONNECTION} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_DATABASE=${DB_DATABASE} + - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} + - GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL} + - MYSQL_HOST=${MYSQL_HOST} + - MYSQL_PORT=${MYSQL_PORT} + - MYSQL_USER=${MYSQL_USER} + - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - MYSQL_DB_NAME=${MYSQL_DB_NAME} + - MYSQL_DATABASE=${MYSQL_DB_NAME} + + volumes: + - jscm_uploads:/app/uploads + - jscm_public:/app/public + command: sh -c "node ace migration:fresh --force && node ace convert:csv-to-json && node ace db:seed && node ace seed:test-data && npm run gen-docs && npm run start" + depends_on: + mysql: + condition: service_healthy + networks: + - adonis_jscm + + +volumes: + jscm_mysql_volume: + jscm_uploads: + jscm_public: + +networks: + adonis_jscm: + driver: bridge diff --git a/config/logger.ts b/config/logger.ts index b961300..5a8d6b8 100644 --- a/config/logger.ts +++ b/config/logger.ts @@ -1,6 +1,5 @@ import env from '#start/env' -import app from '@adonisjs/core/services/app' -import { defineConfig, targets } from '@adonisjs/core/logger' +import { defineConfig } from '@adonisjs/core/logger' const loggerConfig = defineConfig({ default: 'app', @@ -14,12 +13,6 @@ const loggerConfig = defineConfig({ enabled: true, name: env.get('APP_NAME'), level: env.get('LOG_LEVEL'), - transport: { - targets: targets() - .pushIf(!app.inProduction, targets.pretty()) - .pushIf(app.inProduction, targets.file({ destination: 1 })) - .toArray(), - }, }, }, }) diff --git a/memory-bank/.cursorrules b/memory-bank/.cursorrules new file mode 100644 index 0000000..e5251dc --- /dev/null +++ b/memory-bank/.cursorrules @@ -0,0 +1,25 @@ +# .cursorrules + +## Schémas de mise en œuvre critiques + +(Décrivez les schémas importants découverts dans le projet.) + +## Préférences de l'utilisateur et flux de travail + +(Notez les préférences et les flux de travail pertinents pour l'utilisateur.) + +## Schémas spécifiques au projet + +(Décrivez les schémas uniques ou récurrents dans ce projet.) + +## Défis connus + +(Liste des défis techniques ou de processus identifiés.) + +## Évolution des décisions du projet + +(Enregistrez comment les décisions importantes ont évolué.) + +## Schémas d'utilisation des outils + +(Observations sur l'utilisation des outils et des processus dans le projet.) \ No newline at end of file diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md new file mode 100644 index 0000000..71c6daf --- /dev/null +++ b/memory-bank/activeContext.md @@ -0,0 +1,17 @@ +# activeContext.md + +## Focus de travail actuel + +(Décrivez sur quoi on travaille actuellement.) + +## Changements récents + +(Résumez les changements récents apportés au projet.) + +## Prochaines étapes + +(Liste des prochaines étapes ou tâches à accomplir.) + +## Décisions et considérations actives + +(Notez les décisions récentes et les considérations importantes en cours.) \ No newline at end of file diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md new file mode 100644 index 0000000..afdc22b --- /dev/null +++ b/memory-bank/productContext.md @@ -0,0 +1,17 @@ +# productContext.md + +## Pourquoi ce projet existe-t-il ? + +(Expliquez la raison d'être du projet.) + +## Problèmes résolus + +(Décrivez les problèmes ou besoins auxquels le projet répond.) + +## Comment doit-il fonctionner ? + +(Expliquez comment le produit est censé fonctionner.) + +## Objectifs d'expérience utilisateur + +(Définissez les objectifs clés de l'expérience utilisateur.) \ No newline at end of file diff --git a/memory-bank/progress.md b/memory-bank/progress.md new file mode 100644 index 0000000..d61defc --- /dev/null +++ b/memory-bank/progress.md @@ -0,0 +1,17 @@ +# progress.md + +## Ce qui fonctionne + +(Décrivez les parties du projet qui fonctionnent déjà.) + +## Ce qu'il reste à construire + +(Liste des fonctionnalités ou tâches restantes.) + +## État actuel + +(Résumé de l'état actuel du projet.) + +## Problèmes connus + +(Liste des problèmes ou bugs identifiés.) \ No newline at end of file diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md new file mode 100644 index 0000000..e5fbee2 --- /dev/null +++ b/memory-bank/projectbrief.md @@ -0,0 +1,13 @@ +# projectbrief.md + +## Objectif du projet + +(Décrivez ici l'objectif principal du projet, pourquoi il existe et quel problème il résout.) + +## Portée du projet + +(Définissez la portée, les limites et les livrables principaux du projet.) + +## Exigences clés + +(Liste des exigences essentielles pour le succès du projet.) \ No newline at end of file diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md new file mode 100644 index 0000000..022b683 --- /dev/null +++ b/memory-bank/systemPatterns.md @@ -0,0 +1,17 @@ +# systemPatterns.md + +## Architecture du système + +(Décrivez l'architecture générale du système.) + +## Décisions techniques clés + +(Liste des décisions techniques importantes prises.) + +## Modèles de conception utilisés + +(Décrivez les modèles de conception appliqués dans le projet.) + +## Relations entre les composants + +(Expliquez comment les principaux composants du système sont liés.) \ No newline at end of file diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md new file mode 100644 index 0000000..2ff5250 --- /dev/null +++ b/memory-bank/techContext.md @@ -0,0 +1,17 @@ +# techContext.md + +## Technologies utilisées + +(Liste des principales technologies utilisées dans le projet.) + +## Configuration du développement + +(Décrivez comment configurer l'environnement de développement.) + +## Contraintes techniques + +(Notez les contraintes techniques pertinentes.) + +## Dépendances + +(Liste des dépendances clés du projet.) \ No newline at end of file From d6889a58332954b24d90d0c1cb371bf8accab245 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Sun, 27 Apr 2025 16:27:01 +0100 Subject: [PATCH 03/22] =?UTF-8?q?=E2=9C=A8=20Update=20Deployment=20Workflo?= =?UTF-8?q?w=20to=20Include=20Environment=20Variable=20Setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change NODE_ENV to ENV in the deployment workflow for consistency - Add a step to create a .env file with necessary application and database configurations - Update Docker compose commands to reference the new ENV variable --- .github/workflows/deploy.dev.yml | 44 ++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index 59630af..3a84884 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -8,7 +8,7 @@ on: env: APP_NAME: ${{ github.event.repository.name }} APP_PATH: /home/apps/${{ github.event.repository.name }} - NODE_ENV: dev + ENV: dev jobs: deploy: @@ -31,6 +31,40 @@ jobs: rm -rf app fi mkdir -p app + + - name: Ecrire le fichier .env + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USERNAME }} + key: ${{ secrets.VPS_SSH_KEY }} + port: ${{ secrets.VPS_PORT }} + script: | + cd ${{ env.APP_PATH }}/app + echo "NODE_ENV=development" > .env + echo "APP_NAME=${{ env.APP_NAME }}" >> .env + echo "APP_PATH=${{ env.APP_PATH }}" >> .env + echo "TZ=UTC" >> .env + echo "PORT=3306" >> .env + echo "HOST=0.0.0.0" >> .env + echo "LOG_LEVEL=INFO" >> .env + echo "APP_KEY=appkey" >> .env + echo "SESSION_DRIVER=cookie" >> .env + echo "DB_CONNECTION=mysql" >> .env + echo "DB_HOST=mysql" >> .env + echo "DB_PORT=3306" >> .env + echo "DB_USER=root" >> .env + echo "DB_PASSWORD=root" >> .env + echo "DB_DATABASE=jscm" >> .env + echo "MYSQL_HOST=mysql" >> .env + echo "MYSQL_PORT=3306" >> .env + echo "MYSQL_USER=root" >> .env + echo "MYSQL_PASSWORD=root" >> .env + echo "MYSQL_DB_NAME=jscm" >> .env + echo "MYSQL_DATABASE=jscm" >> .env + echo "GITHUB_CLIENT_ID=github_client_id" >> .env + echo "GITHUB_CLIENT_SECRET=github_client_secret" >> .env + echo "GITHUB_CALLBACK_URL=http://0.0.0.0:3333/auth/github/callback" >> .env - name: Copier les fichiers de l'application uses: appleboy/scp-action@v0.1.7 @@ -52,7 +86,7 @@ jobs: port: ${{ secrets.VPS_PORT }} script: | cd "${{ env.APP_PATH }}/app" - sed -i "1s/^/name: ${{ env.APP_NAME }}\n/" compose.${{ env.NODE_ENV }}.yaml + sed -i "1s/^/name: ${{ env.APP_NAME }}\n/" compose.${{ env.ENV }}.yaml - name: Construire et démarrer les conteneurs Docker uses: appleboy/ssh-action@v1.0.3 @@ -63,7 +97,7 @@ jobs: port: ${{ secrets.VPS_PORT }} script: | cd "${{ env.APP_PATH }}/app" - docker compose -f compose.${{ env.NODE_ENV }}.yaml down || true - docker compose -f compose.${{ env.NODE_ENV }}.yaml build --no-cache - docker compose -f compose.${{ env.NODE_ENV }}.yaml up -d + docker compose -f compose.${{ env.ENV }}.yaml down || true + docker compose -f compose.${{ env.ENV }}.yaml build --no-cache + docker compose -f compose.${{ env.ENV }}.yaml up -d docker system prune -f From 9e74719a05b403954c9308fa82b723a9698567d7 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Sun, 27 Apr 2025 17:53:12 +0100 Subject: [PATCH 04/22] =?UTF-8?q?=E2=9C=A8=20Update=20Docker=20and=20Deplo?= =?UTF-8?q?yment=20Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modify Docker Compose to use new environment variable names for database configuration - Add initialization SQL script to the MySQL service in Docker Compose - Update Dockerfile to use npm install with force for dependency installation - Adjust deployment workflow to dynamically set database names in the .env file - Remove outdated migration files related to articles --- .github/workflows/deploy.dev.yml | 7 ++++--- Dockerfile.dev | 4 ++-- compose.dev.yaml | 21 +++++++------------ database/init.sql | 1 + ...=> 1736839410989_create_articles_table.ts} | 1 + ...1736839410999_add_author_id_to_articles.ts | 17 --------------- 6 files changed, 16 insertions(+), 35 deletions(-) create mode 100644 database/init.sql rename database/migrations/{17368394109899_create_articles_table.ts => 1736839410989_create_articles_table.ts} (87%) delete mode 100644 database/migrations/1736839410999_add_author_id_to_articles.ts diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index 3a84884..919ba74 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -9,6 +9,7 @@ env: APP_NAME: ${{ github.event.repository.name }} APP_PATH: /home/apps/${{ github.event.repository.name }} ENV: dev + DATABASE_NAME: javascript_cm jobs: deploy: @@ -55,13 +56,13 @@ jobs: echo "DB_PORT=3306" >> .env echo "DB_USER=root" >> .env echo "DB_PASSWORD=root" >> .env - echo "DB_DATABASE=jscm" >> .env + echo "DB_DATABASE=${{ env.DATABASE_NAME }}" >> .env echo "MYSQL_HOST=mysql" >> .env echo "MYSQL_PORT=3306" >> .env echo "MYSQL_USER=root" >> .env echo "MYSQL_PASSWORD=root" >> .env - echo "MYSQL_DB_NAME=jscm" >> .env - echo "MYSQL_DATABASE=jscm" >> .env + echo "MYSQL_DB_NAME=${{ env.DATABASE_NAME }}" >> .env + echo "MYSQL_DATABASE=${{ env.DATABASE_NAME }}" >> .env echo "GITHUB_CLIENT_ID=github_client_id" >> .env echo "GITHUB_CLIENT_SECRET=github_client_secret" >> .env echo "GITHUB_CALLBACK_URL=http://0.0.0.0:3333/auth/github/callback" >> .env diff --git a/Dockerfile.dev b/Dockerfile.dev index 452e38c..935d562 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -6,13 +6,13 @@ FROM $NODE_IMAGE AS base FROM base AS deps WORKDIR /app COPY package*.json ./ -RUN npm ci +RUN npm install --force # Production only deps stage FROM base AS production-deps WORKDIR /app COPY package*.json ./ -RUN npm ci --omit=dev +RUN npm install --force # Build stage FROM base AS build diff --git a/compose.dev.yaml b/compose.dev.yaml index 5f28110..d5f999b 100644 --- a/compose.dev.yaml +++ b/compose.dev.yaml @@ -4,20 +4,22 @@ services: container_name: jscm_mysql restart: always ports: - - "${MYSQL_PORT:-3306}:3306" + - "${DB_PORT:-3306}:3306" env_file: - .env volumes: - jscm_mysql_volume:/var/lib/mysql + - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_DATABASE=${MYSQL_DB_NAME} - - MYSQL_USER=${MYSQL_USER} - # - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} + - MYSQL_DATABASE=${DB_DATABASE} + # - MYSQL_USER=${DB_USER} healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] retries: 3 timeout: 5s + networks: - adonis_jscm @@ -47,17 +49,10 @@ services: - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} - GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL} - - MYSQL_HOST=${MYSQL_HOST} - - MYSQL_PORT=${MYSQL_PORT} - - MYSQL_USER=${MYSQL_USER} - - MYSQL_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_DB_NAME=${MYSQL_DB_NAME} - - MYSQL_DATABASE=${MYSQL_DB_NAME} - volumes: - jscm_uploads:/app/uploads - jscm_public:/app/public - command: sh -c "node ace migration:fresh --force && node ace convert:csv-to-json && node ace db:seed && node ace seed:test-data && npm run gen-docs && npm run start" + command: sh -c "node ace migration:fresh --force && node ace db:seed && npm run start" depends_on: mysql: condition: service_healthy diff --git a/database/init.sql b/database/init.sql new file mode 100644 index 0000000..2abf8ee --- /dev/null +++ b/database/init.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS DATABASE javascript_cm; \ No newline at end of file diff --git a/database/migrations/17368394109899_create_articles_table.ts b/database/migrations/1736839410989_create_articles_table.ts similarity index 87% rename from database/migrations/17368394109899_create_articles_table.ts rename to database/migrations/1736839410989_create_articles_table.ts index ce3cae4..c900276 100644 --- a/database/migrations/17368394109899_create_articles_table.ts +++ b/database/migrations/1736839410989_create_articles_table.ts @@ -11,6 +11,7 @@ export default class extends BaseSchema { table.text('excerpt').notNullable() table.text('content').notNullable() table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE') + table.integer('author_id').unsigned().references('id').inTable('users').onDelete('CASCADE') table.boolean('is_published').defaultTo(false) table.timestamp('published_at').nullable() table.timestamps(true, true) diff --git a/database/migrations/1736839410999_add_author_id_to_articles.ts b/database/migrations/1736839410999_add_author_id_to_articles.ts deleted file mode 100644 index 6ee3326..0000000 --- a/database/migrations/1736839410999_add_author_id_to_articles.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { BaseSchema } from '@adonisjs/lucid/schema' - -export default class extends BaseSchema { - protected tableName = 'articles' - - async up() { - this.schema.alterTable(this.tableName, (table) => { - table.integer('author_id').unsigned().references('id').inTable('users').onDelete('CASCADE') - }) - } - - async down() { - this.schema.alterTable(this.tableName, (table) => { - table.dropColumn('author_id') - }) - } -} From 61b1ab878c73871b4d054905b265a0811f9d4510 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 10:27:38 +0100 Subject: [PATCH 05/22] =?UTF-8?q?=E2=9C=A8=20Update=20Docker=20Configurati?= =?UTF-8?q?on=20and=20Remove=20Unused=20Files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new file watching patterns for images in adonisrc.ts - Remove obsolete Dockerfile.dev and compose.dev.yaml files - Update deployment workflow to reference the new compose.yaml - Enhance active and progress documentation in memory bank to reflect current project state and architecture --- .github/workflows/deploy.dev.yml | 14 ++--- adonisrc.ts | 8 +++ compose.dev.yaml | 70 ------------------------ compose.yaml | 67 +++++++++++++++++++++++ docker/mysql/Dockerfile | 16 ++++++ docker/mysql/init.sh | 12 ++++ Dockerfile.dev => docker/node/Dockerfile | 23 +++----- env.txt | 0 memory-bank/.cursorrules | 34 ++++++++++-- memory-bank/activeContext.md | 20 +++++-- memory-bank/progress.md | 20 +++++-- memory-bank/systemPatterns.md | 24 ++++++-- memory-bank/techContext.md | 31 +++++++++-- 13 files changed, 225 insertions(+), 114 deletions(-) delete mode 100644 compose.dev.yaml create mode 100644 compose.yaml create mode 100644 docker/mysql/Dockerfile create mode 100644 docker/mysql/init.sh rename Dockerfile.dev => docker/node/Dockerfile (56%) create mode 100644 env.txt diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index 919ba74..b45714e 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -6,9 +6,8 @@ on: workflow_dispatch: env: - APP_NAME: ${{ github.event.repository.name }} - APP_PATH: /home/apps/${{ github.event.repository.name }} - ENV: dev + APP_NAME: ${{ github.event.repository.name }}.dev + APP_PATH: /home/apps/${{ github.event.repository.name }}.dev DATABASE_NAME: javascript_cm jobs: @@ -87,7 +86,7 @@ jobs: port: ${{ secrets.VPS_PORT }} script: | cd "${{ env.APP_PATH }}/app" - sed -i "1s/^/name: ${{ env.APP_NAME }}\n/" compose.${{ env.ENV }}.yaml + sed -i "1s/^/name: ${{ env.APP_NAME }}\n/" compose.yaml - name: Construire et démarrer les conteneurs Docker uses: appleboy/ssh-action@v1.0.3 @@ -98,7 +97,8 @@ jobs: port: ${{ secrets.VPS_PORT }} script: | cd "${{ env.APP_PATH }}/app" - docker compose -f compose.${{ env.ENV }}.yaml down || true - docker compose -f compose.${{ env.ENV }}.yaml build --no-cache - docker compose -f compose.${{ env.ENV }}.yaml up -d + docker compose -f compose.yaml down || true + docker compose -f compose.yaml build --no-cache + docker compose -f compose.yaml up -d + docker system prune -f diff --git a/adonisrc.ts b/adonisrc.ts index 5235734..963e25c 100644 --- a/adonisrc.ts +++ b/adonisrc.ts @@ -90,6 +90,14 @@ export default defineConfig({ pattern: 'resources/views/**/*.edge', reloadServer: false, }, + { + pattern: 'resources/images/**/*', + reloadServer: false, + }, + { + pattern: 'resources/**/*.{png,jpg,jpeg,gif,svg,ico}', + reloadServer: false, + }, { pattern: 'public/**', reloadServer: false, diff --git a/compose.dev.yaml b/compose.dev.yaml deleted file mode 100644 index d5f999b..0000000 --- a/compose.dev.yaml +++ /dev/null @@ -1,70 +0,0 @@ -services: - mysql: - image: mysql:8 - container_name: jscm_mysql - restart: always - ports: - - "${DB_PORT:-3306}:3306" - env_file: - - .env - volumes: - - jscm_mysql_volume:/var/lib/mysql - - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro - - environment: - - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - - MYSQL_DATABASE=${DB_DATABASE} - # - MYSQL_USER=${DB_USER} - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - retries: 3 - timeout: 5s - - networks: - - adonis_jscm - - api: - container_name: jscm_api - restart: unless-stopped - build: - context: . - dockerfile: Dockerfile.dev - ports: - - ${PORT:-3333}:3333 - env_file: - - .env - environment: - - TZ=${TZ} - - PORT=${PORT} - - HOST=${HOST} - - LOG_LEVEL=${LOG_LEVEL} - - APP_KEY=${APP_KEY} - - SESSION_DRIVER=${SESSION_DRIVER} - - DB_CONNECTION=${DB_CONNECTION} - - DB_HOST=${DB_HOST} - - DB_PORT=${DB_PORT} - - DB_USER=${DB_USER} - - DB_PASSWORD=${DB_PASSWORD} - - DB_DATABASE=${DB_DATABASE} - - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} - - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} - - GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL} - volumes: - - jscm_uploads:/app/uploads - - jscm_public:/app/public - command: sh -c "node ace migration:fresh --force && node ace db:seed && npm run start" - depends_on: - mysql: - condition: service_healthy - networks: - - adonis_jscm - - -volumes: - jscm_mysql_volume: - jscm_uploads: - jscm_public: - -networks: - adonis_jscm: - driver: bridge diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..609fde0 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,67 @@ +version: '3.8' + +services: + app: + build: + context: . + dockerfile: docker/node/Dockerfile + container_name: jscm-app + env_file: + - .env + ports: + - "3333:3333" + volumes: + - .:/app + - /app/node_modules + environment: + - NODE_ENV=development + - DB_HOST=mysql + - DB_PORT=3306 + - DB_DATABASE=${DB_DATABASE} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + depends_on: + - mysql + command: sh -c "npm install && npm run dev" + + mysql: + build: + context: . + dockerfile: docker/mysql/Dockerfile + container_name: jscm-mysql + env_file: + - .env + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} + - MYSQL_DATABASE=${DB_DATABASE} + - MYSQL_USER=${DB_USERNAME} + - MYSQL_PASSWORD=${DB_PASSWORD} + volumes: + - mysql_data:/var/lib/mysql + + migrations: + build: + context: . + dockerfile: docker/node/Dockerfile + target: deps + container_name: jscm-migrations + env_file: + - .env + volumes: + - .:/app + - /app/node_modules + environment: + - NODE_ENV=development + - DB_HOST=mysql + - DB_PORT=3306 + - DB_DATABASE=${DB_DATABASE} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + depends_on: + - mysql + command: sh -c "node ace migration:run" + +volumes: + mysql_data: \ No newline at end of file diff --git a/docker/mysql/Dockerfile b/docker/mysql/Dockerfile new file mode 100644 index 0000000..772813b --- /dev/null +++ b/docker/mysql/Dockerfile @@ -0,0 +1,16 @@ +FROM mysql:8.0 + +# Créer le répertoire de travail +WORKDIR /app + +# Copier le script d'initialisation +COPY docker/mysql/init.sh /docker-entrypoint-initdb.d/ + +# Donner les permissions d'exécution au script +RUN chmod +x /docker-entrypoint-initdb.d/init.sh + +# Exposer le port MySQL +EXPOSE 3306 + +# Commande par défaut +CMD ["mysqld"] \ No newline at end of file diff --git a/docker/mysql/init.sh b/docker/mysql/init.sh new file mode 100644 index 0000000..7a490b3 --- /dev/null +++ b/docker/mysql/init.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Attendre que MySQL soit prêt +until mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SELECT 1"; do + echo "Waiting for MySQL to be ready..." + sleep 2 +done + +# Créer la base de données si elle n'existe pas +mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;" + +echo "Database initialization completed!" \ No newline at end of file diff --git a/Dockerfile.dev b/docker/node/Dockerfile similarity index 56% rename from Dockerfile.dev rename to docker/node/Dockerfile index 935d562..4cee8c7 100644 --- a/Dockerfile.dev +++ b/docker/node/Dockerfile @@ -1,24 +1,22 @@ -ARG NODE_IMAGE=node:20.14.0-bookworm-slim - -FROM $NODE_IMAGE AS base +FROM node:20.12.2-alpine3.18 AS base # All deps stage FROM base AS deps WORKDIR /app -COPY package*.json ./ -RUN npm install --force +ADD package.json package-lock.json ./ +RUN npm ci # Production only deps stage FROM base AS production-deps WORKDIR /app -COPY package*.json ./ -RUN npm install --force +ADD package.json package-lock.json ./ +RUN npm ci --omit=dev # Build stage FROM base AS build WORKDIR /app COPY --from=deps /app/node_modules /app/node_modules -COPY . . +ADD . . RUN node ace build # Production stage @@ -27,10 +25,5 @@ ENV NODE_ENV=production WORKDIR /app COPY --from=production-deps /app/node_modules /app/node_modules COPY --from=build /app/build /app - - -RUN npm i -g @redocly/cli@latest - -RUN apt-get update && apt-get install -y default-mysql-client && rm -rf /var/lib/apt/lists/* - -EXPOSE $PORT +EXPOSE 3333 +CMD ["node", "./bin/server.js"] \ No newline at end of file diff --git a/env.txt b/env.txt new file mode 100644 index 0000000..e69de29 diff --git a/memory-bank/.cursorrules b/memory-bank/.cursorrules index e5251dc..c771954 100644 --- a/memory-bank/.cursorrules +++ b/memory-bank/.cursorrules @@ -2,24 +2,46 @@ ## Schémas de mise en œuvre critiques -(Décrivez les schémas importants découverts dans le projet.) +- Utilisation de Dockerfiles multi-stage pour optimiser la taille des images et améliorer la sécurité +- Séparation des responsabilités entre les services Docker +- Utilisation de l'étape de dépendances du Dockerfile pour le service de migrations +- Configuration des variables d'environnement pour une flexibilité maximale +- Utilisation de volumes Docker pour la persistance des données et le développement en temps réel ## Préférences de l'utilisateur et flux de travail -(Notez les préférences et les flux de travail pertinents pour l'utilisateur.) +- Préférence pour une architecture basée sur des conteneurs Docker +- Utilisation de Node.js 20.12.2 pour la production +- Utilisation de MySQL 8.0 pour la base de données +- Configuration des variables d'environnement via le fichier .env +- Exécution des migrations via un service dédié ## Schémas spécifiques au projet -(Décrivez les schémas uniques ou récurrents dans ce projet.) +- Architecture en trois services : App, MySQL et Migrations +- Utilisation d'AdonisJS comme framework backend +- Configuration Docker optimisée pour le développement et la production +- Script d'initialisation pour la base de données MySQL +- Service dédié pour les migrations de base de données ## Défis connus -(Liste des défis techniques ou de processus identifiés.) +- Configuration correcte des variables d'environnement pour tous les services +- Exécution fiable des migrations de base de données +- Optimisation des images Docker pour réduire la taille et améliorer la sécurité +- Gestion des dépendances entre les services Docker ## Évolution des décisions du projet -(Enregistrez comment les décisions importantes ont évolué.) +- Passage d'une configuration simple à une architecture basée sur des conteneurs Docker +- Optimisation du Dockerfile Node.js avec un build multi-stage +- Création d'un service dédié pour les migrations de base de données +- Utilisation de l'étape de dépendances du Dockerfile pour le service de migrations ## Schémas d'utilisation des outils -(Observations sur l'utilisation des outils et des processus dans le projet.) \ No newline at end of file +- Utilisation de Docker et Docker Compose pour la conteneurisation +- Utilisation de Node.js et npm pour le développement backend +- Utilisation de MySQL pour la base de données +- Utilisation d'AdonisJS comme framework backend +- Utilisation de volumes Docker pour la persistance des données et le développement en temps réel \ No newline at end of file diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 71c6daf..8dcb5ca 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -2,16 +2,28 @@ ## Focus de travail actuel -(Décrivez sur quoi on travaille actuellement.) +Configuration de l'environnement de développement avec Docker pour le projet JSCM. Nous avons mis en place une architecture basée sur des conteneurs avec trois services principaux : l'application AdonisJS, la base de données MySQL et un service dédié pour les migrations. ## Changements récents -(Résumez les changements récents apportés au projet.) +- Création d'un Dockerfile multi-stage pour le service Node.js basé sur Node.js 20.12.2 +- Configuration d'un service MySQL avec script d'initialisation +- Mise en place d'un service dédié pour les migrations de base de données +- Optimisation de l'architecture Docker pour améliorer les performances et la fiabilité +- Utilisation de l'étape de dépendances du Dockerfile pour le service de migrations ## Prochaines étapes -(Liste des prochaines étapes ou tâches à accomplir.) +- Tester la configuration Docker complète +- Développer les fonctionnalités de l'application AdonisJS +- Mettre en place les modèles de données et les migrations +- Configurer l'authentification et les autorisations +- Développer les endpoints de l'API ## Décisions et considérations actives -(Notez les décisions récentes et les considérations importantes en cours.) \ No newline at end of file +- Utilisation de Docker pour garantir la cohérence entre les environnements +- Séparation des responsabilités entre les services +- Optimisation des images Docker pour réduire la taille et améliorer la sécurité +- Configuration des variables d'environnement pour une flexibilité maximale +- Utilisation de volumes Docker pour la persistance des données et le développement en temps réel \ No newline at end of file diff --git a/memory-bank/progress.md b/memory-bank/progress.md index d61defc..78de26a 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -2,16 +2,28 @@ ## Ce qui fonctionne -(Décrivez les parties du projet qui fonctionnent déjà.) +- Configuration de l'environnement de développement avec Docker +- Architecture basée sur des conteneurs avec trois services principaux +- Dockerfile multi-stage optimisé pour le service Node.js +- Service MySQL avec script d'initialisation +- Service dédié pour les migrations de base de données +- Configuration des variables d'environnement ## Ce qu'il reste à construire -(Liste des fonctionnalités ou tâches restantes.) +- Développement des fonctionnalités de l'application AdonisJS +- Création des modèles de données et des migrations +- Configuration de l'authentification et des autorisations +- Développement des endpoints de l'API +- Tests de la configuration Docker complète +- Déploiement en production ## État actuel -(Résumé de l'état actuel du projet.) +Le projet est en phase initiale de configuration de l'environnement de développement. L'architecture Docker est en place avec trois services principaux : l'application AdonisJS, la base de données MySQL et un service dédié pour les migrations. Les prochaines étapes consistent à développer les fonctionnalités de l'application et à tester la configuration Docker. ## Problèmes connus -(Liste des problèmes ou bugs identifiés.) \ No newline at end of file +- Aucun problème majeur identifié à ce jour +- La configuration Docker doit être testée pour vérifier son bon fonctionnement +- Les migrations de base de données doivent être créées et testées \ No newline at end of file diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index 022b683..3e1e543 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -2,16 +2,32 @@ ## Architecture du système -(Décrivez l'architecture générale du système.) +Le projet utilise une architecture basée sur des conteneurs Docker avec trois services principaux : + +1. **Service App**: Application AdonisJS qui expose l'API REST +2. **Service MySQL**: Base de données relationnelle pour le stockage des données +3. **Service Migrations**: Service dédié pour l'exécution des migrations de base de données + +Cette architecture permet une séparation claire des responsabilités et facilite le développement et le déploiement. ## Décisions techniques clés -(Liste des décisions techniques importantes prises.) +- **Utilisation de Docker**: Pour garantir la cohérence entre les environnements de développement et de production +- **Dockerfile multi-stage**: Pour optimiser la taille des images et améliorer la sécurité +- **Service de migrations dédié**: Pour garantir l'exécution correcte des migrations de base de données +- **Volumes Docker**: Pour la persistance des données et le développement en temps réel +- **Variables d'environnement**: Pour la configuration flexible des services ## Modèles de conception utilisés -(Décrivez les modèles de conception appliqués dans le projet.) +- **Architecture en conteneurs**: Séparation des services dans des conteneurs distincts +- **Pattern Builder**: Utilisation de Dockerfiles multi-stage pour la construction des images +- **Pattern Dependency Injection**: Utilisation des variables d'environnement pour l'injection de configuration +- **Pattern Service**: Services dédiés pour des fonctionnalités spécifiques (migrations) ## Relations entre les composants -(Expliquez comment les principaux composants du système sont liés.) \ No newline at end of file +- Le service App dépend du service MySQL pour le stockage des données +- Le service Migrations dépend du service MySQL pour exécuter les migrations +- Tous les services partagent les mêmes variables d'environnement via le fichier .env +- Les volumes Docker permettent la persistance des données et le partage de code \ No newline at end of file diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 2ff5250..1dc4dba 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -2,16 +2,39 @@ ## Technologies utilisées -(Liste des principales technologies utilisées dans le projet.) +- **Framework Backend**: AdonisJS (Node.js) +- **Base de données**: MySQL 8.0 +- **Conteneurisation**: Docker et Docker Compose +- **Node.js**: Version 20.12.2 (pour la production) ## Configuration du développement -(Décrivez comment configurer l'environnement de développement.) +Le projet utilise Docker pour l'environnement de développement avec trois services principaux : + +1. **Service App**: Conteneur Node.js qui exécute l'application AdonisJS + - Utilise un Dockerfile multi-stage optimisé pour la production + - Expose le port 3333 pour l'API + - Montage des volumes pour le développement en temps réel + +2. **Service MySQL**: Base de données MySQL 8.0 + - Configuration via variables d'environnement + - Persistance des données via volume Docker + - Script d'initialisation pour créer la base de données + +3. **Service Migrations**: Service dédié pour exécuter les migrations AdonisJS + - Utilise l'étape de dépendances du Dockerfile Node.js + - S'exécute après le démarrage de MySQL + - Exécute les migrations de la base de données ## Contraintes techniques -(Notez les contraintes techniques pertinentes.) +- Node.js 20.6+ requis pour AdonisJS +- MySQL 8.0 pour la base de données +- Variables d'environnement configurées dans le fichier .env ## Dépendances -(Liste des dépendances clés du projet.) \ No newline at end of file +- **AdonisJS**: Framework Node.js pour l'API +- **MySQL**: Base de données relationnelle +- **Docker**: Pour la conteneurisation +- **Docker Compose**: Pour l'orchestration des conteneurs \ No newline at end of file From 0177d084a3d2ebda089c2fbaafe1d543db691a76 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 10:38:16 +0100 Subject: [PATCH 06/22] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Remove=20Environm?= =?UTF-8?q?ent=20File?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete the env.txt file as it is no longer needed in the project structure. --- env.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 env.txt diff --git a/env.txt b/env.txt deleted file mode 100644 index e69de29..0000000 From 4bc2ff67917b640bbfad7945d2fef5ad1338b86f Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 10:57:23 +0100 Subject: [PATCH 07/22] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Remove=20Deployme?= =?UTF-8?q?nt=20Workflow=20for=20Development=20Environment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete the deploy.dev.yml file as it is no longer needed in the project structure. --- .github/workflows/{deploy.dev.yml => deploy.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{deploy.dev.yml => deploy.yml} (100%) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.yml similarity index 100% rename from .github/workflows/deploy.dev.yml rename to .github/workflows/deploy.yml From 6debc9dbbe8ac2558c894b0b62b90c454815acee Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 10:58:05 +0100 Subject: [PATCH 08/22] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Remove=20Deployme?= =?UTF-8?q?nt=20Workflow=20for=20Development=20Environment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete the deploy.yml file as it is no longer needed in the project structure. --- .github/workflows/{deploy.yml => deploy.dev.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{deploy.yml => deploy.dev.yml} (99%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.dev.yml similarity index 99% rename from .github/workflows/deploy.yml rename to .github/workflows/deploy.dev.yml index b45714e..7bdc6e6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.dev.yml @@ -2,7 +2,7 @@ name: Déploiement sur VPS (dev) on: push: - branches: [main] + workflow_dispatch: env: From 1bc706f2418044baf7c131653caee333f1611ede Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 10:59:02 +0100 Subject: [PATCH 09/22] =?UTF-8?q?=E2=9C=A8=20Update=20Deployment=20Workflo?= =?UTF-8?q?w=20to=20Include=20Branch=20Triggers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add branch triggers for push and pull request events on main and develop branches in the deploy.dev.yml workflow. --- .github/workflows/deploy.dev.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index 7bdc6e6..b380bd4 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -2,7 +2,9 @@ name: Déploiement sur VPS (dev) on: push: - + branches: [main, develop] + pull_request: + branches: [main, develop] workflow_dispatch: env: From c49994491d1c27da699f9175489dea17aa02093d Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 11:04:44 +0100 Subject: [PATCH 10/22] =?UTF-8?q?=E2=9C=A8=20Update=20Deployment=20Workflo?= =?UTF-8?q?w=20to=20Include=20Environment=20Specification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add environment specification for the deployment job in the deploy.dev.yml workflow to ensure proper context during deployment. --- .github/workflows/deploy.dev.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index b380bd4..4330496 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -1,5 +1,7 @@ name: Déploiement sur VPS (dev) + + on: push: branches: [main, develop] @@ -15,6 +17,7 @@ env: jobs: deploy: runs-on: ubuntu-latest + environment: dev steps: - name: Vérifier le code uses: actions/checkout@v4 From cb176addd34089fc1c6edb7f867d0ae462e0d7e2 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 11:16:53 +0100 Subject: [PATCH 11/22] =?UTF-8?q?=E2=9C=A8=20Update=20Deployment=20Workflo?= =?UTF-8?q?w=20to=20Change=20Port=20Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modify the deployment workflow in deploy.dev.yml to change the application port from 3306 to 3333, ensuring proper application routing during deployment. --- .github/workflows/deploy.dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index 4330496..de546e1 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -50,7 +50,7 @@ jobs: echo "APP_NAME=${{ env.APP_NAME }}" >> .env echo "APP_PATH=${{ env.APP_PATH }}" >> .env echo "TZ=UTC" >> .env - echo "PORT=3306" >> .env + echo "PORT=3333" >> .env echo "HOST=0.0.0.0" >> .env echo "LOG_LEVEL=INFO" >> .env echo "APP_KEY=appkey" >> .env From 95ba6d32b8d0d2af21fcdfb3b50e6d4ed1c0aedf Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 11:39:41 +0100 Subject: [PATCH 12/22] =?UTF-8?q?=E2=9C=A8=20Update=20Deployment=20Workflo?= =?UTF-8?q?w=20to=20Change=20MySQL=20Port=20Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modify the deployment workflow in deploy.dev.yml to change the MySQL port from 3306 to 3307, ensuring compatibility with the updated database configuration. --- .github/workflows/deploy.dev.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index de546e1..9b9378e 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -57,12 +57,12 @@ jobs: echo "SESSION_DRIVER=cookie" >> .env echo "DB_CONNECTION=mysql" >> .env echo "DB_HOST=mysql" >> .env - echo "DB_PORT=3306" >> .env + echo "DB_PORT=3307" >> .env echo "DB_USER=root" >> .env echo "DB_PASSWORD=root" >> .env echo "DB_DATABASE=${{ env.DATABASE_NAME }}" >> .env echo "MYSQL_HOST=mysql" >> .env - echo "MYSQL_PORT=3306" >> .env + echo "MYSQL_PORT=3307" >> .env echo "MYSQL_USER=root" >> .env echo "MYSQL_PASSWORD=root" >> .env echo "MYSQL_DB_NAME=${{ env.DATABASE_NAME }}" >> .env From 3b8bb4d1ed1d0c3da6dd753c59b12580488597c8 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 11:47:42 +0100 Subject: [PATCH 13/22] =?UTF-8?q?=E2=9C=A8=20Update=20Docker=20Compose=20C?= =?UTF-8?q?onfiguration=20for=20Environment=20Variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modify the compose.yaml file to utilize environment variables for application and database configurations, enhancing flexibility and consistency across environments. - Change NODE_ENV to production and update port configurations to use dynamic environment variables for both application and MySQL services. --- compose.yaml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/compose.yaml b/compose.yaml index 609fde0..4f7018a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -9,17 +9,29 @@ services: env_file: - .env ports: - - "3333:3333" + - "${PORT}:3333" volumes: - .:/app - /app/node_modules environment: - - NODE_ENV=development + - NODE_ENV=production - DB_HOST=mysql - - DB_PORT=3306 + - DB_PORT=${DB_PORT} - DB_DATABASE=${DB_DATABASE} - DB_USERNAME=${DB_USERNAME} - DB_PASSWORD=${DB_PASSWORD} + - PORT=${PORT} + - HOST=${HOST} + - LOG_LEVEL=${LOG_LEVEL} + - APP_KEY=${APP_KEY} + - SESSION_DRIVER=${SESSION_DRIVER} + - DB_CONNECTION=${DB_CONNECTION} + - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} + - GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL} + - TZ=${TZ} + + depends_on: - mysql command: sh -c "npm install && npm run dev" @@ -32,7 +44,7 @@ services: env_file: - .env ports: - - "3306:3306" + - "${DB_PORT}:3306" environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - MYSQL_DATABASE=${DB_DATABASE} @@ -55,7 +67,7 @@ services: environment: - NODE_ENV=development - DB_HOST=mysql - - DB_PORT=3306 + - DB_PORT=${DB_PORT} - DB_DATABASE=${DB_DATABASE} - DB_USERNAME=${DB_USERNAME} - DB_PASSWORD=${DB_PASSWORD} From e6c670a0e086ae95998f15043001af2afbbd6e74 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 11:58:59 +0100 Subject: [PATCH 14/22] =?UTF-8?q?=E2=9C=A8=20Update=20Docker=20Compose=20a?= =?UTF-8?q?nd=20Deployment=20Workflow=20for=20Environment=20Variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change MySQL port configuration in compose.yaml to use MYSQL_PORT for improved flexibility. - Update NODE_ENV to production and APP_KEY in deploy.dev.yml for consistency in the deployment environment. --- .github/workflows/deploy.dev.yml | 4 ++-- compose.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.dev.yml b/.github/workflows/deploy.dev.yml index 9b9378e..6b8a6e1 100644 --- a/.github/workflows/deploy.dev.yml +++ b/.github/workflows/deploy.dev.yml @@ -46,14 +46,14 @@ jobs: port: ${{ secrets.VPS_PORT }} script: | cd ${{ env.APP_PATH }}/app - echo "NODE_ENV=development" > .env + echo "NODE_ENV=production" > .env echo "APP_NAME=${{ env.APP_NAME }}" >> .env echo "APP_PATH=${{ env.APP_PATH }}" >> .env echo "TZ=UTC" >> .env echo "PORT=3333" >> .env echo "HOST=0.0.0.0" >> .env echo "LOG_LEVEL=INFO" >> .env - echo "APP_KEY=appkey" >> .env + echo "APP_KEY=onsklvmesdlvmnrsdmvlrks" >> .env echo "SESSION_DRIVER=cookie" >> .env echo "DB_CONNECTION=mysql" >> .env echo "DB_HOST=mysql" >> .env diff --git a/compose.yaml b/compose.yaml index 4f7018a..f77a91c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -44,7 +44,7 @@ services: env_file: - .env ports: - - "${DB_PORT}:3306" + - "${MYSQL_PORT}:3306" environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - MYSQL_DATABASE=${DB_DATABASE} From 49a8ae1d8d887f7c33941e92dc9f11e67643ea47 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 12:07:02 +0100 Subject: [PATCH 15/22] =?UTF-8?q?=E2=9C=A8=20Update=20MySQL=20Port=20Confi?= =?UTF-8?q?guration=20in=20Docker=20Compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change MySQL port configuration in compose.yaml from dynamic environment variable to a static value of 3306 for both production and development environments, ensuring consistency in database connectivity. --- compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yaml b/compose.yaml index f77a91c..0683f72 100644 --- a/compose.yaml +++ b/compose.yaml @@ -16,7 +16,7 @@ services: environment: - NODE_ENV=production - DB_HOST=mysql - - DB_PORT=${DB_PORT} + - DB_PORT=3306 - DB_DATABASE=${DB_DATABASE} - DB_USERNAME=${DB_USERNAME} - DB_PASSWORD=${DB_PASSWORD} @@ -67,7 +67,7 @@ services: environment: - NODE_ENV=development - DB_HOST=mysql - - DB_PORT=${DB_PORT} + - DB_PORT=3306 - DB_DATABASE=${DB_DATABASE} - DB_USERNAME=${DB_USERNAME} - DB_PASSWORD=${DB_PASSWORD} From 489c53e8c77b657e92a501a3c11d4e74a8a5e444 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 17:32:30 +0100 Subject: [PATCH 16/22] =?UTF-8?q?=E2=9C=A8=20Refactor=20Docker=20Configura?= =?UTF-8?q?tion=20and=20Update=20Memory=20Bank?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplify the Docker Compose configuration by removing unnecessary volume mappings and ensuring the MySQL port is configurable via the new environment variable `${MYSQL_PORT}`. - Update the Dockerfile to correctly copy the build artifacts and resources for production. - Revise activeContext.md and productContext.md to reflect recent changes in the Docker setup and project objectives. - Enhance projectbrief.md with detailed project goals and scope, emphasizing the use of Docker for consistent development and production environments. --- memory-bank/.cursorrules | 47 ----------------------------------- memory-bank/activeContext.md | 29 --------------------- memory-bank/productContext.md | 17 ------------- memory-bank/progress.md | 29 --------------------- memory-bank/projectbrief.md | 13 ---------- memory-bank/systemPatterns.md | 33 ------------------------ memory-bank/techContext.md | 40 ----------------------------- 7 files changed, 208 deletions(-) delete mode 100644 memory-bank/.cursorrules delete mode 100644 memory-bank/activeContext.md delete mode 100644 memory-bank/productContext.md delete mode 100644 memory-bank/progress.md delete mode 100644 memory-bank/projectbrief.md delete mode 100644 memory-bank/systemPatterns.md delete mode 100644 memory-bank/techContext.md diff --git a/memory-bank/.cursorrules b/memory-bank/.cursorrules deleted file mode 100644 index c771954..0000000 --- a/memory-bank/.cursorrules +++ /dev/null @@ -1,47 +0,0 @@ -# .cursorrules - -## Schémas de mise en œuvre critiques - -- Utilisation de Dockerfiles multi-stage pour optimiser la taille des images et améliorer la sécurité -- Séparation des responsabilités entre les services Docker -- Utilisation de l'étape de dépendances du Dockerfile pour le service de migrations -- Configuration des variables d'environnement pour une flexibilité maximale -- Utilisation de volumes Docker pour la persistance des données et le développement en temps réel - -## Préférences de l'utilisateur et flux de travail - -- Préférence pour une architecture basée sur des conteneurs Docker -- Utilisation de Node.js 20.12.2 pour la production -- Utilisation de MySQL 8.0 pour la base de données -- Configuration des variables d'environnement via le fichier .env -- Exécution des migrations via un service dédié - -## Schémas spécifiques au projet - -- Architecture en trois services : App, MySQL et Migrations -- Utilisation d'AdonisJS comme framework backend -- Configuration Docker optimisée pour le développement et la production -- Script d'initialisation pour la base de données MySQL -- Service dédié pour les migrations de base de données - -## Défis connus - -- Configuration correcte des variables d'environnement pour tous les services -- Exécution fiable des migrations de base de données -- Optimisation des images Docker pour réduire la taille et améliorer la sécurité -- Gestion des dépendances entre les services Docker - -## Évolution des décisions du projet - -- Passage d'une configuration simple à une architecture basée sur des conteneurs Docker -- Optimisation du Dockerfile Node.js avec un build multi-stage -- Création d'un service dédié pour les migrations de base de données -- Utilisation de l'étape de dépendances du Dockerfile pour le service de migrations - -## Schémas d'utilisation des outils - -- Utilisation de Docker et Docker Compose pour la conteneurisation -- Utilisation de Node.js et npm pour le développement backend -- Utilisation de MySQL pour la base de données -- Utilisation d'AdonisJS comme framework backend -- Utilisation de volumes Docker pour la persistance des données et le développement en temps réel \ No newline at end of file diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md deleted file mode 100644 index 8dcb5ca..0000000 --- a/memory-bank/activeContext.md +++ /dev/null @@ -1,29 +0,0 @@ -# activeContext.md - -## Focus de travail actuel - -Configuration de l'environnement de développement avec Docker pour le projet JSCM. Nous avons mis en place une architecture basée sur des conteneurs avec trois services principaux : l'application AdonisJS, la base de données MySQL et un service dédié pour les migrations. - -## Changements récents - -- Création d'un Dockerfile multi-stage pour le service Node.js basé sur Node.js 20.12.2 -- Configuration d'un service MySQL avec script d'initialisation -- Mise en place d'un service dédié pour les migrations de base de données -- Optimisation de l'architecture Docker pour améliorer les performances et la fiabilité -- Utilisation de l'étape de dépendances du Dockerfile pour le service de migrations - -## Prochaines étapes - -- Tester la configuration Docker complète -- Développer les fonctionnalités de l'application AdonisJS -- Mettre en place les modèles de données et les migrations -- Configurer l'authentification et les autorisations -- Développer les endpoints de l'API - -## Décisions et considérations actives - -- Utilisation de Docker pour garantir la cohérence entre les environnements -- Séparation des responsabilités entre les services -- Optimisation des images Docker pour réduire la taille et améliorer la sécurité -- Configuration des variables d'environnement pour une flexibilité maximale -- Utilisation de volumes Docker pour la persistance des données et le développement en temps réel \ No newline at end of file diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md deleted file mode 100644 index afdc22b..0000000 --- a/memory-bank/productContext.md +++ /dev/null @@ -1,17 +0,0 @@ -# productContext.md - -## Pourquoi ce projet existe-t-il ? - -(Expliquez la raison d'être du projet.) - -## Problèmes résolus - -(Décrivez les problèmes ou besoins auxquels le projet répond.) - -## Comment doit-il fonctionner ? - -(Expliquez comment le produit est censé fonctionner.) - -## Objectifs d'expérience utilisateur - -(Définissez les objectifs clés de l'expérience utilisateur.) \ No newline at end of file diff --git a/memory-bank/progress.md b/memory-bank/progress.md deleted file mode 100644 index 78de26a..0000000 --- a/memory-bank/progress.md +++ /dev/null @@ -1,29 +0,0 @@ -# progress.md - -## Ce qui fonctionne - -- Configuration de l'environnement de développement avec Docker -- Architecture basée sur des conteneurs avec trois services principaux -- Dockerfile multi-stage optimisé pour le service Node.js -- Service MySQL avec script d'initialisation -- Service dédié pour les migrations de base de données -- Configuration des variables d'environnement - -## Ce qu'il reste à construire - -- Développement des fonctionnalités de l'application AdonisJS -- Création des modèles de données et des migrations -- Configuration de l'authentification et des autorisations -- Développement des endpoints de l'API -- Tests de la configuration Docker complète -- Déploiement en production - -## État actuel - -Le projet est en phase initiale de configuration de l'environnement de développement. L'architecture Docker est en place avec trois services principaux : l'application AdonisJS, la base de données MySQL et un service dédié pour les migrations. Les prochaines étapes consistent à développer les fonctionnalités de l'application et à tester la configuration Docker. - -## Problèmes connus - -- Aucun problème majeur identifié à ce jour -- La configuration Docker doit être testée pour vérifier son bon fonctionnement -- Les migrations de base de données doivent être créées et testées \ No newline at end of file diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md deleted file mode 100644 index e5fbee2..0000000 --- a/memory-bank/projectbrief.md +++ /dev/null @@ -1,13 +0,0 @@ -# projectbrief.md - -## Objectif du projet - -(Décrivez ici l'objectif principal du projet, pourquoi il existe et quel problème il résout.) - -## Portée du projet - -(Définissez la portée, les limites et les livrables principaux du projet.) - -## Exigences clés - -(Liste des exigences essentielles pour le succès du projet.) \ No newline at end of file diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md deleted file mode 100644 index 3e1e543..0000000 --- a/memory-bank/systemPatterns.md +++ /dev/null @@ -1,33 +0,0 @@ -# systemPatterns.md - -## Architecture du système - -Le projet utilise une architecture basée sur des conteneurs Docker avec trois services principaux : - -1. **Service App**: Application AdonisJS qui expose l'API REST -2. **Service MySQL**: Base de données relationnelle pour le stockage des données -3. **Service Migrations**: Service dédié pour l'exécution des migrations de base de données - -Cette architecture permet une séparation claire des responsabilités et facilite le développement et le déploiement. - -## Décisions techniques clés - -- **Utilisation de Docker**: Pour garantir la cohérence entre les environnements de développement et de production -- **Dockerfile multi-stage**: Pour optimiser la taille des images et améliorer la sécurité -- **Service de migrations dédié**: Pour garantir l'exécution correcte des migrations de base de données -- **Volumes Docker**: Pour la persistance des données et le développement en temps réel -- **Variables d'environnement**: Pour la configuration flexible des services - -## Modèles de conception utilisés - -- **Architecture en conteneurs**: Séparation des services dans des conteneurs distincts -- **Pattern Builder**: Utilisation de Dockerfiles multi-stage pour la construction des images -- **Pattern Dependency Injection**: Utilisation des variables d'environnement pour l'injection de configuration -- **Pattern Service**: Services dédiés pour des fonctionnalités spécifiques (migrations) - -## Relations entre les composants - -- Le service App dépend du service MySQL pour le stockage des données -- Le service Migrations dépend du service MySQL pour exécuter les migrations -- Tous les services partagent les mêmes variables d'environnement via le fichier .env -- Les volumes Docker permettent la persistance des données et le partage de code \ No newline at end of file diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md deleted file mode 100644 index 1dc4dba..0000000 --- a/memory-bank/techContext.md +++ /dev/null @@ -1,40 +0,0 @@ -# techContext.md - -## Technologies utilisées - -- **Framework Backend**: AdonisJS (Node.js) -- **Base de données**: MySQL 8.0 -- **Conteneurisation**: Docker et Docker Compose -- **Node.js**: Version 20.12.2 (pour la production) - -## Configuration du développement - -Le projet utilise Docker pour l'environnement de développement avec trois services principaux : - -1. **Service App**: Conteneur Node.js qui exécute l'application AdonisJS - - Utilise un Dockerfile multi-stage optimisé pour la production - - Expose le port 3333 pour l'API - - Montage des volumes pour le développement en temps réel - -2. **Service MySQL**: Base de données MySQL 8.0 - - Configuration via variables d'environnement - - Persistance des données via volume Docker - - Script d'initialisation pour créer la base de données - -3. **Service Migrations**: Service dédié pour exécuter les migrations AdonisJS - - Utilise l'étape de dépendances du Dockerfile Node.js - - S'exécute après le démarrage de MySQL - - Exécute les migrations de la base de données - -## Contraintes techniques - -- Node.js 20.6+ requis pour AdonisJS -- MySQL 8.0 pour la base de données -- Variables d'environnement configurées dans le fichier .env - -## Dépendances - -- **AdonisJS**: Framework Node.js pour l'API -- **MySQL**: Base de données relationnelle -- **Docker**: Pour la conteneurisation -- **Docker Compose**: Pour l'orchestration des conteneurs \ No newline at end of file From 203cc68ea651c8e5994ec42b39f28ccb68fe48b2 Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Tue, 29 Apr 2025 17:32:36 +0100 Subject: [PATCH 17/22] =?UTF-8?q?=E2=9C=A8=20Update=20Docker=20Configurati?= =?UTF-8?q?on=20and=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Correct the directory name in .gitignore from 'memory_bank/' to 'memory-bank/' for consistency. - Simplify the Docker Compose configuration by removing unnecessary volume mappings. - Adjust the Dockerfile to ensure proper copying of build artifacts and resources, including images for production deployment. --- .dockerignore | 8 ++++++++ .gitignore | 2 +- compose.yaml | 8 -------- docker/node/Dockerfile | 6 ++++-- 4 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..79b9611 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +build +node_modules +.env +.git +.gitignore +.dockerignore +.github +.vscode diff --git a/.gitignore b/.gitignore index e0d6a14..4e1ef74 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ yarn-error.log # Platform specific .DS_Store -memory-bank/ +memory-bank/ \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 0683f72..7c3ee4f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,3 @@ -version: '3.8' - services: app: build: @@ -10,9 +8,6 @@ services: - .env ports: - "${PORT}:3333" - volumes: - - .:/app - - /app/node_modules environment: - NODE_ENV=production - DB_HOST=mysql @@ -30,11 +25,8 @@ services: - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} - GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL} - TZ=${TZ} - - depends_on: - mysql - command: sh -c "npm install && npm run dev" mysql: build: diff --git a/docker/node/Dockerfile b/docker/node/Dockerfile index 4cee8c7..162f2dd 100644 --- a/docker/node/Dockerfile +++ b/docker/node/Dockerfile @@ -24,6 +24,8 @@ FROM base ENV NODE_ENV=production WORKDIR /app COPY --from=production-deps /app/node_modules /app/node_modules -COPY --from=build /app/build /app +COPY --from=build /app/build/ /app +COPY --from=build /app/resources/images /app/public/resources/images + EXPOSE 3333 -CMD ["node", "./bin/server.js"] \ No newline at end of file +CMD ["node", "bin/server.js"] From a1a303c5ef3dae5a73eb0ca344098f741162ad8d Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Mon, 12 May 2025 01:49:26 +0100 Subject: [PATCH 18/22] =?UTF-8?q?=E2=9C=A8=20Enhance=20Application=20Funct?= =?UTF-8?q?ionality=20and=20Update=20Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new dashboard method in ArticlesController to fetch article statistics for the dashboard view. - Update package.json to include a new path for enums. - Modify CORS configuration to allow all origins for improved API accessibility. - Adjust user migration to change password field to nullable and implement role-based access using enums. - Update login and register pages to replace Link components with anchor tags for better compatibility. - Refactor dashboard page to accept individual article statistics as props instead of a single stats object. - Update routes to link the dashboard to the new controller method for dynamic data rendering. --- app/controllers/articles_controller.ts | 11 ++++ app/services/article_stats_service.ts | 12 ++++ config/cors.ts | 2 +- .../1736839410875_create_users_table.ts | 6 +- enums/role.ts | 7 +++ inertia/pages/auth/login.tsx | 31 +++++++++- inertia/pages/auth/register.tsx | 4 +- inertia/pages/dashboard/index.tsx | 25 +++++---- mock-data/users.ts | 18 ++++++ nomenclature.md | 56 +++++++++++++++++++ package.json | 3 +- start/routes.ts | 10 +--- tsconfig.json | 6 +- 13 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 app/services/article_stats_service.ts create mode 100644 enums/role.ts create mode 100644 mock-data/users.ts create mode 100644 nomenclature.md diff --git a/app/controllers/articles_controller.ts b/app/controllers/articles_controller.ts index 7a5d845..c7b8ea9 100644 --- a/app/controllers/articles_controller.ts +++ b/app/controllers/articles_controller.ts @@ -2,6 +2,7 @@ import { HttpContext } from '@adonisjs/core/http' import { articleValidator } from '#validators/article_validator' import Article from '#models/article' import { DateTime } from 'luxon' +import ArticleStatsService from '#services/article_stats_service' export default class ArticlesController { async index({ inertia, request }: HttpContext) { @@ -45,4 +46,14 @@ export default class ArticlesController { return inertia.render('articles/[slug]', { article }) } + + async dashboard({ inertia }: HttpContext) { + const stats = await ArticleStatsService.getStats() + return inertia.render('dashboard/index', { + publishedArticles: stats.published, + draftArticles: stats.drafts, + discussions: 0, + questions: 0, + }) + } } diff --git a/app/services/article_stats_service.ts b/app/services/article_stats_service.ts new file mode 100644 index 0000000..9bb6d3f --- /dev/null +++ b/app/services/article_stats_service.ts @@ -0,0 +1,12 @@ +import Article from '#models/article' + +export default class ArticleStatsService { + static async getStats() { + const publishedResult = await Article.query().where('is_published', true).count('* as total') + const draftResult = await Article.query().where('is_published', false).count('* as total') + return { + published: Number(publishedResult[0].$extras.total), + drafts: Number(draftResult[0].$extras.total), + } + } +} diff --git a/config/cors.ts b/config/cors.ts index dd79007..4160453 100644 --- a/config/cors.ts +++ b/config/cors.ts @@ -8,7 +8,7 @@ import { defineConfig } from '@adonisjs/cors' */ const corsConfig = defineConfig({ enabled: true, - origin: [], + origin: ['*'], methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], headers: true, exposeHeaders: [], diff --git a/database/migrations/1736839410875_create_users_table.ts b/database/migrations/1736839410875_create_users_table.ts index d318ef8..27e4f40 100644 --- a/database/migrations/1736839410875_create_users_table.ts +++ b/database/migrations/1736839410875_create_users_table.ts @@ -1,4 +1,5 @@ import { BaseSchema } from '@adonisjs/lucid/schema' +import { Role, roles } from '#enums/role' export default class extends BaseSchema { protected tableName = 'users' @@ -9,12 +10,11 @@ export default class extends BaseSchema { table.string('username').unique().notNullable() table.string('name').nullable() table.string('email').unique().notNullable() - table.string('password').notNullable() + table.string('password').nullable() table.string('avatar').nullable() table.string('github_id').unique().nullable() table.string('twitter_id').unique().nullable() - table.boolean('is_admin').defaultTo(false) - table.boolean('is_sponsor').defaultTo(false) + table.enu('role', roles).notNullable().defaultTo(Role.MEMBER) table.timestamp('email_verified_at').nullable() table.timestamps(true, true) }) diff --git a/enums/role.ts b/enums/role.ts new file mode 100644 index 0000000..0fcbaa6 --- /dev/null +++ b/enums/role.ts @@ -0,0 +1,7 @@ +export enum Role { + ADMIN = 'ADMIN', + SPONSOR = 'SPONSOR', + MEMBER = 'MEMBER', +} + +export const roles = Object.values(Role) diff --git a/inertia/pages/auth/login.tsx b/inertia/pages/auth/login.tsx index 7447c33..4e591ea 100644 --- a/inertia/pages/auth/login.tsx +++ b/inertia/pages/auth/login.tsx @@ -3,6 +3,7 @@ import { FormEvent, useState } from 'react' import { router } from '@inertiajs/react' import Navbar from '../../components/navbar' import Footer from '../../components/footer' +import { demoUsers } from '../../../mock-data/users' interface PageProps { errors: { @@ -40,6 +41,32 @@ export default function Login() { + {/* Demo Buttons */} +
+ + +
+ {/* Flash Messages */} {(flash?.error || pageErrors?.form) && (
@@ -83,7 +110,7 @@ export default function Login() { {/* Social Login */}
- @@ -95,7 +122,7 @@ export default function Login() { /> Continue with GitHub - +
diff --git a/inertia/pages/auth/register.tsx b/inertia/pages/auth/register.tsx index 5ee664d..369a5a6 100644 --- a/inertia/pages/auth/register.tsx +++ b/inertia/pages/auth/register.tsx @@ -231,7 +231,7 @@ export default function Register() {
- @@ -243,7 +243,7 @@ export default function Register() { /> GitHub - +
diff --git a/inertia/pages/dashboard/index.tsx b/inertia/pages/dashboard/index.tsx index d3ef077..f9525e0 100644 --- a/inertia/pages/dashboard/index.tsx +++ b/inertia/pages/dashboard/index.tsx @@ -2,14 +2,13 @@ import { Head, Link } from '@inertiajs/react' import DashboardLayout from '../../layouts/dashboard' interface DashboardProps { - stats: { - articles: number - discussions: number - questions: number - } + publishedArticles: number + draftArticles: number + discussions: number + questions: number } -export default function Dashboard({ stats }: DashboardProps) { +export default function Dashboard({ publishedArticles, draftArticles, discussions, questions }: DashboardProps) { return ( @@ -18,21 +17,27 @@ export default function Dashboard({ stats }: DashboardProps) { {/* Stats */}
-
Total Articles
+
Articles publiés
- {stats.articles} + {publishedArticles} +
+
+
+
Brouillons
+
+ {draftArticles}
Total Discussions
- {stats.discussions} + {discussions}
Total Questions
- {stats.questions} + {questions}
diff --git a/mock-data/users.ts b/mock-data/users.ts new file mode 100644 index 0000000..b159751 --- /dev/null +++ b/mock-data/users.ts @@ -0,0 +1,18 @@ +import { Role } from '#enums/role' + +export const demoUsers = [ + { + username: 'admin', + name: 'admin', + email: 'admin@example.com', + password: 'password123', + role: Role.ADMIN, + }, + { + username: 'regularuser', + name: 'Regular User', + email: 'regular@example.com', + password: 'password123', + role: Role.MEMBER, + }, +] diff --git a/nomenclature.md b/nomenclature.md new file mode 100644 index 0000000..f422f16 --- /dev/null +++ b/nomenclature.md @@ -0,0 +1,56 @@ +## 🚀 Nomenclature des noms de branches + +Les branches doivent décrire leur objectif. Utilise un **préfixe** suivi d’un **slug** clair et concis. + +### 📂 Types de branches : + +| Type | Préfixe | Exemple | +| -------- | ----------- | --------------------------------- | +| Feature | `feature/` | `feature/user-authentication` | +| Bug fix | `fix/` | `fix/login-redirect-bug` | +| Hotfix | `hotfix/` | `hotfix/crash-on-startup` | +| Refactor | `refactor/` | `refactor/user-service-structure` | +| Chore | `chore/` | `chore/update-dependencies` | +| Test | `test/` | `test/add-login-unit-tests` | +| Docs | `docs/` | `docs/api-auth-docs` | +| Release | `release/` | `release/v1.0.0` | + +**Bonus :** Ajoute un identifiant de ticket si tu travailles avec un outil comme Jira, Notion ou Linear : + +``` +feature/1234-user-authentication +fix/BUG-982-login-crash +``` + +--- + +## 📝 Nomenclature des messages de commit + +Utilise le **format conventionnel** `type(scope): message`, inspiré de [Conventional Commits](https://www.conventionalcommits.org/), très apprécié dans les projets pro, surtout avec les outils d’intégration continue (CI/CD). + +### 🔠 Types courants de commits + +| Type | Utilisation | Exemple | +| ---------- | ------------------------------------------------------ | ----------------------------------------------- | +| `feat` | Nouvelle fonctionnalité | `feat(auth): add GitHub OAuth login` | +| `fix` | Correction de bug | `fix(api): prevent crash on empty payload` | +| `docs` | Changement de documentation | `docs(readme): add usage instructions` | +| `style` | Changement sans impact sur le code (indentation, etc.) | `style(ui): reindent login form` | +| `refactor` | Refactoring sans changement fonctionnel | `refactor(db): simplify user query` | +| `perf` | Amélioration des performances | `perf(api): reduce response time for dashboard` | +| `test` | Ajout ou mise à jour de tests | `test(user): add signup unit tests` | +| `chore` | Tâches diverses (build, dépendances, scripts, etc.) | `chore: update eslint config` | +| `ci` | Configuration de l’intégration continue | `ci(github): add action for test coverage` | + +### 🎯 Bonnes pratiques + +* **Présent** : Utilise l’impératif présent → `add`, `fix`, `update` (pas `added` ou `adds`) +* **Court et clair** : Max 50-60 caractères pour le titre +* **Optionnel** : Ajoute une description en dessous si nécessaire + +```bash +git commit -m "feat(profile): add profile picture upload" + +# ou avec un corps : +git commit -m "fix(auth): handle empty token on refresh" -m "Previously, the app crashed when the token was null. This ensures a fallback redirect." +``` diff --git a/package.json b/package.json index 921cb46..5fcbaa7 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "#database/*": "./database/*.js", "#tests/*": "./tests/*.js", "#start/*": "./start/*.js", - "#config/*": "./config/*.js" + "#config/*": "./config/*.js", + "#enums/*": "./enums/*.js" }, "devDependencies": { "@adonisjs/assembler": "^7.8.2", diff --git a/start/routes.ts b/start/routes.ts index 601257c..235fb5e 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -54,15 +54,7 @@ router.get('/:username', [ProfileController, 'show']).where('username', '@.*').a router .group(() => { router - .get('dashboard', async ({ inertia }) => { - return inertia.render('dashboard/index', { - stats: { - articles: 0, - discussions: 0, - questions: 0, - }, - }) - }) + .get('dashboard', [ArticlesController, 'dashboard']) .as('dashboard') }) .middleware(middleware.auth()) diff --git a/tsconfig.json b/tsconfig.json index cd3596e..976e7ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,9 @@ "compilerOptions": { "rootDir": "./", "outDir": "./build", + "paths": { + "@/*": ["./inertia/lib/*"] + } }, - "exclude": ["./inertia/**/*", "node_modules", "build"], - + "exclude": ["./inertia/**/*", "node_modules", "build"] } From 0606e34a8a0d9a607c83d0ecaad734b62b25f13a Mon Sep 17 00:00:00 2001 From: Brightky Efoo Date: Sun, 18 May 2025 16:42:39 +0100 Subject: [PATCH 19/22] =?UTF-8?q?=E2=9C=A8=20Add=20MinIO=20Service=20and?= =?UTF-8?q?=20Update=20Dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduce MinIO service in Docker Compose for object storage, including environment variables for configuration. - Update package.json and package-lock.json to include new dependencies: axios and minio. - Refactor articles and user models to utilize enums for article status and user roles, enhancing code clarity and maintainability. - Modify article and register validators to incorporate new fields and validation rules. - Update article creation and registration forms to support new fields and improve user experience. --- app/controllers/api/upload_controller.ts | 19 ++ app/controllers/articles_controller.ts | 11 +- app/controllers/auth/register_controller.ts | 6 +- app/models/article.ts | 12 +- app/models/user.ts | 9 +- app/services/article_stats_service.ts | 13 +- app/services/minio_service.ts | 37 +++ app/validators/article_validator.ts | 7 +- app/validators/register_validator.ts | 2 + compose.yaml | 22 +- .../1736839410875_create_users_table.ts | 4 +- .../1736839410989_create_articles_table.ts | 7 +- database/seeders/user_seeder.ts | 20 +- enums/article_status.ts | 7 + enums/role.ts | 2 +- inertia/components/articles/create-form.tsx | 106 ++++-- inertia/components/articles/tag-input.tsx | 60 ++++ inertia/hooks/useCreateArticleForm.ts | 71 ++++ inertia/pages/articles/create.tsx | 27 +- inertia/pages/auth/register.tsx | 25 +- inertia/pages/dashboard/index.tsx | 32 +- package-lock.json | 307 ++++++++++++++++-- package.json | 8 +- start/env.ts | 7 + start/routes.ts | 10 +- 25 files changed, 715 insertions(+), 116 deletions(-) create mode 100644 app/controllers/api/upload_controller.ts create mode 100644 app/services/minio_service.ts create mode 100644 enums/article_status.ts create mode 100644 inertia/components/articles/tag-input.tsx create mode 100644 inertia/hooks/useCreateArticleForm.ts diff --git a/app/controllers/api/upload_controller.ts b/app/controllers/api/upload_controller.ts new file mode 100644 index 0000000..c053f7d --- /dev/null +++ b/app/controllers/api/upload_controller.ts @@ -0,0 +1,19 @@ +import { HttpContext } from '@adonisjs/core/http' +import MinioService from '#services/minio_service' + +export default class UploadController { + async presign({ request }: HttpContext) { + const { fileName, mimeType } = request.only(['fileName', 'mimeType']) + const { url, key } = await MinioService.getPresignedUrl(fileName, mimeType) + return { url, key } + } + + async presignView({ request }: HttpContext) { + const key = request.input('key') || request.qs().key + if (!key) { + return { error: 'Missing key' } + } + const url = await MinioService.getPresignedViewUrl(key) + return { url } + } +} diff --git a/app/controllers/articles_controller.ts b/app/controllers/articles_controller.ts index c7b8ea9..65e1bb2 100644 --- a/app/controllers/articles_controller.ts +++ b/app/controllers/articles_controller.ts @@ -3,13 +3,14 @@ import { articleValidator } from '#validators/article_validator' import Article from '#models/article' import { DateTime } from 'luxon' import ArticleStatsService from '#services/article_stats_service' +import { ArticleStatus } from '#enums/article_status' export default class ArticlesController { async index({ inertia, request }: HttpContext) { const page = request.input('page', 1) const articles = await Article.query() .preload('author') - .where('is_published', true) + .where('status', ArticleStatus.PUBLISHED) .orderBy('published_at', 'desc') .paginate(page, 10) @@ -29,11 +30,14 @@ export default class ArticlesController { article.title = data.title article.content = data.content article.excerpt = data.excerpt - article.isPublished = data.isPublished || false + article.status = data.status as ArticleStatus article.authorId = auth.user!.id - if (data.isPublished) { + if (data.status === ArticleStatus.PUBLISHED) { article.publishedAt = DateTime.now() } + article.coverImage = data.coverImage || null + article.canonicalUrl = data.canonicalUrl || null + article.tags = data.tags || [] await article.generateSlug() await article.save() @@ -52,6 +56,7 @@ export default class ArticlesController { return inertia.render('dashboard/index', { publishedArticles: stats.published, draftArticles: stats.drafts, + waitingArticles: stats.waiting, discussions: 0, questions: 0, }) diff --git a/app/controllers/auth/register_controller.ts b/app/controllers/auth/register_controller.ts index f6d4857..4d2e8a6 100644 --- a/app/controllers/auth/register_controller.ts +++ b/app/controllers/auth/register_controller.ts @@ -1,6 +1,7 @@ import { HttpContext } from '@adonisjs/core/http' import User from '#models/user' import { registerValidator } from '#validators/register_validator' +import { Role } from '#enums/role' export default class RegisterController { async show({ inertia }: HttpContext) { @@ -10,7 +11,10 @@ export default class RegisterController { async store({ request, auth, response }: HttpContext) { const data = await registerValidator.validate(request.all()) - const user = await User.create(data) + const user = await User.create({ + ...data, + role: (data.role as Role) || Role.MEMBER, + }) await auth.use('web').login(user) return response.redirect().toRoute('dashboard') diff --git a/app/models/article.ts b/app/models/article.ts index 766ff19..f7d617d 100644 --- a/app/models/article.ts +++ b/app/models/article.ts @@ -3,6 +3,7 @@ import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' import type { BelongsTo } from '@adonisjs/lucid/types/relations' import string from '@adonisjs/core/helpers/string' import User from '#models/user' +import { ArticleStatus } from '#enums/article_status' export default class Article extends BaseModel { @column({ isPrimary: true }) @@ -21,7 +22,7 @@ export default class Article extends BaseModel { declare excerpt: string @column() - declare isPublished: boolean + declare status: ArticleStatus @column() declare authorId: number @@ -38,6 +39,15 @@ export default class Article extends BaseModel { @column.dateTime() declare publishedAt: DateTime | null + @column() + declare coverImage: string | null + + @column() + declare canonicalUrl: string | null + + @column() + declare tags: string[] | null + // Generate slug before saving public async generateSlug() { this.slug = string.slug(this.title) diff --git a/app/models/user.ts b/app/models/user.ts index b39d318..9440aa5 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -6,6 +6,7 @@ import { withAuthFinder } from '@adonisjs/auth/mixins/lucid' import type { HasMany } from '@adonisjs/lucid/types/relations' import Article from '#models/article' import { DbRememberMeTokensProvider } from '@adonisjs/auth/session' +import { Role } from '#enums/role' const AuthFinder = withAuthFinder(() => hash.use('scrypt'), { uids: ['email'], @@ -38,10 +39,7 @@ export default class User extends compose(BaseModel, AuthFinder) { declare twitterId: string | null @column() - declare isAdmin: boolean - - @column() - declare isSponsor: boolean + declare role: Role @column.dateTime() declare emailVerifiedAt: DateTime | null @@ -70,8 +68,7 @@ export default class User extends compose(BaseModel, AuthFinder) { name: this.name, email: this.email, avatar: this.avatar, - isAdmin: this.isAdmin, - isSponsor: this.isSponsor, + role: this.role, } } } diff --git a/app/services/article_stats_service.ts b/app/services/article_stats_service.ts index 9bb6d3f..4dc2c80 100644 --- a/app/services/article_stats_service.ts +++ b/app/services/article_stats_service.ts @@ -1,12 +1,21 @@ +import { ArticleStatus } from '#enums/article_status' import Article from '#models/article' export default class ArticleStatsService { static async getStats() { - const publishedResult = await Article.query().where('is_published', true).count('* as total') - const draftResult = await Article.query().where('is_published', false).count('* as total') + const publishedResult = await Article.query() + .where('status', ArticleStatus.PUBLISHED) + .count('* as total') + const draftResult = await Article.query() + .where('status', ArticleStatus.DRAFT) + .count('* as total') + const waitingResult = await Article.query() + .where('status', ArticleStatus.WAITING_APPROVAL) + .count('* as total') return { published: Number(publishedResult[0].$extras.total), drafts: Number(draftResult[0].$extras.total), + waiting: Number(waitingResult[0].$extras.total), } } } diff --git a/app/services/minio_service.ts b/app/services/minio_service.ts new file mode 100644 index 0000000..72b3762 --- /dev/null +++ b/app/services/minio_service.ts @@ -0,0 +1,37 @@ +import { Client } from 'minio' +import env from '#start/env' +import { v4 as uuidv4 } from 'uuid' + +const minioClient = new Client({ + endPoint: env.get('MINIO_ENDPOINT') || 'localhost', + port: Number(env.get('MINIO_PORT') || '9000'), + useSSL: false, + accessKey: env.get('MINIO_ROOT_USER') || 'minio', + secretKey: env.get('MINIO_ROOT_PASSWORD') || 'minio123', +}) + +const BUCKET = env.get('MINIO_DEFAULT_BUCKETS') || 'public' + +export default class MinioService { + static async upload(file: Buffer, fileName: string, mimeType: string): Promise { + // S'assurer que le bucket existe + const exists = await minioClient.bucketExists(BUCKET) + if (!exists) { + await minioClient.makeBucket(BUCKET) + } + await minioClient.putObject(BUCKET, fileName, file, undefined, { 'Content-Type': mimeType }) + // URL publique + return `${env.get('MINIO_PUBLIC_URL') || 'http://localhost:9000'}/${BUCKET}/${fileName}` + } + + static async getPresignedUrl(fileName: string, mimeType: string) { + const ext = fileName.split('.').pop() + const key = `${uuidv4()}.${ext}` + const url = await minioClient.presignedPutObject(BUCKET, key, 60 * 5) + return { url, key } + } + + static async getPresignedViewUrl(key: string, expirySeconds = 300): Promise { + return minioClient.presignedGetObject(BUCKET, key, expirySeconds) + } +} diff --git a/app/validators/article_validator.ts b/app/validators/article_validator.ts index b263745..828db0c 100644 --- a/app/validators/article_validator.ts +++ b/app/validators/article_validator.ts @@ -1,3 +1,4 @@ +import { ARTICLE_STATUS_LIST } from '#enums/article_status' import vine from '@vinejs/vine' export const articleValidator = vine.compile( @@ -5,7 +6,9 @@ export const articleValidator = vine.compile( title: vine.string().minLength(10).maxLength(255), content: vine.string().minLength(100), excerpt: vine.string().minLength(50).maxLength(160), - tags: vine.array(vine.string()), - isPublished: vine.boolean().optional(), + coverImage: vine.string().optional().nullable(), + canonicalUrl: vine.string().optional().nullable(), + tags: vine.array(vine.string()).optional(), + status: vine.enum(ARTICLE_STATUS_LIST), }) ) diff --git a/app/validators/register_validator.ts b/app/validators/register_validator.ts index 023bfd0..d04db14 100644 --- a/app/validators/register_validator.ts +++ b/app/validators/register_validator.ts @@ -1,4 +1,5 @@ import vine from '@vinejs/vine' +import { ROLES_LIST } from '#enums/role' /** * Validates the register action @@ -9,5 +10,6 @@ export const registerValidator = vine.compile( name: vine.string(), email: vine.string().email(), password: vine.string().minLength(6), + role: vine.enum(ROLES_LIST).optional(), }) ) diff --git a/compose.yaml b/compose.yaml index 7c3ee4f..c99b1ca 100644 --- a/compose.yaml +++ b/compose.yaml @@ -67,5 +67,25 @@ services: - mysql command: sh -c "node ace migration:run" + minio: + image: minio/minio:latest + container_name: jscm-minio + ports: + - "9000:9000" + - "9001:9001" + env_file: + - .env + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} + MINIO_PUBLIC_URL: ${MINIO_PUBLIC_URL} + MINIO_DEFAULT_BUCKETS: ${MINIO_DEFAULT_BUCKETS} + MINIO_ENDPOINT: ${MINIO_ENDPOINT} + MINIO_PORT: ${MINIO_PORT} + command: server /data --console-address ":9001" + volumes: + - minio_data:/data + volumes: - mysql_data: \ No newline at end of file + mysql_data: + minio_data: \ No newline at end of file diff --git a/database/migrations/1736839410875_create_users_table.ts b/database/migrations/1736839410875_create_users_table.ts index 27e4f40..58e7b96 100644 --- a/database/migrations/1736839410875_create_users_table.ts +++ b/database/migrations/1736839410875_create_users_table.ts @@ -1,5 +1,5 @@ +import { Role, ROLES_LIST } from '#enums/role' import { BaseSchema } from '@adonisjs/lucid/schema' -import { Role, roles } from '#enums/role' export default class extends BaseSchema { protected tableName = 'users' @@ -14,7 +14,7 @@ export default class extends BaseSchema { table.string('avatar').nullable() table.string('github_id').unique().nullable() table.string('twitter_id').unique().nullable() - table.enu('role', roles).notNullable().defaultTo(Role.MEMBER) + table.enu('role', ROLES_LIST).notNullable().defaultTo(Role.MEMBER) table.timestamp('email_verified_at').nullable() table.timestamps(true, true) }) diff --git a/database/migrations/1736839410989_create_articles_table.ts b/database/migrations/1736839410989_create_articles_table.ts index c900276..f836853 100644 --- a/database/migrations/1736839410989_create_articles_table.ts +++ b/database/migrations/1736839410989_create_articles_table.ts @@ -1,5 +1,5 @@ import { BaseSchema } from '@adonisjs/lucid/schema' - +import { ARTICLE_STATUS_LIST, ArticleStatus } from '#enums/article_status' export default class extends BaseSchema { protected tableName = 'articles' @@ -12,8 +12,11 @@ export default class extends BaseSchema { table.text('content').notNullable() table.integer('user_id').unsigned().references('id').inTable('users').onDelete('CASCADE') table.integer('author_id').unsigned().references('id').inTable('users').onDelete('CASCADE') - table.boolean('is_published').defaultTo(false) + table.enu('status', ARTICLE_STATUS_LIST).defaultTo(ArticleStatus.DRAFT) table.timestamp('published_at').nullable() + table.string('cover_image').nullable() + table.string('canonical_url').nullable() + table.json('tags').nullable() table.timestamps(true, true) }) } diff --git a/database/seeders/user_seeder.ts b/database/seeders/user_seeder.ts index cc2ecaa..2c1dcf2 100644 --- a/database/seeders/user_seeder.ts +++ b/database/seeders/user_seeder.ts @@ -1,27 +1,11 @@ import { BaseSeeder } from '@adonisjs/lucid/seeders' import User from '#models/user' +import { demoUsers } from '#mock-data/users' export default class UserSeeder extends BaseSeeder { async run() { // Create a test user - await User.create({ - username: 'testuser', - name: 'Test User', - email: 'test@example.com', - password: 'password123', - isAdmin: true, - isSponsor: false, - }) - - // You can add more test users if needed - await User.create({ - username: 'regularuser', - name: 'Regular User', - email: 'regular@example.com', - password: 'password123', - isAdmin: false, - isSponsor: false, - }) + await User.createMany(demoUsers) console.log('✅ Users seeded successfully') } diff --git a/enums/article_status.ts b/enums/article_status.ts new file mode 100644 index 0000000..a04d812 --- /dev/null +++ b/enums/article_status.ts @@ -0,0 +1,7 @@ +export enum ArticleStatus { + DRAFT = 'draft', + WAITING_APPROVAL = 'waiting_approval', + PUBLISHED = 'published', +} + +export const ARTICLE_STATUS_LIST: ArticleStatus[] = Object.values(ArticleStatus) diff --git a/enums/role.ts b/enums/role.ts index 0fcbaa6..aa8345a 100644 --- a/enums/role.ts +++ b/enums/role.ts @@ -4,4 +4,4 @@ export enum Role { MEMBER = 'MEMBER', } -export const roles = Object.values(Role) +export const ROLES_LIST: Role[] = Object.values(Role) diff --git a/inertia/components/articles/create-form.tsx b/inertia/components/articles/create-form.tsx index 69cb270..e6920f8 100644 --- a/inertia/components/articles/create-form.tsx +++ b/inertia/components/articles/create-form.tsx @@ -1,7 +1,9 @@ -import { FormEvent, useState, Suspense, lazy } from 'react' -import { useForm } from '@inertiajs/react' +import { FormEvent, Suspense, lazy } from 'react' import { Switch } from '@headlessui/react' import SlideOver from '../slide-over' +import TagInput from './tag-input' +import { useCreateArticleForm } from '../../hooks/useCreateArticleForm' +import { ArticleStatus } from '#enums/article_status' const MDEditor = lazy(() => import('@uiw/react-md-editor')) @@ -11,23 +13,18 @@ interface CreateArticleFormProps { } export default function CreateArticleForm({ isOpen, onClose }: CreateArticleFormProps) { - const [isDraft, setIsDraft] = useState(true) - const { data, setData, post, processing, errors } = useForm({ - title: '', - canonicalUrl: '', - content: '', - excerpt: '', - tags: [], - language: 'fr', - coverImage: null as File | null, - }) - - function handleSubmit(e: FormEvent) { - e.preventDefault() - post('/articles', { - onSuccess: () => onClose(), - }) - } + const { + data, + setData, + processing, + errors, + isDraft, + fileInputRef, + coverPreview, + handleCoverChange, + handleSwitchChange, + handleSubmit, + } = useCreateArticleForm(onClose) return ( @@ -55,8 +52,41 @@ export default function CreateArticleForm({ isOpen, onClose }: CreateArticleForm
-
-
+
fileInputRef.current?.click()} + > + {coverPreview ? ( + cover + ) : ( + <> + + + + Ajouter une photo de couverture + + )} + +
+ +
+
@@ -71,7 +101,7 @@ export default function CreateArticleForm({ isOpen, onClose }: CreateArticleForm {errors.title &&

{errors.title}

}
-
+
@@ -79,7 +109,7 @@ export default function CreateArticleForm({ isOpen, onClose }: CreateArticleForm
+
+
+ + setData('canonicalUrl', e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" + /> +
+
+ + setData('tags', tags)} /> +
+
+
diff --git a/inertia/components/articles/tag-input.tsx b/inertia/components/articles/tag-input.tsx new file mode 100644 index 0000000..89ab65e --- /dev/null +++ b/inertia/components/articles/tag-input.tsx @@ -0,0 +1,60 @@ +import { useState, KeyboardEvent } from 'react' + +interface TagInputProps { + value: string[] + onChange: (tags: string[]) => void +} + +export default function TagInput({ value, onChange }: TagInputProps) { + const [input, setInput] = useState('') + + function handleKeyDown(e: KeyboardEvent) { + if ((e.key === 'Enter' || e.key === ',' || e.key === ' ') && input.trim()) { + e.preventDefault() + if (value.length >= 3) return + if (!value.includes(input.trim())) { + onChange([...value, input.trim()]) + } + setInput('') + } else if (e.key === 'Backspace' && !input && value.length) { + onChange(value.slice(0, -1)) + } + } + + function removeTag(tag: string) { + onChange(value.filter(t => t !== tag)) + } + + return ( +
+ {value.map((tag) => ( + + {tag} + + + ))} + = 3 ? 'opacity-50 cursor-not-allowed' : ''}`} + value={input} + onChange={(e) => setInput(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={value.length ? '' : 'Ajouter un tag...'} + disabled={value.length >= 3} + + /> +
+ {value.length}/3 +
+
+ ) +} \ No newline at end of file diff --git a/inertia/hooks/useCreateArticleForm.ts b/inertia/hooks/useCreateArticleForm.ts new file mode 100644 index 0000000..89b9e60 --- /dev/null +++ b/inertia/hooks/useCreateArticleForm.ts @@ -0,0 +1,71 @@ +import { useState, useRef, FormEvent, Suspense, lazy } from 'react' +import { useForm } from '@inertiajs/react' +import { ArticleStatus } from '#enums/article_status' +import axios from 'axios' + +export function useCreateArticleForm(onClose: () => void) { + const [isDraft, setIsDraft] = useState(true) + const { data, setData, post, processing, errors } = useForm({ + title: '', + content: '', + excerpt: '', + language: 'fr', + status: ArticleStatus.DRAFT, + coverImage: '', + canonicalUrl: '', + tags: [] as string[], + }) + + const fileInputRef = useRef(null) + const [coverPreview, setCoverPreview] = useState(null) + + async function handleCoverChange(e: React.ChangeEvent) { + const file = e.target.files?.[0] + if (!file) return + // 1. Demander une URL presign au backend + const presignRes = await axios.post('/api/upload/presign', { + fileName: file.name, + mimeType: file.type, + }) + const { url, key } = presignRes.data + // 2. Uploader le fichier sur l'URL presignée + await fetch(url, { + method: 'PUT', + body: file, + credentials: 'same-origin', + headers: { 'Content-Type': file.type }, + }) + // 3. Stocker la clé et récupérer une presigned URL de lecture + setData('coverImage', key) + const viewRes = await axios.get(`/api/upload/presign-view?key=${encodeURIComponent(key)}`) + setCoverPreview(viewRes.data.url) + } + + function handleSwitchChange(checked: boolean) { + setIsDraft(!checked) + setData('status', checked ? ArticleStatus.PUBLISHED : ArticleStatus.DRAFT) + } + + function handleSubmit(e: FormEvent) { + e.preventDefault() + + console.log('data', data) + post('/articles', { onSuccess: () => onClose() }) + } + + return { + data, + setData, + post, + processing, + errors, + isDraft, + setIsDraft, + fileInputRef, + coverPreview, + setCoverPreview, + handleCoverChange, + handleSwitchChange, + handleSubmit, + } +} \ No newline at end of file diff --git a/inertia/pages/articles/create.tsx b/inertia/pages/articles/create.tsx index c619e94..ea35edc 100644 --- a/inertia/pages/articles/create.tsx +++ b/inertia/pages/articles/create.tsx @@ -2,6 +2,7 @@ import { Head, useForm } from '@inertiajs/react' import { FormEvent } from 'react' import Navbar from '../../components/navbar' import Footer from '../../components/footer' +import { ARTICLE_STATUS_LIST, ArticleStatus } from '../../../enums/article_status' export default function ArticleCreate() { const { data, setData, post, processing, errors } = useForm({ @@ -9,7 +10,7 @@ export default function ArticleCreate() { content: '', excerpt: '', tags: [], - published: false, + status: ArticleStatus.DRAFT, }) function handleSubmit(e: FormEvent) { @@ -67,17 +68,21 @@ export default function ArticleCreate() { {errors.content &&

{errors.content}

}
-
- setData('published', e.target.checked)} - className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" - /> -