Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
alwaysApply: true
---

You are an expert full-stack developer proficient in TypeScript, React, Next.js, and modern UI/UX frameworks (e.g., Tailwind CSS, Shadcn UI, Radix UI). Your task is to produce the most optimized and maintainable Next.js code, following best practices and adhering to the principles of clean code and robust architecture.

### Objective
- Create a Next.js solution that is not only functional but also adheres to the best practices in performance, security, and maintainability.

### Code Style and Structure
- Write concise, technical TypeScript code with accurate examples.
- Use functional and declarative programming patterns; avoid classes.
- Favor iteration and modularization over code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., `isLoading`, `hasError`).
- Structure files with exported components, subcomponents, helpers, static content, and types.
- Use lowercase with dashes for directory names (e.g., `components/auth-wizard`).

### Optimization and Best Practices
- Minimize the use of `'use client'`, `useEffect`, and `setState`; favor React Server Components (RSC) and Next.js SSR features.
- Implement dynamic imports for code splitting and optimization.
- Use responsive design with a mobile-first approach.
- Optimize images: use WebP format, include size data, implement lazy loading.

### Error Handling and Validation
- Prioritize error handling and edge cases:
- Use early returns for error conditions.
- Implement guard clauses to handle preconditions and invalid states early.
- Use custom error types for consistent error handling.

### UI and Styling
- Use modern UI frameworks (e.g., Tailwind CSS, Shadcn UI, Radix UI) for styling.
- Implement consistent design and responsive patterns across platforms.

### State Management and Data Fetching
- Use modern state management solutions (e.g., Zustand, TanStack React Query) to handle global state and data fetching.
- Implement validation using Zod for schema validation.

### Security and Performance
- Implement proper error handling, user input validation, and secure coding practices.
- Follow performance optimization techniques, such as reducing load times and improving rendering efficiency.

### Testing and Documentation
- Write unit tests for components using Jest and React Testing Library.
- Provide clear and concise comments for complex logic.
- Use JSDoc comments for functions and components to improve IDE intellisense.

### Methodology
1. **System 2 Thinking**: Approach the problem with analytical rigor. Break down the requirements into smaller, manageable parts and thoroughly consider each step before implementation.
2. **Tree of Thoughts**: Evaluate multiple possible solutions and their consequences. Use a structured approach to explore different paths and select the optimal one.
3. **Iterative Refinement**: Before finalizing the code, consider improvements, edge cases, and optimizations. Iterate through potential enhancements to ensure the final solution is robust.

**Process**:
1. **Deep Dive Analysis**: Begin by conducting a thorough analysis of the task at hand, considering the technical requirements and constraints.
2. **Planning**: Develop a clear plan that outlines the architectural structure and flow of the solution, using <PLANNING> tags if necessary.
3. **Implementation**: Implement the solution step-by-step, ensuring that each part adheres to the specified best practices.
4. **Review and Optimize**: Perform a review of the code, looking for areas of potential optimization and improvement.
5. **Finalization**: Finalize the code by ensuring it meets all requirements, is secure, and is performant.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ data/
!README.md
.husky
.vscode
.cursor
.idea
CHANGELOG.md
AGENTS.md
Expand Down
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false
10 changes: 7 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Copier vers .env.local (dev) ou .env (Docker) puis renseigner les valeurs.
# Ne jamais committer de fichier .env contenant des secrets.

# Obligatoire — signature des sessions (min. 16 caractères)
# Obligatoire — signature des sessions (min. 16 caractères en dev, 32 en production)
# Générer : openssl rand -hex 32
AUTH_SECRET=

# Optionnel — cookie Secure (true/false). Par défaut : true en production
# ou si le proxy envoie X-Forwarded-Proto=https.
# ou, avec TRUST_PROXY=true, si le proxy envoie X-Forwarded-Proto=https.
# COOKIE_SECURE=true

# Optionnel — chemin de la base SQLite (défaut : data/hobbyhoops.db)
# Optionnel — chemin de la base SQLite dans le projet (défaut : data/hobbyhoops.db)
# HOBBYHOOPS_DB_PATH=data/hobbyhoops.db

# Optionnel — faire confiance aux en-têtes X-Forwarded-For / X-Real-IP.
# À activer uniquement derrière un reverse proxy qui réécrit ces en-têtes.
# TRUST_PROXY=false
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ updates:
directory: /
schedule:
interval: weekly

- package-ecosystem: docker
directory: /
schedule:
interval: weekly
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: [main, master]
branches: [main]
pull_request:
branches: [main, master]
branches: [main]

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
Expand All @@ -16,17 +16,17 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version-file: .nvmrc
cache: npm

- run: npm ci

- name: Contrôles qualité (lint, typecheck, audit)
run: npm run ci
run: npm run ci:full

docker:
runs-on: ubuntu-latest
Expand All @@ -35,26 +35,26 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- uses: docker/setup-buildx-action@v4
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd

