20 Commits

Author SHA1 Message Date
Mathis HERRIOT
2450977e61 chore(versioning): bump package versions to 0.1.0 across all modules
Some checks failed
CI/CD Pipeline / Valider backend (push) Failing after 1m12s
CI/CD Pipeline / Valider documentation (push) Successful in 1m41s
CI/CD Pipeline / Valider frontend (push) Successful in 1m43s
CI/CD Pipeline / Déploiement en Production (push) Has been skipped
2026-01-20 12:00:35 +01:00
Mathis HERRIOT
afc18b555a chore(versioning): improve version script with enhanced validation and refactored command handling 2026-01-20 11:57:24 +01:00
Mathis HERRIOT
9699127739 feat(docs): add details on S3-compatible storage and email notifications system
Some checks failed
CI/CD Pipeline / Valider backend (push) Failing after 1m0s
CI/CD Pipeline / Valider documentation (push) Successful in 1m39s
CI/CD Pipeline / Valider frontend (push) Successful in 1m33s
CI/CD Pipeline / Déploiement en Production (push) Has been skipped
2026-01-20 11:50:29 +01:00
Mathis HERRIOT
938d8bde7b feat(docs): extend database schema documentation with new fields avatar_url, bio, and slug 2026-01-20 11:50:18 +01:00
Mathis HERRIOT
65c7096f46 feat(docs): update API reference with new endpoints and extended parameter details 2026-01-20 11:49:47 +01:00
Mathis HERRIOT
57c00ad4d1 chore(ci): remove deprecated deploy workflow and update CI pipeline with production deployment steps 2026-01-20 11:21:23 +01:00
Mathis HERRIOT
39618f7708 chore(versioning): bump package versions to 0.0.1 across all modules
Some checks failed
Deploy to Production / Validate Build & Lint (backend) (push) Failing after 1m12s
Deploy to Production / Validate Build & Lint (documentation) (push) Successful in 1m46s
Deploy to Production / Validate Build & Lint (frontend) (push) Successful in 1m46s
Deploy to Production / Deploy to Production (push) Has been skipped
2026-01-20 10:53:11 +01:00
Mathis HERRIOT
e84e4a5a9d chore(ci): add new workflow for linting and testing components 2026-01-20 10:52:58 +01:00
Mathis HERRIOT
e74973a9d0 chore(ci): update deploy workflow to include tag-based triggers and conditional testing steps 2026-01-20 10:52:48 +01:00
Mathis HERRIOT
9233c1bf89 feat(versioning): add support for SemVer increment commands (PATCH, MINOR, MAJOR) in version script 2026-01-20 10:52:41 +01:00
Mathis HERRIOT
88c7f45a2c chore(ci): remove unused backend and lint workflows to clean up repository 2026-01-20 10:52:27 +01:00
Mathis HERRIOT
9af72156f5 chore: remove unused .output.txt file to clean up repository 2026-01-20 10:51:48 +01:00
Mathis HERRIOT
597a4d615e Changement système de branches: passage à main et unification des versions via CMake
All checks were successful
Lint / lint (backend) (push) Successful in 1m18s
Backend Tests / test (push) Successful in 1m18s
Lint / lint (documentation) (push) Successful in 1m18s
Lint / lint (frontend) (push) Successful in 1m15s
2026-01-20 10:39:53 +01:00
Mathis HERRIOT
2df45af305 style(logging): reformat hashed IP computation for improved readability
All checks were successful
Lint / lint (documentation) (push) Successful in 1m18s
Lint / lint (backend) (push) Successful in 1m21s
Backend Tests / test (push) Successful in 1m23s
Lint / lint (frontend) (push) Successful in 1m10s
Lint / lint (backend) (pull_request) Successful in 1m20s
Lint / lint (documentation) (pull_request) Successful in 1m22s
Backend Tests / test (pull_request) Successful in 1m24s
Lint / lint (frontend) (pull_request) Successful in 1m10s
2026-01-20 10:01:40 +01:00
Mathis HERRIOT
863a4bf528 style(app): reformat middleware configuration for improved readability
Some checks failed
Lint / lint (backend) (push) Failing after 52s
Backend Tests / test (push) Successful in 1m15s
Lint / lint (frontend) (push) Successful in 1m10s
Lint / lint (documentation) (push) Successful in 2m39s
2026-01-20 09:58:10 +01:00
Mathis HERRIOT
9a1cdb05a4 fix(auth): adjust 2FA verification log formatting for consistency 2026-01-20 09:57:59 +01:00
Mathis HERRIOT
28caf92f9a fix(media): update S3 file info type casting for stricter type safety
Replace `any` with `BucketItemStat` for `getFileInfo` response in MediaController to ensure accurate type definition.
2026-01-20 09:57:38 +01:00
Mathis HERRIOT
8b2728dc5a test(s3): update mock implementation types for stricter type safety
Refactor mock implementations in S3 service tests to replace `any` with `unknown` for improved type safety and consistency.
2026-01-20 09:57:27 +01:00
Mathis HERRIOT
3bbbbc307f test(media): fix type casting in MediaController unit tests
Update type casting for `Response` object in MediaController tests to use `unknown as Response` for stricter type safety. Remove unused `s3Service` variable for cleanup.
2026-01-20 09:57:11 +01:00
Mathis HERRIOT
f080919563 fix(logging): resolve type issue in hashed IP logging
Ensure `ip` parameter is explicitly cast to string before creating a SHA-256 hash to prevent runtime errors.
2026-01-20 09:56:44 +01:00
19 changed files with 341 additions and 120 deletions

