Compare commits
7 Commits
2df45af305
...
39618f7708
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39618f7708
|
||
|
|
e84e4a5a9d
|
||
|
|
e74973a9d0
|
||
|
|
9233c1bf89
|
||
|
|
88c7f45a2c
|
||
|
|
9af72156f5
|
||
|
|
597a4d615e
|
@@ -1,36 +0,0 @@
|
|||||||
name: Backend Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'backend/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'backend/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> "${GITEA_OUTPUT:-$GITHUB_OUTPUT}"
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile --prefer-offline
|
|
||||||
- name: Run Backend Tests
|
|
||||||
run: pnpm -F @memegoat/backend test
|
|
||||||
@@ -1,43 +1,67 @@
|
|||||||
name: Lint
|
name: CI (Lint & Test)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- main
|
||||||
|
tags-ignore:
|
||||||
|
- 'v*'
|
||||||
paths:
|
paths:
|
||||||
- 'frontend/**'
|
|
||||||
- 'backend/**'
|
- 'backend/**'
|
||||||
|
- 'frontend/**'
|
||||||
- 'documentation/**'
|
- 'documentation/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'frontend/**'
|
|
||||||
- 'backend/**'
|
- 'backend/**'
|
||||||
|
- 'frontend/**'
|
||||||
- 'documentation/**'
|
- 'documentation/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
validate:
|
||||||
|
name: Validate ${{ matrix.component }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
component: [backend, frontend, documentation]
|
component: [backend, frontend, documentation]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout code
|
||||||
- uses: pnpm/action-setup@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: 9
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> "${GITEA_OUTPUT:-$GITHUB_OUTPUT}"
|
echo "STORE_PATH=$(pnpm store path --silent)" >> "${GITEA_OUTPUT:-$GITHUB_OUTPUT}"
|
||||||
- uses: actions/cache@v4
|
|
||||||
|
- name: Setup pnpm cache
|
||||||
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile --prefer-offline
|
run: pnpm install --frozen-lockfile --prefer-offline
|
||||||
|
|
||||||
- name: Lint ${{ matrix.component }}
|
- name: Lint ${{ matrix.component }}
|
||||||
run: pnpm -F @memegoat/${{ matrix.component }} lint
|
run: pnpm -F @memegoat/${{ matrix.component }} lint
|
||||||
|
|
||||||
|
- name: Test ${{ matrix.component }}
|
||||||
|
if: matrix.component == 'backend' || matrix.component == 'frontend'
|
||||||
|
run: |
|
||||||
|
if pnpm -F @memegoat/${{ matrix.component }} run | grep -q "test"; then
|
||||||
|
pnpm -F @memegoat/${{ matrix.component }} test
|
||||||
|
else
|
||||||
|
echo "No test script found for ${{ matrix.component }}, skipping."
|
||||||
|
fi
|
||||||
@@ -3,7 +3,9 @@ name: Deploy to Production
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- prod
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate:
|
validate:
|
||||||
@@ -46,6 +48,15 @@ jobs:
|
|||||||
- name: Lint ${{ matrix.component }}
|
- name: Lint ${{ matrix.component }}
|
||||||
run: pnpm -F @memegoat/${{ matrix.component }} lint
|
run: pnpm -F @memegoat/${{ matrix.component }} lint
|
||||||
|
|
||||||
|
- name: Test ${{ matrix.component }}
|
||||||
|
if: matrix.component == 'backend' || matrix.component == 'frontend'
|
||||||
|
run: |
|
||||||
|
if pnpm -F @memegoat/${{ matrix.component }} run | grep -q "test"; then
|
||||||
|
pnpm -F @memegoat/${{ matrix.component }} test
|
||||||
|
else
|
||||||
|
echo "No test script found for ${{ matrix.component }}, skipping."
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build ${{ matrix.component }}
|
- name: Build ${{ matrix.component }}
|
||||||
run: pnpm -F @memegoat/${{ matrix.component }} build
|
run: pnpm -F @memegoat/${{ matrix.component }} build
|
||||||
env:
|
env:
|
||||||
|
|||||||
50
ROADMAP.md
Normal file
50
ROADMAP.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 🐐 Memegoat - Roadmap & Critères de Production
|
||||||
|
|
||||||
|
Ce document définit les objectifs, les critères techniques et les fonctionnalités à atteindre pour que le projet Memegoat soit considéré comme prêt pour la production et conforme aux normes européennes (RGPD) et françaises.
|
||||||
|
|
||||||
|
## 1. 🏗️ Architecture & Infrastructure
|
||||||
|
- [x] Backend NestJS (TypeScript)
|
||||||
|
- [x] Base de données PostgreSQL avec Drizzle ORM
|
||||||
|
- [x] Stockage d'objets compatible S3 (MinIO)
|
||||||
|
- [x] Service d'Emailing (Nodemailer / SMTPS)
|
||||||
|
- [x] Documentation Technique & Référence API (`docs.memegoat.fr`)
|
||||||
|
- [x] Health Checks (`/health`)
|
||||||
|
- [x] Gestion des variables d'environnement (Validation avec Zod)
|
||||||
|
- [ ] CI/CD (Build, Lint, Test, Deploy)
|
||||||
|
|
||||||
|
## 2. 🔐 Sécurité & Authentification
|
||||||
|
- [x] Hachage des mots de passe (Argon2id)
|
||||||
|
- [x] Gestion des sessions robuste (JWT avec Refresh Token et Rotation)
|
||||||
|
- [x] RBAC (Role Based Access Control) fonctionnel
|
||||||
|
- [x] Système de Clés API (Hachées en base)
|
||||||
|
- [x] Double Authentification (2FA / TOTP)
|
||||||
|
- [x] Limitation de débit (Rate Limiting / Throttler)
|
||||||
|
- [x] Validation stricte des entrées (DTOs + ValidationPipe)
|
||||||
|
- [x] Protection contre les vulnérabilités OWASP (Helmet, CORS)
|
||||||
|
|
||||||
|
## 3. ⚖️ Conformité RGPD (EU & France)
|
||||||
|
- [x] Chiffrement natif des données personnelles (PII) via PGP (pgcrypto)
|
||||||
|
- [x] Hachage aveugle (Blind Indexing) pour l'email (recherche/unicité)
|
||||||
|
- [x] Journalisation d'audit complète (Audit Logs) pour les actions sensibles
|
||||||
|
- [x] Gestion du consentement (Versionnage CGU/Politique de Confidentialité)
|
||||||
|
- [x] Droit à l'effacement : Flux de suppression (Soft Delete -> Purge définitive)
|
||||||
|
- [x] Droit à la portabilité : Export des données utilisateur (JSON)
|
||||||
|
- [x] Purge automatique des données obsolètes (Signalements, Sessions expirées)
|
||||||
|
- [x] Anonymisation des adresses IP (Hachage) dans les logs
|
||||||
|
|
||||||
|
## 4. 🖼️ Fonctionnalités Coeur (Media & Galerie)
|
||||||
|
- [x] Exploration (Trends, Recent, Favoris)
|
||||||
|
- [x] Recherche par Tags, Catégories, Auteur, Texte
|
||||||
|
- [x] Gestion des Favoris
|
||||||
|
- [x] Upload sécurisé via S3 (URLs présignées)
|
||||||
|
- [x] Scan Antivirus (ClamAV) et traitement des médias (WebP, WebM, AVIF, AV1)
|
||||||
|
- [x] Limitation de la taille et des formats de fichiers entrants (Configurable)
|
||||||
|
- [x] Système de Signalement (Reports) et workflow de modération
|
||||||
|
- [ ] SEO : Metatags dynamiques et slugs sémantiques
|
||||||
|
|
||||||
|
## 5. ✅ Qualité & Robustesse
|
||||||
|
- [ ] Couverture de tests unitaires (Jest) > 80%
|
||||||
|
- [ ] Tests d'intégration et E2E
|
||||||
|
- [x] Gestion centralisée des erreurs (Filters NestJS)
|
||||||
|
- [ ] Monitoring et centralisation des logs (ex: Sentry, ELK/Loki)
|
||||||
|
- [ ] Performance : Cache (Redis) pour les tendances et recherches fréquentes
|
||||||
30
frontend/todo.md
Normal file
30
frontend/todo.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Réalisation du frontend :
|
||||||
|
|
||||||
|
# Exigences
|
||||||
|
|
||||||
|
- Responsive dans tout les formats tailwindcss
|
||||||
|
- Accessibilité A11Y
|
||||||
|
- Implémentation réel uniquement
|
||||||
|
- Site en français
|
||||||
|
- SEO parfaitement réalisé, robot.txt, sitemap.xml...
|
||||||
|
- Utilisation des composants shadcn/ui
|
||||||
|
- Réalisation d'une page d'erreur customisé
|
||||||
|
- Utilisation des fonctionalités de NextJS suivantes :
|
||||||
|
- Nested routes
|
||||||
|
- Dynamic routes
|
||||||
|
- Route groups
|
||||||
|
- Private folders
|
||||||
|
- Parralel and intercepted routes
|
||||||
|
- Prefetching pages
|
||||||
|
- Streaming pages
|
||||||
|
- Server and Client Components
|
||||||
|
- Cache Components
|
||||||
|
- Image optimization
|
||||||
|
- Incremental Static Regeneration
|
||||||
|
- Custom hooks
|
||||||
|
- Axios
|
||||||
|
|
||||||
|
Toute l'application est basé sur un système dashboard/sidebar intégrant le routing.
|
||||||
|
La page principale est la page de navigation du contennu.
|
||||||
|
En mode desktop nous retrouvons la sidebar à gauche, le contennu en scroll infini au milieu et les paramètres de recherche sur la droite.
|
||||||
|
En mode mobile la sidebar est replié, les paramètres de recherche sont représenté comme une icône de filtrage flotante en haut à droite
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"version:get": "cmake -P version.cmake GET",
|
||||||
|
"version:set": "cmake -P version.cmake SET",
|
||||||
|
"v:patch": "cmake -P version.cmake PATCH",
|
||||||
|
"v:minor": "cmake -P version.cmake MINOR",
|
||||||
|
"v:major": "cmake -P version.cmake MAJOR",
|
||||||
"build": "pnpm run build:back && pnpm run build:front && pnpm run build:docs",
|
"build": "pnpm run build:back && pnpm run build:front && pnpm run build:docs",
|
||||||
"build:front": "pnpm run -F @memegoat/frontend build",
|
"build:front": "pnpm run -F @memegoat/frontend build",
|
||||||
"build:back": "pnpm run -F @memegoat/backend build",
|
"build:back": "pnpm run -F @memegoat/backend build",
|
||||||
|
|||||||
109
version.cmake
Normal file
109
version.cmake
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# version.cmake - Script pour gérer la version SemVer de manière centralisée
|
||||||
|
|
||||||
|
# Usage: cmake -P version.cmake [GET|SET|PATCH|MINOR|MAJOR] [new_version]
|
||||||
|
|
||||||
|
set(PACKAGE_JSON_FILES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/package.json"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/backend/package.json"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/frontend/package.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fonction pour lire la version depuis le package.json racine
|
||||||
|
function(get_current_version OUT_VAR)
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/package.json" ROOT_JSON)
|
||||||
|
string(JSON CURRENT_VERSION GET "${ROOT_JSON}" "version")
|
||||||
|
set(${OUT_VAR} ${CURRENT_VERSION} PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Fonction pour incrémenter la version SemVer
|
||||||
|
function(increment_version CURRENT_VERSION TYPE OUT_VAR)
|
||||||
|
string(REPLACE "." ";" VERSION_LIST ${CURRENT_VERSION})
|
||||||
|
list(GET VERSION_LIST 0 MAJOR)
|
||||||
|
list(GET VERSION_LIST 1 MINOR)
|
||||||
|
list(GET VERSION_LIST 2 PATCH)
|
||||||
|
|
||||||
|
if("${TYPE}" STREQUAL "MAJOR")
|
||||||
|
math(EXPR MAJOR "${MAJOR} + 1")
|
||||||
|
set(MINOR 0)
|
||||||
|
set(PATCH 0)
|
||||||
|
elseif("${TYPE}" STREQUAL "MINOR")
|
||||||
|
math(EXPR MINOR "${MINOR} + 1")
|
||||||
|
set(PATCH 0)
|
||||||
|
elseif("${TYPE}" STREQUAL "PATCH")
|
||||||
|
math(EXPR PATCH "${PATCH} + 1")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(${OUT_VAR} "${MAJOR}.${MINOR}.${PATCH}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Fonction pour créer un tag git
|
||||||
|
function(create_git_tag VERSION)
|
||||||
|
find_package(Git QUIET)
|
||||||
|
if(GIT_FOUND)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${GIT_EXECUTABLE} tag -a "v${VERSION}" -m "Release v${VERSION}"
|
||||||
|
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
||||||
|
RESULT_VARIABLE TAG_RESULT
|
||||||
|
)
|
||||||
|
if(TAG_RESULT EQUAL 0)
|
||||||
|
message(STATUS "Tag v${VERSION} créé avec succès")
|
||||||
|
else()
|
||||||
|
message(WARNING "Échec de la création du tag v${VERSION}. Il existe peut-être déjà.")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(WARNING "Git non trouvé, impossible de créer le tag.")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Fonction pour mettre à jour la version dans tous les fichiers package.json
|
||||||
|
function(set_new_version NEW_VERSION)
|
||||||
|
foreach(JSON_FILE ${PACKAGE_JSON_FILES})
|
||||||
|
if(EXISTS "${JSON_FILE}")
|
||||||
|
message(STATUS "Mise à jour de ${JSON_FILE} vers la version ${NEW_VERSION}")
|
||||||
|
file(READ "${JSON_FILE}" CONTENT)
|
||||||
|
# Utilisation de string(JSON ...) pour modifier la version si disponible (CMake >= 3.19)
|
||||||
|
# Sinon on peut utiliser une regex simple pour package.json
|
||||||
|
string(REGEX REPLACE "\"version\": \"[^\"]+\"" "\"version\": \"${NEW_VERSION}\"" NEW_CONTENT "${CONTENT}")
|
||||||
|
file(WRITE "${JSON_FILE}" "${NEW_CONTENT}")
|
||||||
|
else()
|
||||||
|
message(WARNING "Fichier non trouvé: ${JSON_FILE}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Demander à l'utilisateur s'il veut tagger (ou le faire par défaut si spécifié)
|
||||||
|
create_git_tag(${NEW_VERSION})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Logique principale
|
||||||
|
set(ARG_OFFSET 0)
|
||||||
|
while(ARG_OFFSET LESS CMAKE_ARGC)
|
||||||
|
if("${CMAKE_ARGV${ARG_OFFSET}}" STREQUAL "-P")
|
||||||
|
math(EXPR COMMAND_INDEX "${ARG_OFFSET} + 2")
|
||||||
|
math(EXPR VERSION_INDEX "${ARG_OFFSET} + 3")
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
math(EXPR ARG_OFFSET "${ARG_OFFSET} + 1")
|
||||||
|
endwhile()
|
||||||
|
|
||||||
|
if(NOT DEFINED COMMAND_INDEX OR COMMAND_INDEX GREATER_EQUAL CMAKE_ARGC)
|
||||||
|
message(FATAL_ERROR "Usage: cmake -P version.cmake [GET|SET|PATCH|MINOR|MAJOR] [new_version]")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(COMMAND "${CMAKE_ARGV${COMMAND_INDEX}}")
|
||||||
|
|
||||||
|
if("${COMMAND}" STREQUAL "GET")
|
||||||
|
get_current_version(VERSION)
|
||||||
|
message("${VERSION}")
|
||||||
|
elseif("${COMMAND}" STREQUAL "SET")
|
||||||
|
if(VERSION_INDEX GREATER_EQUAL CMAKE_ARGC)
|
||||||
|
message(FATAL_ERROR "Veuillez spécifier la nouvelle version: cmake -P version.cmake SET 0.0.0")
|
||||||
|
endif()
|
||||||
|
set(NEW_VERSION "${CMAKE_ARGV${VERSION_INDEX}}")
|
||||||
|
set_new_version("${NEW_VERSION}")
|
||||||
|
elseif("${COMMAND}" MATCHES "^(PATCH|MINOR|MAJOR)$")
|
||||||
|
get_current_version(CURRENT_VERSION)
|
||||||
|
increment_version("${CURRENT_VERSION}" "${COMMAND}" NEW_VERSION)
|
||||||
|
set_new_version("${NEW_VERSION}")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Commande inconnue: ${COMMAND}. Utilisez GET, SET, PATCH, MINOR ou MAJOR.")
|
||||||
|
endif()
|
||||||
Reference in New Issue
Block a user