diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 719c1d4..c631644 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -3,7 +3,7 @@ name: Deploy to Production on: push: branches: - - prod + - main jobs: validate: diff --git a/.output.txt b/.output.txt new file mode 100644 index 0000000..1a85605 --- /dev/null +++ b/.output.txt @@ -0,0 +1,225 @@ +{ + "name": "@memegoat/source", + "version": "0.0.1", + "description": "", + "scripts": { + "build": "pnpm run build:back && pnpm run build:front && pnpm run build:docs", + "build:front": "pnpm run -F @memegoat/frontend build", + "build:back": "pnpm run -F @memegoat/backend build", + "build:docs": "pnpm run -F @memegoat/documentation build", + "lint": "pnpm run lint:back && pnpm run lint:front && pnpm run lint:docs", + "lint:back": "pnpm run -F @memegoat/backend lint", + "lint:front": "pnpm run -F @memegoat/frontend lint", + "lint:docs": "pnpm run -F @memegoat/documentation lint", + "test": "pnpm run test:back && pnpm run test:front", + "test:back": "pnpm run -F @memegoat/backend test", + "test:front": "pnpm run -F @memegoat/frontend test", + "format": "pnpm run format:back && pnpm run format:front && pnpm run format:docs", + "format:back": "pnpm run -F @memegoat/backend format", + "format:front": "pnpm run -F @memegoat/frontend format", + "format:docs": "pnpm run -F @memegoat/documentation format", + "upgrade": "pnpm dlx taze minor" + }, + "keywords": [], + "author": { + "name": "Mathis HERRIOT", + "email": "mherriot.pro@proton.me", + "role": "Author" + }, + "license": "AGPL-3.0-only", + "devDependencies": { + "@biomejs/biome": "2.3.11" + } +} +{ + "name": "@memegoat/backend", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "files": [ + "dist", + ".migrations", + "drizzle.config.ts" + ], + "scripts": { + "build": "nest build", + "lint": "biome check", + "lint:write": "biome check --write", + "format": "biome format --write", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio" + }, + "dependencies": { + "@nestjs-modules/mailer": "^2.0.2", + "@nestjs/cache-manager": "^3.1.0", + "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.0.1", + "@nestjs/mapped-types": "^2.1.0", + "@nestjs/platform-express": "^11.0.1", + "@nestjs/schedule": "^6.1.0", + "@nestjs/throttler": "^6.5.0", + "@noble/post-quantum": "^0.5.4", + "@node-rs/argon2": "^2.0.2", + "@sentry/nestjs": "^10.32.1", + "@sentry/profiling-node": "^10.32.1", + "cache-manager": "^7.2.7", + "cache-manager-redis-yet": "^5.1.5", + "clamscan": "^2.4.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.3", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.45.1", + "fluent-ffmpeg": "^2.1.3", + "helmet": "^8.1.0", + "iron-session": "^8.0.4", + "jose": "^6.1.3", + "minio": "^8.0.6", + "nodemailer": "^7.0.12", + "otplib": "^12.0.1", + "pg": "^8.16.3", + "qrcode": "^1.5.4", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1", + "sharp": "^0.34.5", + "uuid": "^13.0.0", + "zod": "^4.3.5", + "drizzle-kit": "^0.31.8" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.0", + "globals": "^16.0.0", + "jest": "^30.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "tsx": "^4.21.0", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@types/express": "^5.0.0", + "@types/fluent-ffmpeg": "^2.1.28", + "@types/jest": "^30.0.0", + "@types/multer": "^2.0.0", + "@types/node": "^22.10.7", + "@types/nodemailer": "^7.0.4", + "@types/pg": "^8.16.0", + "@types/qrcode": "^1.5.6", + "@types/sharp": "^0.32.0", + "@types/supertest": "^6.0.2", + "@types/uuid": "^11.0.0", + "drizzle-kit": "^0.31.8" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node", + "transformIgnorePatterns": [ + "node_modules/(?!(.pnpm/)?(jose|@noble|uuid)/)" + ], + "transform": { + "^.+\\.(t|j)sx?$": "ts-jest" + }, + "moduleNameMapper": { + "^@noble/post-quantum/(.*)$": "/../node_modules/@noble/post-quantum/$1", + "^@noble/hashes/(.*)$": "/../node_modules/@noble/hashes/$1" + } + } +} +{ + "name": "@memegoat/frontend", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "biome check", + "format": "biome format --write" + }, + "dependencies": { + "@hookform/resolvers": "^5.2.2", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.8", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "axios": "^1.13.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.562.0", + "next": "16.1.1", + "next-themes": "^0.4.6", + "react": "19.2.3", + "react-day-picker": "^9.13.0", + "react-dom": "19.2.3", + "react-hook-form": "^7.71.1", + "react-resizable-panels": "^4.4.1", + "recharts": "2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "vaul": "^1.1.2", + "zod": "^4.3.5" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "babel-plugin-react-compiler": "1.0.0", + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", + "typescript": "^5" + } +} diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..0d2e50a --- /dev/null +++ b/ROADMAP.md @@ -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 diff --git a/backend/package.json b/backend/package.json index 4518a44..5bcceaf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@memegoat/backend", - "version": "0.0.1", + "version": "0.0.0", "description": "", "author": "", "private": true, diff --git a/frontend/package.json b/frontend/package.json index a503b1c..955a328 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@memegoat/frontend", - "version": "0.0.1", + "version": "0.0.0", "private": true, "scripts": { "dev": "next dev", diff --git a/frontend/todo.md b/frontend/todo.md new file mode 100644 index 0000000..e2634cc --- /dev/null +++ b/frontend/todo.md @@ -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 \ No newline at end of file diff --git a/package.json b/package.json index 139d0cb..db4b961 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { "name": "@memegoat/source", - "version": "0.0.1", + "version": "0.0.0", "description": "", "scripts": { + "version:get": "cmake -P version.cmake GET", + "version:set": "cmake -P version.cmake SET", "build": "pnpm run build:back && pnpm run build:front && pnpm run build:docs", "build:front": "pnpm run -F @memegoat/frontend build", "build:back": "pnpm run -F @memegoat/backend build", diff --git a/version.cmake b/version.cmake new file mode 100644 index 0000000..0f29552 --- /dev/null +++ b/version.cmake @@ -0,0 +1,84 @@ +# version.cmake - Script pour gérer la version SemVer de manière centralisée + +# Usage: cmake -P version.cmake [GET|SET] [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 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] [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}") +else() + message(FATAL_ERROR "Commande inconnue: ${COMMAND}. Utilisez GET ou SET.") +endif()