View File

@@ -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

View File

@@ -1,13 +1,18 @@
name: Deploy to Production
# Pipeline CI/CD pour Gitea Actions (Forgejo)
# Compatible avec GitHub Actions pour la portabilité
name: CI/CD Pipeline
on:
push:
branches:
- prod
- '**'
tags:
- 'v*'
pull_request:
jobs:
validate:
name: Validate Build & Lint
name: Valider ${{ matrix.component }}
runs-on: ubuntu-latest
strategy:
matrix:
@@ -16,23 +21,23 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Install pnpm
- name: Installer pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
- name: Configurer Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Get pnpm store directory
- name: Obtenir le chemin du store pnpm
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> "${GITEA_OUTPUT:-$GITHUB_OUTPUT}"
- name: Setup pnpm cache
- name: Configurer le cache pnpm
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
@@ -40,26 +45,43 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
- name: Installer les dépendances
run: pnpm install --frozen-lockfile --prefer-offline
- name: Lint ${{ matrix.component }}
run: pnpm -F @memegoat/${{ matrix.component }} lint
- name: Tester ${{ 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 "Pas de script de test trouvé pour ${{ matrix.component }}, passage."
fi
- name: Build ${{ matrix.component }}
run: pnpm -F @memegoat/${{ matrix.component }} build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
deploy:
name: Deploy to Production
name: Déploiement en Production
needs: validate
# Déclenchement uniquement sur push sur main ou tag de version
# Gitea supporte le contexte 'github' pour la compatibilité
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy with Docker Compose
- name: Vérifier l'environnement Docker
run: |
docker version
docker compose version
- name: Déployer avec Docker Compose
run: |
docker compose -f docker-compose.prod.yml up -d --build
env:

View File

@@ -1,43 +0,0 @@
name: Lint
on:
push:
paths:
- 'frontend/**'
- 'backend/**'
- 'documentation/**'
pull_request:
paths:
- 'frontend/**'
- 'backend/**'
- 'documentation/**'
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
component: [backend, frontend, documentation]
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: Lint ${{ matrix.component }}
run: pnpm -F @memegoat/${{ matrix.component }} lint

50
ROADMAP.md Normal file
View 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

View File

@@ -1,6 +1,6 @@
{
"name": "@memegoat/backend",
"version": "0.0.1",
"version": "0.1.0",
"description": "",
"author": "",
"private": true,

View File

@@ -77,6 +77,8 @@ import { UsersModule } from "./users/users.module";
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(HTTPLoggerMiddleware, CrawlerDetectionMiddleware).forRoutes("*");
consumer
.apply(HTTPLoggerMiddleware, CrawlerDetectionMiddleware)
.forRoutes("*");
}
}

View File

@@ -169,7 +169,9 @@ export class AuthService {
const isValid = authenticator.verify({ token, secret });
if (!isValid) {
this.logger.warn(`2FA verification failed for user ${userId}: invalid token`);
this.logger.warn(
`2FA verification failed for user ${userId}: invalid token`,
);
throw new UnauthorizedException("Invalid 2FA token");
}

View File

@@ -1,6 +1,6 @@
import { createHash } from "node:crypto";
import { Injectable, Logger, NestMiddleware } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";
import { createHash } from "node:crypto";
@Injectable()
export class HTTPLoggerMiddleware implements NestMiddleware {
@@ -16,7 +16,9 @@ export class HTTPLoggerMiddleware implements NestMiddleware {
const contentLength = response.get("content-length");
const duration = Date.now() - startTime;
const hashedIp = createHash("sha256").update(ip).digest("hex");
const hashedIp = createHash("sha256")
.update(ip as string)
.digest("hex");
const message = `${method} ${originalUrl} ${statusCode} ${contentLength || 0} - ${userAgent} ${hashedIp} +${duration}ms`;
if (statusCode >= 500) {

View File

@@ -1,12 +1,12 @@
import { Readable } from "node:stream";
import { NotFoundException } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import type { Response } from "express";
import { S3Service } from "../s3/s3.service";
import { MediaController } from "./media.controller";
describe("MediaController", () => {
let controller: MediaController;
let s3Service: S3Service;
const mockS3Service = {
getFileInfo: jest.fn(),
@@ -20,7 +20,6 @@ describe("MediaController", () => {
}).compile();
controller = module.get<MediaController>(MediaController);
s3Service = module.get<S3Service>(S3Service);
});
it("should be defined", () => {
@@ -31,7 +30,7 @@ describe("MediaController", () => {
it("should stream the file and set headers with path containing slashes", async () => {
const res = {
setHeader: jest.fn(),
} as any;
} as unknown as Response;
const stream = new Readable();
stream.pipe = jest.fn();
const key = "contents/user-id/test.webp";
@@ -52,7 +51,7 @@ describe("MediaController", () => {
it("should throw NotFoundException if file is not found", async () => {
mockS3Service.getFileInfo.mockRejectedValue(new Error("Not found"));
const res = {} as any;
const res = {} as unknown as Response;
await expect(controller.getFile("invalid", res)).rejects.toThrow(
NotFoundException,

View File

@@ -1,5 +1,6 @@
import { Controller, Get, NotFoundException, Param, Res } from "@nestjs/common";
import type { Response } from "express";
import type { BucketItemStat } from "minio";
import { S3Service } from "../s3/s3.service";
@Controller("media")
@@ -9,7 +10,7 @@ export class MediaController {
@Get("*key")
async getFile(@Param("key") key: string, @Res() res: Response) {
try {
const stats = (await this.s3Service.getFileInfo(key)) as any;
const stats = (await this.s3Service.getFileInfo(key)) as BucketItemStat;
const stream = await this.s3Service.getFile(key);
const contentType =

View File

@@ -197,7 +197,7 @@ describe("S3Service", () => {
it("should use DOMAIN_NAME and PORT for localhost", () => {
(configService.get as jest.Mock).mockImplementation(
(key: string, def: any) => {
(key: string, def: unknown) => {
if (key === "API_URL") return null;
if (key === "DOMAIN_NAME") return "localhost";
if (key === "PORT") return 3000;
@@ -210,7 +210,7 @@ describe("S3Service", () => {
it("should use api.DOMAIN_NAME for production", () => {
(configService.get as jest.Mock).mockImplementation(
(key: string, def: any) => {
(key: string, def: unknown) => {
if (key === "API_URL") return null;
if (key === "DOMAIN_NAME") return "memegoat.fr";
return def;

View File

@@ -82,6 +82,11 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
Récupère les informations détaillées de l'utilisateur connecté. Requiert l'authentification.
</Accordion>
<Accordion title="GET /users/public/:username">
Récupère le profil public d'un utilisateur par son nom d'utilisateur.
**Réponse :** `id`, `username`, `displayName`, `avatarUrl`, `createdAt`.
</Accordion>
<Accordion title="GET /users/me/export">
Extrait l'intégralité des données de l'utilisateur au format JSON (Conformité RGPD).
Contient le profil, les contenus et les favoris.
@@ -89,7 +94,22 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
<Accordion title="PATCH /users/me">
Met à jour les informations du profil.
**Corps :**
- `displayName` (string)
- `bio` (string)
</Accordion>
<Accordion title="POST /users/me/avatar">
Met à jour l'avatar de l'utilisateur.
**Type :** `multipart/form-data`
**Champ :** `file` (Image)
</Accordion>
<Accordion title="PATCH /users/me/consent">
Met à jour les consentements légaux de l'utilisateur.
**Corps :**
- `termsVersion` (string)
- `privacyVersion` (string)
</Accordion>
<Accordion title="DELETE /users/me">
@@ -105,9 +125,9 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
- `POST /users/me/2fa/disable` : Désactive avec jeton.
</Accordion>
<Accordion title="Administration (GET /users/admin)">
Liste tous les utilisateurs. Réservé aux administrateurs.
**Params :** `limit`, `offset`.
<Accordion title="Administration (Admin uniquement)">
- `GET /users/admin` : Liste tous les utilisateurs (avec pagination `limit`, `offset`).
- `DELETE /users/:uuid` : Supprime définitivement un utilisateur par son UUID.
</Accordion>
</Accordions>
@@ -118,12 +138,15 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
Recherche et filtre les contenus. Ces endpoints sont mis en cache (Redis + Navigateur).
**Query Params :**
- `limit` (number) : Défaut 10.
- `offset` (number) : Défaut 0.
- `sort` : `trend` | `recent` (uniquement sur `/explore`)
- `tag` (string)
- `category` (slug ou id)
- `author` (username)
- `query` (titre)
- `favoritesOnly` (bool)
- `tag` (string) : Filtrer par tag.
- `category` (slug ou id) : Filtrer par catégorie.
- `author` (username) : Filtrer par auteur.
- `query` (titre) : Recherche textuelle.
- `favoritesOnly` (bool) : Ne montrer que les favoris de l'utilisateur connecté.
- `userId` (uuid) : Filtrer les contenus d'un utilisateur spécifique.
</Accordion>
<Accordion title="GET /contents/:idOrSlug">
@@ -133,8 +156,13 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
Si l'User-Agent correspond à un robot d'indexation (Googlebot, Twitterbot, etc.), l'API retourne un rendu HTML minimal contenant les méta-tags **OpenGraph** et **Twitter Cards** pour un partage optimal. Pour les autres clients, les données sont retournées en JSON.
</Accordion>
<Accordion title="POST /contents">
Crée une entrée de contenu (sans upload de fichier direct). Utile pour référencer des URLs externes.
**Corps :** `title`, `description`, `url`, `type`, `categoryId`, `tags`.
</Accordion>
<Accordion title="POST /contents/upload">
Upload un fichier avec traitement automatique.
Upload un fichier avec traitement automatique par le serveur.
**Type :** `multipart/form-data`
**Champs :**
@@ -145,6 +173,11 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
- `tags`? : string[]
</Accordion>
<Accordion title="POST /contents/upload-url">
Génère une URL présignée pour un upload direct vers S3.
**Query Param :** `fileName` (string).
</Accordion>
<Accordion title="POST /contents/:id/view | /use">
Incrémente les statistiques de vue ou d'utilisation.
</Accordion>
@@ -152,6 +185,10 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
<Accordion title="DELETE /contents/:id">
Supprime un contenu (Soft Delete). Doit être l'auteur.
</Accordion>
<Accordion title="DELETE /contents/:id/admin">
Supprime définitivement un contenu. **Réservé aux administrateurs.**
</Accordion>
</Accordions>
### 📂 Catégories, ⭐ Favoris, 🚩 Signalements
@@ -159,19 +196,23 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
<Accordions>
<Accordion title="Catégories (/categories)">
- `GET /categories` : Liste toutes les catégories.
- `GET /categories/:id` : Détails d'une catégorie.
- `POST /categories` : Création (Admin uniquement).
- `PATCH /categories/:id` : Mise à jour (Admin uniquement).
- `DELETE /categories/:id` : Suppression (Admin uniquement).
</Accordion>
<Accordion title="Favoris (/favorites)">
- `GET /favorites` : Liste les favoris de l'utilisateur.
Requiert l'authentification.
- `GET /favorites` : Liste les favoris de l'utilisateur (avec pagination `limit`, `offset`).
- `POST /favorites/:contentId` : Ajoute un favori.
- `DELETE /favorites/:contentId` : Retire un favori.
</Accordion>
<Accordion title="Signalements (/reports)">
- `POST /reports` : Signale un contenu ou un tag.
- `GET /reports` : Liste (Modérateurs).
- `PATCH /reports/:id/status` : Gère le workflow.
- `GET /reports` : Liste des signalements (Pagination `limit`, `offset`). **Admin/Modérateurs**.
- `PATCH /reports/:id/status` : Change le statut (`pending`, `resolved`, `dismissed`). **Admin/Modérateurs**.
</Accordion>
</Accordions>
@@ -185,7 +226,23 @@ Cette page documente tous les points de terminaison disponibles sur l'API Memego
</Accordion>
<Accordion title="Tags (/tags)">
- `GET /tags` : Recherche de tags populaires ou récents.
**Params :** `query`, `sort`, `limit`.
- `GET /tags` : Recherche de tags.
- **Params :** `query` (recherche), `sort` (`popular` | `recent`), `limit`, `offset`.
</Accordion>
</Accordions>
### 🛠️ Système & Médias
<Accordions>
<Accordion title="Santé (/health)">
- `GET /health` : Vérifie l'état de l'API et de la connexion à la base de données.
</Accordion>
<Accordion title="Médias (/media)">
- `GET /media/*key` : Accès direct aux fichiers stockés sur S3. Supporte la mise en cache agressive.
</Accordion>
<Accordion title="Administration (/admin)">
- `GET /admin/stats` : Récupère les statistiques globales de la plateforme. **Admin uniquement**.
</Accordion>
</Accordions>

View File

@@ -20,6 +20,13 @@ Le système utilise plusieurs méthodes d'authentification sécurisées pour ré
<Card title="Double Authentification" description="Support TOTP natif avec secret chiffré PGP pour une sécurité maximale." />
</Cards>
### Webhooks / Services Externes
### Stockage & Médias (S3)
Liste des intégrations tierces.
Memegoat utilise une architecture de stockage d'objets compatible S3 (MinIO). Les interactions se font de deux manières :
1. **Proxification Backend** : Pour l'accès public via `/media/*`.
2. **URLs Présignées** : Pour l'upload sécurisé direct depuis le client (via `/contents/upload-url`).
### Notifications (Mail)
Le système intègre un service d'envoi d'emails (SMTP) pour les notifications critiques et la gestion des comptes.

View File

@@ -35,10 +35,13 @@ erDiagram
string username
string email
string display_name
string avatar_url
string bio
string status
}
CONTENT {
string title
string slug
string type
string storage_key
}
@@ -82,6 +85,8 @@ erDiagram
bytea email
varchar email_hash
varchar display_name
varchar avatar_url
varchar bio
varchar password_hash
user_status status
bytea two_factor_secret
@@ -100,6 +105,7 @@ erDiagram
uuid category_id FK
content_type type
varchar title
varchar slug
varchar storage_key
varchar mime_type
integer file_size
@@ -233,6 +239,8 @@ erDiagram
varchar email_hash "UNIQUE, INDEXED"
varchar username "UNIQUE, NOT NULL"
varchar password_hash "NOT NULL"
varchar avatar_url "NULLABLE"
varchar bio "NULLABLE"
bytea two_factor_secret "ENCRYPTED"
boolean is_two_factor_enabled "DEFAULT false"
timestamp gdpr_accepted_at "NULLABLE"
@@ -241,6 +249,7 @@ erDiagram
contents {
uuid id "DEFAULT gen_random_uuid()"
uuid user_id "REFERENCES users(uuid)"
varchar slug "UNIQUE, NOT NULL"
varchar storage_key "UNIQUE, NOT NULL"
integer file_size "NOT NULL"
timestamp deleted_at "SOFT DELETE"

View File

@@ -1,6 +1,6 @@
{
"name": "@memegoat/documentation",
"version": "0.0.1",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "next build",

View File

@@ -1,6 +1,6 @@
{
"name": "@memegoat/frontend",
"version": "0.0.1",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",

30
frontend/todo.md Normal file
View 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

View File

@@ -1,8 +1,13 @@
{
"name": "@memegoat/source",
"version": "0.0.1",
"version": "0.1.0",
"description": "",
"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:front": "pnpm run -F @memegoat/frontend build",
"build:back": "pnpm run -F @memegoat/backend build",

114
version.cmake Normal file
View File

@@ -0,0 +1,114 @@
# 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)
if(CURRENT_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)")
set(MAJOR ${CMAKE_MATCH_1})
set(MINOR ${CMAKE_MATCH_2})
set(PATCH ${CMAKE_MATCH_3})
else()
message(FATAL_ERROR "Format de version invalide: ${CURRENT_VERSION}. Attendu: X.Y.Z")
endif()
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()
# Créer le tag git
create_git_tag(${NEW_VERSION})
endfunction()
# Logique principale
if(CMAKE_SCRIPT_MODE_FILE STREQUAL CMAKE_CURRENT_LIST_FILE)
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()
endif()