- uses: docker/login-action@v4
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
if: github.event_name == 'pull_request'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/metadata-action@v6
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=pr
type=sha,prefix=sha-

- uses: docker/build-push-action@v7
- uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f
with:
context: .
# Sur main : build de validation uniquement. Les tags semver + latest sont poussés par release-docker.yml.
Expand All @@ -74,11 +74,11 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with:
node-version-file: .nvmrc
cache: npm
Expand Down
29 changes: 23 additions & 6 deletions .github/workflows/release-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,49 @@ jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.event.release.tag_name }}
fetch-depth: 0

- uses: docker/setup-buildx-action@v4
- name: Valider le tag de release
id: release
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
IS_PRERELEASE: ${{ github.event.release.prerelease }}
run: |
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Tag de release invalide: $RELEASE_TAG" >&2
exit 1
fi

- uses: docker/login-action@v4
if [[ "$IS_PRERELEASE" == "false" && "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "latest=true" >> "$GITHUB_OUTPUT"
else
echo "latest=false" >> "$GITHUB_OUTPUT"
fi

- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd

- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Sur l'événement `release`, GITHUB_REF n'est pas toujours refs/tags/* :
# il faut passer explicitement le tag semantic-release (ex. v1.2.3).
- uses: docker/metadata-action@v6
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ github.event.release.tag_name }}
type=semver,pattern={{version}},value=${{ github.event.release.tag_name }}
type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }}
type=raw,value=latest
type=raw,value=latest,enable=${{ steps.release.outputs.latest == 'true' }}

- uses: docker/build-push-action@v7
- uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f
with:
context: .
push: true
Expand Down
6 changes: 6 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss"
]
}
12 changes: 8 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# ── Étape 1 : dépendances ─────────────────────────────────────────────────────
FROM node:24-alpine AS deps
FROM node:24-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f AS deps
WORKDIR /app

RUN apk add --no-cache python3 make g++

COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
RUN HUSKY=0 npm ci

# ── Étape 2 : build ───────────────────────────────────────────────────────────
FROM node:24-alpine AS builder
FROM node:24-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f AS builder
WORKDIR /app

ENV NEXT_TELEMETRY_DISABLED=1
Expand All @@ -17,9 +19,11 @@ COPY . .
RUN npm run build

# ── Étape 3 : image de production ─────────────────────────────────────────────
FROM node:24-alpine AS runner
FROM node:24-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f AS runner
WORKDIR /app

RUN apk add --no-cache libstdc++

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Application Next.js pour gérer une collection de cartes NBA : collection, fiche

## Prérequis

- Node.js 22.5 ou plus récent (24 recommandé, voir `.nvmrc`)
- Node.js 24 (voir `.nvmrc`)
- npm

## Démarrage local
Expand All @@ -29,7 +29,8 @@ Fichiers de traduction : `messages/fr.json`, `messages/en.json`.
## Scripts utiles

```bash
npm run ci # même suite que la job GitHub « quality » (recommandé avant push)
npm run ci # contrôles locaux rapides (recommandé avant push)
npm run ci:full # lint + typecheck + audit npm high+ (utilisé par GitHub Actions)
npm run check # alias de npm run ci
npm run lint
npm run typecheck
Expand All @@ -40,7 +41,7 @@ npm run clean # supprime .next, caches TypeScript, etc.

## Données

Tout est stocké dans **`data/hobbyhoops.db`** (SQLite) : collection, références, comptes et sessions. Ce fichier et ses journaux WAL sont **locaux** et ne doivent **jamais** être commités.
Tout est stocké dans **`data/hobbyhoops.db`** (SQLite via `better-sqlite3`) : collection, références, comptes, sessions et rate limiting. Ce fichier et ses journaux WAL sont **locaux** et ne doivent **jamais** être commités.

Au premier lancement, la base est créée vide ; créez le compte administrateur via l’écran de connexion.

Expand All @@ -60,19 +61,20 @@ L’application écoute sur `127.0.0.1:3000`. Les données persistent dans le do
Le conteneur tourne sous l’utilisateur système **`hobbyhoops`** (UID/GID **1111**). Le répertoire `data/` doit être inscriptible par cet utilisateur (voir `chown` ci-dessus).

En production derrière un reverse proxy HTTPS, laisser `COOKIE_SECURE` à sa valeur par défaut ou forcez `COOKIE_SECURE=true`.
Si le proxy réécrit strictement `X-Forwarded-For` / `X-Real-IP`, activez `TRUST_PROXY=true` pour appliquer le rate limit par IP réelle. Sinon, laissez la valeur par défaut afin d’éviter les en-têtes spoofés.

Image de production (après chaque release semantic-release) : `ghcr.io/<organisation>/hobbyhoops:latest`, `ghcr.io/<organisation>/hobbyhoops:1.2.0`, etc.

## CI GitHub Actions

Le workflow `.github/workflows/ci.yml` exécute sur chaque push et pull request vers `main` ou `master` :

- `npm ci` puis `npm run ci` (Node, ESLint, TypeScript, audit npm high+)
- `npm ci` puis `npm run ci:full` (Node, ESLint, TypeScript, audit npm high+)
- build de l’image Docker (validation sur `main`, push d’images de test sur les PR)
- sur **push vers `main` uniquement** : **semantic-release** (tag Git `vX.Y.Z`, release GitHub, `CHANGELOG.md`, bump de `package.json`)
- à chaque **GitHub Release publiée** : workflow `release-docker.yml` pousse `ghcr.io/<organisation>/hobbyhoops:X.Y.Z` et `:latest` (aligné sur le tag semantic-release)

En local, lancez la même commande avant de pousser : `npm run ci`.
En local, lancez `npm run ci` avant de pousser. L’audit réseau complet reste disponible avec `npm run ci:full`.

### Versions (semantic-release)

Expand Down Expand Up @@ -105,6 +107,7 @@ Ne poussez jamais `.env`, `.env.local`, les comptes locaux ni la base SQLite de

## Qualité du code

- **`npm run ci`** : contrôles identiques à la CI (lint, typecheck, audit)
- **`npm run ci`** : contrôles locaux sans dépendance réseau (Node, lint, typecheck)
- **`npm run ci:full`** : contrôles GitHub Actions avec audit npm high+
- **Husky** : `pre-commit` (ESLint sur les fichiers stagés), `pre-push` (`npm run ci`), `commit-msg` (commitlint conventional)
- **Dependabot** : mises à jour hebdomadaires npm et GitHub Actions
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ N’ouvrez pas d’issue publique pour un problème de sécurité. Contactez le

## Bonnes pratiques de déploiement

- Définir `AUTH_SECRET` avec au moins 16 caractères aléatoires (`openssl rand -hex 32`).
- Définir `AUTH_SECRET` avec une valeur forte (`openssl rand -hex 32` recommandé ; 32 caractères minimum en production).
- Ne jamais committer `.env` ni `data/hobbyhoops.db` (ni ses fichiers WAL associés).
- Exposer l’application derrière un reverse proxy HTTPS ; laisser `COOKIE_SECURE` par défaut ou forcer `true`.
- Limiter l’accès réseau au conteneur (port bindé sur `127.0.0.1` dans `docker-compose.yml`).
Expand All @@ -18,6 +18,6 @@ Tout utilisateur authentifié peut accéder à `/admin` et aux API de modificati

## Limitations connues

- Limite de débit en mémoire (par processus) : insuffisante en déploiement multi-réplicas sans store partagé.
- Limite de débit persistée dans SQLite : suffisante pour une instance unique, à remplacer par un store partagé en déploiement multi-réplicas.
- CSP stricte avec nonces (`src/proxy.ts`) : en développement seulement, `'unsafe-eval'` (React DevTools) et `'unsafe-inline'` pour les styles ; absent en production.
- Langues : français (défaut) et anglais (US), cookie `hh_locale`, commutation via la barre latérale ou l’écran de connexion.
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ services:
AUTH_SECRET: ${AUTH_SECRET}
# Cookie de session en HTTPS (true/false). Par défaut : true en production ou si X-Forwarded-Proto=https.
# COOKIE_SECURE: "true"
# Faire confiance à X-Forwarded-For / X-Real-IP uniquement derrière un reverse proxy qui les réécrit.
TRUST_PROXY: ${TRUST_PROXY:-false}
volumes:
# Persistance SQLite + JSON (fichiers vides créés au premier démarrage si absents).
- ./data:/app/data
Expand Down
7 changes: 7 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"error": "Error",
"previous": "Previous",
"next": "Next",
"firstPage": "First page",
"previousPage": "Previous page",
"nextPage": "Next page",
"lastPage": "Last page",
"cards": "cards",
"cardsLabel": "Cards",
"pageOf": "Page {page} of {total}",
Expand Down Expand Up @@ -202,6 +206,8 @@
"addCard": "Add card",
"search": "Search cards…",
"actions": "Actions",
"editCard": "Edit card {player}",
"deleteCard": "Delete card {player}",
"brandSet": "Brand / Set",
"deleteTitle": "Delete card",
"deleteConfirm": "Permanently delete this card? This cannot be undone.",
Expand Down Expand Up @@ -275,6 +281,7 @@
"errors": {
"unauthorized": "Unauthorized",
"invalidRequest": "Invalid request.",
"crossSiteRequest": "Cross-site request rejected.",
"invalidJson": "Invalid JSON.",
"authMisconfigured": "Invalid authentication configuration.",
"loginRateLimit": "Too many attempts. Try again later.",
Expand Down
Loading