No description
- JavaScript 62.4%
- CSS 26.3%
- HTML 9.7%
- PowerShell 1.4%
- Dockerfile 0.2%
| data | ||
| db | ||
| docs | ||
| models | ||
| public | ||
| routes | ||
| scripts | ||
| services | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| CHANGELOG.md | ||
| config.js | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| PUBLISH.md | ||
| README.md | ||
| ROADMAP.md | ||
| server.js | ||
My Library
My Library est un projet personnel qui a pour but de garder à jour l'avancement dans les animes, les films, les webtoons, etc...
Stack technique
| Couche | Technologie | |
|---|---|---|
| Frontend | HTML / CSS / JS Vanilla | |
| Backend | Node.js + Express | |
| Base de données | SQLite (better-sqlite3) + sauvegarde JSON |
|
| Auth | Token HMAC (mode Propriétaire/Visiteur) avec timingSafeEqual |
|
| Services | Couche métier entre les routes et les modèles (services/) |
|
| UI | CSS Vanilla (Mobile First) | |
| Upload | Multer (png/jpg, max 5 Mo) |
Prérequis & Installation
Prérequis
- Node.js 22 ou supérieur
- npm (inclus avec Node.js)
Installation
npm install
cp .env.example .env
Éditez .env :
| Variable | Rôle | Obligatoire |
|---|---|---|
HOST |
Hôte d'écoute (défaut : localhost) |
Non |
PORT |
Port d'écoute (défaut : 3000) | Non |
OWNER_PASSWORD |
Mot de passe propriétaire | Oui |
AUTH_SECRET |
Clé secrète pour le token HMAC | Oui |
DOCKER_USER |
Nom d'utilisateur Docker Hub | Non |
DOCKER_TOKEN |
Token ou mot de passe Docker Hub | Non |
Lancement
npm run dev
Ouvrez http://localhost:3000.
Docker
Build local
npm run docker:build
npm run docker:run
Via Docker Hub (pré-buildé)
docker compose -f docker-compose.hub.yml up -d
Une image pré-buildée est disponible sur Docker Hub : primorion/my-library, mise à jour plus ou moins en même temps que le dépôt git.
Schéma de base de données
CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
cover_image TEXT,
type TEXT NOT NULL CHECK(type IN ('anime','serie','webtoon','film','video','livre')),
status TEXT NOT NULL CHECK(status IN ('à regarder','en cours','terminé')) DEFAULT 'à regarder',
location TEXT, -- JSON: [{"name": "...", "url": "..."}]
rating REAL CHECK(rating >= 0 AND rating <= 100),
is_favorite INTEGER DEFAULT 0,
description TEXT,
metadata TEXT, -- JSON selon le type
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE folders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE folder_items (
folder_id INTEGER NOT NULL REFERENCES folders(id) ON DELETE CASCADE,
item_id INTEGER NOT NULL REFERENCES items(id) ON DELETE CASCADE,
PRIMARY KEY (folder_id, item_id)
);
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE item_tags (
item_id INTEGER NOT NULL REFERENCES items(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (item_id, tag_id)
);
-- Index pour la synchronisation
CREATE INDEX IF NOT EXISTS idx_folders_updated_at ON folders(updated_at);
CREATE INDEX IF NOT EXISTS idx_tags_updated_at ON tags(updated_at);
Métadonnées par type (JSON dans metadata)
| type | metadata |
|---|---|
anime, serie |
{"season": int, "episode": int, "timecode": {"hours": int, "minutes": int, "seconds": int}} |
webtoon |
{"season": int, "chapter": int} |
film, video |
{"timecode": {"hours": int, "minutes": int, "seconds": int}} |
livre |
{"volume": int, "chapter": int, "page": int} |
Contrainte : si status = 'en cours'
| type | Champs obligatoires |
|---|---|
| anime/serie | season + episode |
| webtoon | season + chapter |
| film/video | (timecode optionnel) |
| livre | volume + page (chapter optionnel) |
Routes API REST
POST /api/auth/verify Body: { password }
GET /api/items Query: ?type=&status=&folder_id=&tag=&search=&sort=&order=&page=&limit=
POST /api/items Body: { title, type, status, location, rating, is_favorite, description, metadata, folder_ids[], tag_ids[] }
GET /api/items/:id
PUT /api/items/:id
DELETE /api/items/:id → 204 No Content
POST /api/items/batch-delete Body: { ids: [1, 2, 3] }
GET /api/folders
POST /api/folders Body: { name }
PUT /api/folders/:id Body: { name }
DELETE /api/folders/:id
PUT /api/folders/:id/items Body: { item_ids: [1, 2, 3] }
GET /api/tags
POST /api/tags Body: { name }
PUT /api/tags/:id Body: { name }
DELETE /api/tags/:id
POST /api/items/:id/tags Body: { tag_ids: [1, 2] }
POST /api/upload multipart/form-data → { url: "/uploads/fichier.jpg" }
GET /api/export?format=json|csv
Arborescence du projet
my-library/
├── config.js # Configuration (port, host, auth)
├── server.js # Point d'entrée Express
├── package.json
├── .env.example
├── Dockerfile
├── docker-compose.yml
├── docker-compose.hub.yml
├── .dockerignore
├── .gitignore
├── db/
│ ├── schema.js # Création + migrations des tables SQLite
│ ├── database.js # Initialisation DB
│ └── backup.js # Export JSON/CSV
├── routes/
│ ├── items.js # CRUD items (délègue à services/)
│ ├── folders.js # CRUD dossiers
│ ├── tags.js # CRUD tags + item_tags
│ ├── upload.js # Upload d'image
│ ├── export.js # Export JSON/CSV
│ └── auth.js # Authentification propriétaire
├── services/
│ ├── itemService.js # Logique métier des items
│ └── fileService.js # Gestion des fichiers (cover)
├── models/
│ ├── item.js
│ ├── folder.js
│ └── tag.js
├── scripts/
│ ├── cleanup-roadmap.js # Nettoyage ROADMAP → CHANGELOG
│ └── migrate-db.js # Migration manuelle de la DB
├── public/
│ ├── index.html # Page principale
│ ├── style.css # Styles vanilla
│ ├── js/ # JS client modulaire
│ │ ├── app.js
│ │ ├── api.js
│ │ ├── auth.js
│ │ ├── folders.js
│ │ ├── items.js
│ │ ├── render.js
│ │ └── tags.js
│ └── uploads/ # Images uploadées
├── data/
│ └── library.db # Fichier SQLite (généré)
└── docs/
├── USER_GUIDE.md # Guide utilisateur complet
└── MOBILE.md # Documentation app mobile
Fonctionnalités
- Créer des dossiers (animes, films à voir, livres fantasy, etc...)
- Ajouter des items à un dossier (un item peut être dans plusieurs dossiers)
- Recherche et filtres (par type, statut, dossier, tag, texte)
- Upload d'image de couverture (png/jpg, 5 Mo max)
- Export JSON / CSV
- Tags libres avec table dédiée
- Mode Propriétaire/Visiteur — consultation seule vs accès complet (auth via token HMAC avec
timingSafeEqual) - Raccourcis clavier —
/(recherche),N(ajout),Échap(fermer) - Mode sombre — automatique selon le système, sauvegardé dans le navigateur
- Sélection multiple — supprimer en lot
- Docker — build et lancement via
npm run docker:* - Configuration — via
.env(host, port, mot de passe, secret) - Couche service — logique métier extraite des routes (
services/) - Migrations automatiques — la DB se met à jour au démarrage (
db/schema.js) - Comparaison sécurisée —
crypto.timingSafeEqualpour le mot de passe et le token
Roadmap & Changelog
Le fichier ROADMAP.md liste les bugs, modifications et fonctionnalités
à venir. Les entrées terminées (marquées ✅ Fait) sont déplacées vers
CHANGELOG.md via la commande :
npm run roadmap:cleanup # bump mineur (v1.0 → v1.1)
npm run roadmap:cleanup major # bump majeur (v1.1 → v2.0)
La version du ROADMAP est incrémentée à chaque nettoyage et la date est enregistrée dans l'en-tête. Le CHANGELOG conserve l'historique de toutes les entrées déplacées.