chore: initial commit
No files matched your search
@@ -0,0 +1,2 @@
|
||||
# Variables exposées au client (préfixées NEXT_PUBLIC_)
|
||||
# NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||
@@ -0,0 +1,51 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# Environnement (jamais committer les secrets)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Couverture de tests
|
||||
coverage
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
.next
|
||||
out
|
||||
coverage
|
||||
next-env.d.ts
|
||||
pnpm-lock.yaml
|
||||
public/*.png
|
||||
public/*.ico
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# Application web Flashcards
|
||||
|
||||
Flashcards est application web SPA PWA permet d'afficher des cartes sur des sujets de code, de développement ou de culture tech.
|
||||
|
||||
## Principe du jeu
|
||||
|
||||
Le jeu consiste à afficher une carte au hasard.
|
||||
une question ou un terme apparaît. La réponse est cachée. Si l'utilisateur souhaite consuletr la réponse, il peut faire dérouler la réponse.
|
||||
|
||||
Il est néanmoins possible de sélectionner une carte au hasard sur un thème plus précis : code, langage, etc...
|
||||
|
||||
L'utilisateur peut créer, modifier ou supprimer une carte (CRUD).
|
||||
|
||||
L'utilisatuer peut afficher la liste des cartes ou consulter une carte spécifique.
|
||||
|
||||
## Stack technique
|
||||
|
||||
- Framework : Next Js
|
||||
- Typescript
|
||||
- Tailwind
|
||||
- Eslint, Prettier
|
||||
- Test unitaires : Vitest avec coverage
|
||||
|
||||
### Base de données
|
||||
|
||||
Bien qu'il n'y ait réellement pas besoin de 2 SGBD, dans un but pédagogique, il faudra gérer les 2 bases de données simultanément.
|
||||
|
||||
Les composants d'accès aux données devront être le plus génériques possibles.
|
||||
|
||||
- Mysql (conteneur Docker)
|
||||
- Mongo Db (conteneur Docker)
|
||||
|
||||
### Déploiement
|
||||
|
||||
- Docker via docker-compose
|
||||
- Volumes managés
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) $(date +%Y)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,37 @@
|
||||
# flashcards
|
||||
|
||||
Application web PWA (Next.js 16 + React + TypeScript + Tailwind CSS 4 + Serwist).
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Node.js >= 20.19.0 (ou >= 22.12.0)
|
||||
- pnpm >= 9
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Commandes
|
||||
|
||||
| Commande | Description |
|
||||
| -------------------- | ------------------------------------------------------------- |
|
||||
| `pnpm dev` | Serveur de développement (Turbopack) |
|
||||
| `pnpm build` | Build de production + service worker (Serwist) |
|
||||
| `pnpm start` | Prévisualisation du build en local |
|
||||
| `pnpm test` | Tests unitaires (Vitest) |
|
||||
| `pnpm test:watch` | Tests en mode watch |
|
||||
| `pnpm test:coverage` | Tests + rapport de couverture (coverage/) |
|
||||
| `pnpm lint` | Lint ESLint |
|
||||
| `pnpm format` | Formatage Prettier |
|
||||
| `pnpm typecheck` | Vérification TypeScript |
|
||||
| `pnpm check` | Chaîne qualité : lint → format → typecheck → tests → coverage |
|
||||
|
||||
## Environnement
|
||||
|
||||
Copiez `.env.example` vers `.env` pour définir les variables (préfixe `NEXT_PUBLIC_`).
|
||||
|
||||
## Licence
|
||||
|
||||
MIT — voir [LICENSE](LICENSE).
|
||||
@@ -0,0 +1,179 @@
|
||||
# TODO — Application web Flashcards (développement assisté par IA)
|
||||
|
||||
Document de spécifications et checklist destiné à guider un agent IA dans le développement complet de l'application. Il complète le GOAL.md en le rendant actionnable.
|
||||
|
||||
---
|
||||
|
||||
## 1. Contexte produit
|
||||
|
||||
Application web **SPA + PWA** : Flashcards affiche des cartes (questions/réponses) sur des sujets de code, de développement ou de culture tech.
|
||||
|
||||
### Principe du jeu
|
||||
|
||||
- Affichage d'une **carte au hasard** : une question ou un terme apparaît, la réponse est cachée.
|
||||
- L'utilisateur peut **révéler la réponse** à la demande.
|
||||
- Possibilité de tirer une carte au hasard sur un **thème précis** (code, langage, etc.).
|
||||
|
||||
### Gestion des cartes
|
||||
|
||||
- L'utilisateur peut **créer, modifier ou supprimer** une carte (CRUD complet).
|
||||
- L'utilisateur peut **afficher la liste** des cartes ou **consulter une carte spécifique**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Exigences fonctionnelles
|
||||
|
||||
> Chaque exigence doit être satisfaite avant de considérer la feature terminée.
|
||||
|
||||
| Ref | Exigence | Critère d'acceptation |
|
||||
|-----|----------|------------------------|
|
||||
| F-01 | Afficher une carte au hasard | Un clic sur « Jouer » affiche UNE carte tirée aléatoirement de l'ensemble des cartes |
|
||||
| F-02 | Dans un tirage aléatoire, la question est visible et la réponse cachée | Le terme/question s'affiche ; la réponse ne s'affiche pas |
|
||||
| F-03 | Révéler la réponse | Un clic (ou action) déroule/affiche la réponse cachée ; un second clic la masque |
|
||||
| F-04 | Tirer une carte au hasard **par thème** | L'utilisateur choisit un thème ; le tirage se fait uniquement parmi les cartes de ce thème |
|
||||
| F-05 | Créer une carte | Formulaire de création avec question, réponse et thème ; la carte est persistée |
|
||||
| F-06 | Modifier une carte | Formulaire pré-rempli ; la modification est persistée |
|
||||
| F-07 | Supprimer une carte | Confirmation avant suppression ; la carte disparaît de la liste |
|
||||
| F-08 | Lister les cartes | La liste complète des cartes s'affiche (avec au minimum question + thème) |
|
||||
| F-09 | Consulter une carte spécifique | L'accès direct à une carte (via sa route/id) affiche son détail complet |
|
||||
| F-10 | Thèmes | L'utilisateur peut choisir un thème reflétant les cartes existantes |
|
||||
|
||||
---
|
||||
|
||||
## 3. Modélisation de données (proposition à valider)
|
||||
|
||||
### Entité `Card`
|
||||
|
||||
| Champ | Type | Contrainte | Description |
|
||||
|-------------|------------|-------------------|------------------------------------------|
|
||||
| `id` | string/INT | unique (PK) | Identifiant stable de la carte |
|
||||
| `question` | string | requis | Terme ou question affiché |
|
||||
| `response` | string | requis | Réponse cachée puis révélée |
|
||||
| `theme` | string | requis | Thème/catégorie (ex. « code », « javascript ») |
|
||||
| `tags` | string[] | optionnel | Mots-clés additionnels |
|
||||
| `createdAt` | datetime | auto | Date de création |
|
||||
| `updatedAt` | datetime | auto | Date de dernière modification |
|
||||
|
||||
> La liste des thèmes peut être dérivée dynamiquement des valeurs `theme` distinctes présentes en base (simplicité) ou gérée en entité `Theme` dédiée (robustesse). Choix à figer en phase de conception.
|
||||
|
||||
### Représentation duale SGBD
|
||||
|
||||
- **MySQL** : table `cards`
|
||||
- **MongoDB** : collection `cards`
|
||||
|
||||
Le même `id` doit permettre de retrouver une carte dans les deux SGBD (l'id métier n'est pas nécessairement l'id technique Mongo).
|
||||
|
||||
---
|
||||
|
||||
## 4. Exigences techniques et architecture
|
||||
|
||||
### Stack
|
||||
|
||||
- Framework : **Next.js**
|
||||
- Langage : **TypeScript** (strict)
|
||||
- Styles : **Tailwind**
|
||||
- Qualité : **ESLint**, **Prettier**
|
||||
- Tests unitaires : **Vitest** avec **coverage**
|
||||
|
||||
### Base de données — double SGBD simultané (contrainte pédagogique)
|
||||
|
||||
Bien qu'il n'y ait pas réellement besoin de 2 SGBD, dans un but pédagogique les **deux bases doivent être gérées simultanément** :
|
||||
|
||||
- **MySQL** (conteneur Docker)
|
||||
- **MongoDB** (conteneur Docker)
|
||||
|
||||
### Exigence architecturale majeure : couche d'accès aux données GÉNÉRIQUE
|
||||
|
||||
Les composants d'accès aux données doivent être **le plus générique possible**. Concrètement :
|
||||
|
||||
- Définir une **abstraction commune** (interface/repository générique, ex. `CardRepository`) exposant les opérations : `findAll`, `findById`, `create`, `update`, `delete`, `findRandom`, `findRandomByTheme`, `findThemes`.
|
||||
- Fournir **deux implémentations** de cette abstraction : une pour MySQL, une pour MongoDB.
|
||||
- **Aucun composant métier (service, route, UI) ne doit dépendre d'un SGBD précis** : il consomme uniquement l'abstraction.
|
||||
- Les deux implémentations doivent être utilisables et activables (par config/DI/injection), et les tests doivent couvrir les deux.
|
||||
|
||||
### Déploiement
|
||||
|
||||
- **Docker** via **docker-compose**
|
||||
- **Volumes nommés** pour la persistance des données des 2 SGBD
|
||||
- Services au minimum : `web` (Next.js), `mysql`, `mongo`
|
||||
|
||||
---
|
||||
|
||||
## 5. Définition de fait (Definition of Done)
|
||||
|
||||
Une feature est « faite » si et seulement si :
|
||||
|
||||
- [ ] Les tests unitaires (Vitest) passent, **coverage** généré et au-dessus du seuil défini
|
||||
- [ ] `lint` (ESLint) et `format` (Prettier — check) sans erreur
|
||||
- [ ] TypeScript strict compile sans erreur
|
||||
- [ ] L'implémentation est branchée sur la **couche générique d'accès aux données**
|
||||
- [ ] Les **deux SGBD** (MySQL + MongoDB) fonctionnent simultanément en local
|
||||
- [ ] Le comportement correspond aux critères d'acceptation de la section 2
|
||||
- [ ] Construit et lançable via docker-compose avec volumes persistants
|
||||
|
||||
---
|
||||
|
||||
## 6. Checklist de développement par phases
|
||||
|
||||
> Ordre d'exécution recommandé. Passer à la phase suivante uniquement si la DoD de la phase courante est remplie.
|
||||
|
||||
### Phase 0 — Scaffolding
|
||||
- [ ] Initialiser le projet Next.js + TypeScript (strict)
|
||||
- [ ] Configurer Tailwind
|
||||
- [ ] Configurer ESLint + Prettier
|
||||
- [ ] Configurer Vitest + rapport de coverage
|
||||
- [ ] Fixer le seuil de coverage (à définir, ex. 80%)
|
||||
- [ ] DoD : `lint`, `format`, `typecheck`, `test` s'exécutent sans erreur sur un squelette
|
||||
|
||||
### Phase 1 — Données et couche d'accès générique
|
||||
- [ ] Schéma MySQL (table `cards`) + migration/seed
|
||||
- [ ] Schéma MongoDB (collection `cards`) + seed
|
||||
- [ ] Interface générique `CardRepository` (contrat complet)
|
||||
- [ ] Implémentation `MysqlCardRepository`
|
||||
- [ ] Implémentation `MongoCardRepository`
|
||||
- [ ] Mécanisme d'activation/sélection de l'implémentation (config)
|
||||
- [ ] Tests unitaires des deux implémentations
|
||||
- [ ] DoD : phase 0 + les 2 repositories testés et interchangeables
|
||||
|
||||
### Phase 2 — API / services métier
|
||||
- [ ] Service métier `CardService` reposant uniquement sur l'abstraction repository
|
||||
- [ ] Routes API : liste, détail, création, modification, suppression
|
||||
- [ ] Routes API : tirage aléatoire simple + tirage aléatoire par thème + liste des thèmes
|
||||
- [ ] Validation des entrées (contrats TypeScript / schémas)
|
||||
- [ ] Gestion d'erreurs cohérente (404 carte inconnue, 400 payload invalide…)
|
||||
- [ ] Tests unitaires du service (mock du repository)
|
||||
- [ ] DoD : phases précédentes + contexte de MCP de l'API
|
||||
|
||||
### Phase 3 — Interface utilisateur
|
||||
- [ ] Page/vue **liste des cartes** (F-08)
|
||||
- [ ] Page/vue **détail d'une carte** (F-09)
|
||||
- [ ] Formulaire **création** (F-05)
|
||||
- [ ] Formulaire **édition** (F-06)
|
||||
- [ ] **Suppression** avec confirmation (F-07)
|
||||
- [ ] Vue **jeu** : tirage aléatoire global + sélecteur de thème (F-01, F-02, F-03, F-04, F-10)
|
||||
- [ ] Navigation/flux SPA cohérent (routes Next.js)
|
||||
- [ ] DoD : phases précédentes + parcours utilisateur complet fonctionnel
|
||||
|
||||
### Phase 4 — Tests
|
||||
- [ ] Tests des composants UI (composants critiques)
|
||||
- [ ] Couverture des critères F-01 à F-10
|
||||
- [ ] Coverage au seuil défini (Phase 0)
|
||||
- [ ] DoD : toutes phases précédentes vertes + coverage ok
|
||||
|
||||
### Phase 5 — PWA, déploiement
|
||||
- [ ] Manifest PWA + service worker (installable, hors-ligne) — la stack SPA le permet
|
||||
- [ ] `docker-compose.yml` : `web`, `mysql`, `mongo`
|
||||
- [ ] Volumes nommés pour l'ensemble des données
|
||||
- [ ] Variables/env de connexion aux 2 SGBD selon l'implémentation sélectionnée
|
||||
- [ ] Construction de l'image web et démarrage complet via `docker compose up`
|
||||
- [ ] Test de persistance après redémarrage des conteneurs
|
||||
- [ ] DoD finale pleinement vérifiée
|
||||
|
||||
---
|
||||
|
||||
## 7. Points à trancher / en attente de décision
|
||||
|
||||
- [ ] Couverture : seuil de coverage exact
|
||||
- [ ] Selon cas : `Theme` en table dédiée vs dérivation des `theme` distincts
|
||||
- [ ] Choix par défaut de l'implémentation repository pour la prod (MySQL ou Mongo)
|
||||
- [ ] Authentification éventuelle (hors périmètre README, à confirmer si nécessaire)
|
||||
@@ -0,0 +1,70 @@
|
||||
@startuml c4-container
|
||||
' C4 modèle — Niveau 2 : Conteneurs (syntaxe native PlantUML, compatible anciennes versions)
|
||||
' Application web Flashcards (SPA/PWA)
|
||||
|
||||
hide stereotype
|
||||
skinparam backgroundColor #FFFFFF
|
||||
|
||||
' ===== Styles par type =====
|
||||
skinparam rectangle<<person>> {
|
||||
BackgroundColor #08427B
|
||||
BorderColor #073B6F
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam rectangle<<system>> {
|
||||
'BackgroundColor #1168BD
|
||||
BorderColor #3C7FC0
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam rectangle<<container>> {
|
||||
BackgroundColor #438DD5
|
||||
BorderColor #135F97
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam rectangle<<external_system>> {
|
||||
BackgroundColor #999999
|
||||
BorderColor #8A8A8A
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam arrow {
|
||||
Color #707070
|
||||
}
|
||||
|
||||
title Conteneurs — Flashcards (Niveau 2)
|
||||
|
||||
' ===== Personne =====
|
||||
rectangle "Utilisateur\nNavigateur / mobile\n(accès au jeu et au CRUD)" <<person>> as User
|
||||
|
||||
' ===== Système Flashcards et ses conteneurs =====
|
||||
rectangle "Application Flashcards [Système]\nSPA/PWA Next.js — double persistance simultanée" <<system>> as Flashcards {
|
||||
|
||||
rectangle "SPA React (Next.js)\n[Conteneur : Web Browser]\nRendu de l'UI, état local, navigation" <<container>> as Spa
|
||||
|
||||
rectangle "API Next.js / App Router\n[Conteneur : Node.js]\nCardController > CardService :\nliste, CRUD, tirage aléatoire, thèmes\n(seul conteneur qui accède aux données)" <<container>> as Api
|
||||
|
||||
rectangle "CardRepository (générique)\n[Abstraction de données]\nContracts : findAll, findById,\nfindRandom, findRandomByTheme,\nfindThemes, create, update, delete" <<container>> as Repo
|
||||
|
||||
rectangle "Implémentation MySQL\n[Conteneur : librairie]\nImplémentation SQL du repository" <<container>> as RepoMysql
|
||||
rectangle "Implémentation MongoDB\n[Conteneur : librairie]\nImplémentation Mongo du repository" <<container>> as RepoMongo
|
||||
}
|
||||
|
||||
' ===== Systèmes externes =====
|
||||
rectangle "Base MySQL\n[Conteneur externe : SGBD]\nPersistance des cartes (table cards)\n+ stockage Docker avec volume" <<external_system>> as MySql
|
||||
rectangle "Base MongoDB\n[Conteneur externe : SGBD]\nPersistance des cartes (collection cards)\n+ stockage Docker avec volume" <<external_system>> as Mongo
|
||||
'rectangle "Docker / docker-compose\n[infrastructure]\nOrchestration des conteneurs\nweb + bases + volumes" <<external_system>> as Docker
|
||||
|
||||
' ===== Relations =====
|
||||
User -[#707070]-> Spa : "Navigue et interagit (HTTP/HTTPS)"
|
||||
Spa -[#707070]-> Api : "Appels API JSON (dialogues, CRUD,\ntirage, révélation de réponse)"
|
||||
Api -[#707070]down-> Repo : "Utilise uniquement l'abstraction"
|
||||
Repo -[#707070]-> RepoMysql : "implémente"
|
||||
Repo -[#707070]-> RepoMongo : "implémente"
|
||||
Api -[#707070]down-> MySql : "Tables SQL via repository\n(selectionné par config/driver)"
|
||||
Api -[#707070]down-> Mongo : "Requêtes Mongo via repository\n(selectionné par config/driver)"
|
||||
'Docker -[#707070]-> MySql : "Démarre / arrête (docker-compose)"
|
||||
'Docker -[#707070]-> Mongo : "Démarre / arrête (docker-compose)"
|
||||
@enduml
|
||||
@@ -0,0 +1,55 @@
|
||||
@startuml c4-context
|
||||
' C4 modèle — Niveau 1 : Contexte système (syntaxe native PlantUML, compatible anciennes versions)
|
||||
' Application web Flashcards (SPA/PWA)
|
||||
|
||||
hide stereotype
|
||||
skinparam backgroundColor #FFFFFF
|
||||
|
||||
' ===== Styles par type =====
|
||||
skinparam rectangle<<person>> {
|
||||
BackgroundColor #08427B
|
||||
BorderColor #073B6F
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam rectangle<<external_person>> {
|
||||
BackgroundColor #686868
|
||||
BorderColor #8A8A8A
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam rectangle<<system>> {
|
||||
BackgroundColor #1168BD
|
||||
BorderColor #3C7FC0
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam rectangle<<external_system>> {
|
||||
BackgroundColor #999999
|
||||
BorderColor #8A8A8A
|
||||
FontColor #FFFFFF
|
||||
}
|
||||
|
||||
skinparam arrow {
|
||||
Color #707070
|
||||
}
|
||||
|
||||
title Contexte système — Flashcards (Niveau 1)
|
||||
|
||||
' ===== Personnes =====
|
||||
rectangle "Utilisateur\nJoueur et contributeur de cartes\n(CRUD) : code, dev, culture tech" <<person>> as User
|
||||
|
||||
' ===== Système à modéliser =====
|
||||
rectangle "App Flashcards\nSPA/PWA Next.js — tirage de cartes au\nhasard, tirage par thème, CRUD des cartes.\nDouble persistance simultanée\nMySQL + MongoDB via couche générique." <<system>> as Flashcards
|
||||
|
||||
' ===== Systèmes externes =====
|
||||
rectangle "MySQL\nBase relationnelle\n(conteneur Docker)" <<external_system>> as MySql
|
||||
rectangle "MongoDB\nBase documentaire\n(conteneur Docker)" <<external_system>> as Mongo
|
||||
'rectangle "Docker / docker-compose\nOrchestration des conteneurs\n(web, bases) + volumes persistants" <<external_system>> as Docker
|
||||
|
||||
User -[#707070]-> Flashcards : Joue, crée, modifie, supprime,\nconsulte et révèle des cartes
|
||||
Flashcards -[#707070]-> MySql : Lit / écrit (CardRepository)
|
||||
Flashcards -[#707070]-> Mongo : Lit / écrit (CardRepository)
|
||||
'Docker -[#707070]-> MySql : Démarre et gère
|
||||
'Docker -[#707070]-> Mongo : Démarre et gère
|
||||
@enduml
|
||||
@@ -0,0 +1,105 @@
|
||||
@startuml
|
||||
' Diagramme de classes — Application web Flashcards (Architecture : couche d'accès générique)
|
||||
skinparam classAttributeIconSize 0
|
||||
skinparam classFontStyle bold
|
||||
|
||||
' ===== Domaine =====
|
||||
class Card {
|
||||
- id : string
|
||||
- question : string
|
||||
- response : string
|
||||
- theme : string
|
||||
- tags : string[]
|
||||
- createdAt : Date
|
||||
- updatedAt : Date
|
||||
+ Card(id, question, response, theme, tags)
|
||||
+ validate() : void
|
||||
}
|
||||
|
||||
' ===== Abstraction générique d'accès aux données =====
|
||||
interface CardRepository {
|
||||
+ findAll() : Promise<Card[]>
|
||||
+ findById(id : string) : Promise<Card | null>
|
||||
+ findRandom() : Promise<Card | null>
|
||||
+ findRandomByTheme(theme : string) : Promise<Card | null>
|
||||
+ findThemes() : Promise<string[]>
|
||||
+ create(card : Card) : Promise<Card>
|
||||
+ update(card : Card) : Promise<Card>
|
||||
+ delete(id : string) : Promise<boolean>
|
||||
}
|
||||
|
||||
' ===== Implémentations SGBD =====
|
||||
class MysqlCardRepository {
|
||||
- connection : MysqlConnection
|
||||
+ MysqlCardRepository(connection)
|
||||
}
|
||||
|
||||
class MongoCardRepository {
|
||||
- collection : MongoCollection
|
||||
+ MongoCardRepository(collection)
|
||||
}
|
||||
|
||||
class "MysqlConnection" as ConnMysql {
|
||||
- host : string
|
||||
- port : number
|
||||
- user : string
|
||||
- password : string
|
||||
- database : string
|
||||
+ query(sql : string, params : unknown[]) : Promise<unknown>
|
||||
}
|
||||
|
||||
class "MongoCollection" as ColMongo {
|
||||
+ find(filter : object) : Promise<unknown[]>
|
||||
+ insertOne(doc : object) : Promise<unknown>
|
||||
+ updateOne(filter : object, update : object) : Promise<unknown>
|
||||
+ deleteOne(filter : object) : Promise<unknown>
|
||||
}
|
||||
|
||||
class DatabaseConfig {
|
||||
- driver : string
|
||||
+ getActiveDriver() : string
|
||||
}
|
||||
|
||||
' ===== Service métier (dépend uniquement de l'abstraction) =====
|
||||
class CardService {
|
||||
- repository : CardRepository
|
||||
+ CardService(repository : CardRepository)
|
||||
+ listCards() : Promise<Card[]>
|
||||
+ getCard(id : string) : Promise<Card>
|
||||
+ createCard(payload : NewCardPayload) : Promise<Card>
|
||||
+ updateCard(id : string, payload : UpdateCardPayload) : Promise<Card>
|
||||
+ deleteCard(id : string) : Promise<void>
|
||||
+ playRandom(theme? : string) : Promise<Card>
|
||||
+ listThemes() : Promise<string[]>
|
||||
}
|
||||
|
||||
' ===== Couche exposition (routes / contrôleur) =====
|
||||
class CardController {
|
||||
+ handleList(req, res) : Promise<void>
|
||||
+ handleGetById(req, res) : Promise<void>
|
||||
+ handleCreate(req, res) : Promise<void>
|
||||
+ handleUpdate(req, res) : Promise<void>
|
||||
+ handleDelete(req, res) : Promise<void>
|
||||
+ handlePlayRandom(req, res) : Promise<void>
|
||||
+ handleThemes(req, res) : Promise<void>
|
||||
}
|
||||
|
||||
class Factory {
|
||||
{static} buildCardRepository(config : DatabaseConfig) : CardRepository
|
||||
}
|
||||
|
||||
' ===== Relations =====
|
||||
CardRepository <|.. MysqlCardRepository
|
||||
CardRepository <|.. MongoCardRepository
|
||||
|
||||
MysqlCardRepository *-- ConnMysql : utilise >
|
||||
MongoCardRepository *-- ColMongo : utilise >
|
||||
|
||||
CardService *-- CardRepository : <<injecté>> utilise >
|
||||
CardService ..> Card : < crée/retourne
|
||||
|
||||
CardController --> CardService : utilise
|
||||
Factory ..> CardRepository : <<instance de>> construit
|
||||
Factory ..> CardService : construit
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,74 @@
|
||||
@startuml
|
||||
' Diagramme de séquence — "Afficher une carte" puis "Révéler la réponse"
|
||||
' Application web Flashcards (SPA/PWA, couche d'accès générique MySQL/Mongo)
|
||||
|
||||
hide footbox
|
||||
|
||||
title Jeu : afficher une carte au hasard puis révéler la réponse
|
||||
|
||||
actor Utilisateur as User
|
||||
participant "SPA React\n(UI)" as SPA
|
||||
participant "CardController\n(API Next.js)" as Api
|
||||
participant "CardService" as Service
|
||||
participant "CardRepository\n(abstraction générique)" as Repo
|
||||
participant "SGBD\n(MySQL OU MongoDB)" as DB
|
||||
|
||||
== Afficher une carte au hasard ==
|
||||
|
||||
User -> SPA : clique « Jouer »
|
||||
SPA -> SPA : un thème est-il déjà sélectionné ?
|
||||
|
||||
alt Thème précis sélectionné (F-04)
|
||||
User -> SPA : sélectionne un thème (ex. javascript)
|
||||
SPA -> Api : GET /api/cards/random?theme=javascript
|
||||
else Tirage global (F-01)
|
||||
SPA -> Api : GET /api/cards/random
|
||||
end
|
||||
|
||||
Api -> Service : playRandom(theme?)
|
||||
activate Service
|
||||
|
||||
alt theme fourni
|
||||
Service -> Repo : findRandomByTheme(theme)
|
||||
else sans thème
|
||||
Service -> Repo : findRandom()
|
||||
end
|
||||
activate Repo
|
||||
|
||||
Repo -> DB : Requête aléatoire (ORDER BY RAND() / $sample)
|
||||
activate DB
|
||||
DB --> Repo : Enregistrement(s) : question, réponse, thème
|
||||
deactivate DB
|
||||
|
||||
Repo --> Service : Card
|
||||
deactivate Repo
|
||||
Service --> Api : Card (réponse non renvoyée par sécurité)
|
||||
deactivate Service
|
||||
Api --> SPA : 200 JSON { id, question, theme }
|
||||
|
||||
SPA -> SPA : affiche la question\nla réponse reste masquée
|
||||
|
||||
== Révéler la réponse ==
|
||||
|
||||
User -> SPA : clique « Voir la réponse » (F-03)
|
||||
SPA -> Api : GET /api/cards/{id}/reveal
|
||||
activate Api
|
||||
Api -> Repo : findById(id)
|
||||
activate Repo
|
||||
Repo -> DB : SELECT par id / findOne
|
||||
activate DB
|
||||
DB --> Repo : Carte complète
|
||||
deactivate DB
|
||||
Repo --> Api : Card { response }
|
||||
deactivate Repo
|
||||
Api --> SPA : 200 JSON { response }
|
||||
deactivate Api
|
||||
|
||||
SPA --> SPA : déroule et affiche la réponse
|
||||
User -> SPA : reclique (masquer/réafficher) — boucle libre
|
||||
loop tant que joueur le souhaite
|
||||
SPA --> User : réponse visible / masquée
|
||||
end
|
||||
|
||||
note over Repo : L'implémentation concrète (MySQL ou MongoDB)\nest fournie par la Factory selon DatabaseConfig\nsans impact sur Service ni Api (générique).
|
||||
@enduml
|
||||
@@ -0,0 +1,45 @@
|
||||
@startuml Usecase
|
||||
' Diagramme de cas d'utilisation — Application web Flashcards (SPA/PWA)
|
||||
' Le CRUD et le jeu sont volontairement isolés : le jeu ne dépend d'aucun cas du catalogue.
|
||||
left to right direction
|
||||
skinparam actorStyle awesome
|
||||
skinparam packageStyle rectangle
|
||||
|
||||
actor "Utilisateur" as User
|
||||
|
||||
rectangle "Jeu — révision" {
|
||||
usecase "Afficher une carte au hasard" as UC_Jouer
|
||||
usecase "Tirer des cartes d'un thème" as UC_Theme
|
||||
usecase "Révéler la réponse" as UC_Revele
|
||||
}
|
||||
|
||||
rectangle "Gestion des cartes (CRUD)" {
|
||||
usecase "Lister les cartes" as UC_Liste
|
||||
usecase "Consulter une carte" as UC_Detail
|
||||
usecase "Créer une carte" as UC_Create
|
||||
usecase "Modifier une carte" as UC_Update
|
||||
usecase "Supprimer une carte" as UC_Delete
|
||||
}
|
||||
|
||||
' --- Jeu ---
|
||||
User --> UC_Jouer
|
||||
UC_Jouer ..> UC_Theme : <<extend>>
|
||||
UC_Jouer --> UC_Revele : <<include>>
|
||||
|
||||
' --- CRUD : la liste est l'entrée unique, le détail est le point d'ancrage des actions ---
|
||||
User --> UC_Liste
|
||||
UC_Liste ..> UC_Detail : <<extend>>
|
||||
UC_Liste ..> UC_Create : <<extend>>
|
||||
UC_Detail ..> UC_Update : <<extend>>
|
||||
UC_Detail ..> UC_Delete : <<extend>>
|
||||
|
||||
note bottom of UC_Delete
|
||||
Suppression avec confirmation
|
||||
end note
|
||||
|
||||
note bottom of UC_Revele
|
||||
La réponse est cachée tant que
|
||||
l'utilisateur ne la déroule pas
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,13 @@
|
||||
Élément,Entité/Table,Champ,Type TypeScript,Type MySQL,Type MongoDB,Description,Contraintes,Obligatoire,Exemple
|
||||
Carte,cards,id,string,VARCHAR(36) (UUID),string (champ c_id),Identifiant métier stable de la carte,UNIQUE / PRIMARY KEY,Oui,carte_123e4567-e89b-12d3-a456-426614174000
|
||||
Carte,cards,question,string,TEXT,string,Terme ou question affichée à l'utilisateur lors du tirage jeu,Non vide,Oui,a2f3
|
||||
Carte,cards,response,string,TEXT,string,"Réponse cachée, révélée à la demande par le joueur",Non vide,Oui,Asynchrone et non bloquant (Event Loop)
|
||||
Carte,cards,theme,string,VARCHAR(50),string,Thème / catégorie de la carte,Non vide + index,Oui,javascript
|
||||
Carte,cards,tags,string[],JSON (type JSON),string[],Mots-clés additionnels pour la recherche,Optionnel,Non,"[""frontend"",""DOM""]"
|
||||
Carte,cards,createdAt,Date,DATETIME / TIMESTAMP,Date,Date de création de la carte,Automatique à l'insertion,Oui,9/16/26 8:00
|
||||
Carte,cards,updatedAt,Date,DATETIME / TIMESTAMP,Date,Date de dernière modification,Automatique à chaque modification,Oui,9/16/26 9:30
|
||||
Thème,themes,id,string,VARCHAR(36) (UUID),string,Identifiant unique du thème (si entité dédiée),UNIQUE / PRIMARY KEY,Oui,thm_2026-0001
|
||||
Thème,themes,name,string,VARCHAR(50) UNIQUE,string,Nom du thème,UNIQUE,Oui,javascript
|
||||
Liste des types,"Option ""recommandation""",liste des thèmes,-,-,-,Alternative : dériver dynamiquement les thèmes depuis les valeurs DISTINCT de cards.theme,-,-,-
|
||||
Index recommandé MySQL,idx_theme_sur_cards.theme,-,-,INDEX (theme),-,-,-,-,-
|
||||
Index recommandé MongoDB,index_sur_cards.theme,-,-,-,index(theme),-,-,-,-
|
||||
|
@@ -0,0 +1,13 @@
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import nextVitals from 'eslint-config-next/core-web-vitals'
|
||||
import nextTs from 'eslint-config-next/typescript'
|
||||
import prettier from 'eslint-config-prettier/flat'
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
prettier,
|
||||
globalIgnores(['.next/**', 'out/**', 'build/**', 'coverage/**', 'next-env.d.ts']),
|
||||
])
|
||||
|
||||
export default eslintConfig
|
||||
@@ -0,0 +1,5 @@
|
||||
import { withSerwist } from '@serwist/turbopack'
|
||||
|
||||
export default withSerwist({
|
||||
// Configuration Next.js (Turbopack activé par défaut dans Next 16)
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "flashcards",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"typecheck": "next typegen && tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"check": "pnpm lint && pnpm format && pnpm format:check && pnpm typecheck && pnpm test && pnpm test:coverage",
|
||||
"pwa:assets": "pwa-assets-generator --preset minimal-2023 public/icon.svg"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.3.5",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@serwist/turbopack": "^9.5.12",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@vite-pwa/assets-generator": "^2.0.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"@vitest/coverage-v8": "^5.0.1",
|
||||
"esbuild": "^0.28.2",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.3.5",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"jsdom": "^30.1.0",
|
||||
"prettier": "^3.9.8",
|
||||
"serwist": "^9.5.12",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5",
|
||||
"vitest": "^5.0.1"
|
||||
},
|
||||
"packageManager": "pnpm@12.4.2",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
allowBuilds:
|
||||
sharp: true
|
||||
'@tailwindcss/oxide': true
|
||||
'@swc/core': true
|
||||
esbuild: true
|
||||
unrs-resolver: true
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 483 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
|
||||
<rect width="1024" height="1024" rx="200" fill="#0f172a"/>
|
||||
<g fill="none" stroke="#38bdf8" stroke-width="64" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M384 256 L256 512 L384 768"/>
|
||||
<path d="M640 256 L768 512 L640 768"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 455 B |
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1 @@
|
||||
@import 'tailwindcss';
|
||||
@@ -0,0 +1,63 @@
|
||||
import { SerwistProvider } from '@serwist/turbopack/react'
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
import type { ReactNode } from 'react'
|
||||
import './globals.css'
|
||||
|
||||
const APP_NAME = 'flashcards'
|
||||
const APP_DESCRIPTION = 'Application web PWA installable et utilisable hors connexion.'
|
||||
const APP_TITLE_TEMPLATE = '%s · flashcards'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
applicationName: APP_NAME,
|
||||
title: {
|
||||
default: APP_NAME,
|
||||
template: APP_TITLE_TEMPLATE,
|
||||
},
|
||||
description: APP_DESCRIPTION,
|
||||
manifest: '/manifest.webmanifest',
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
statusBarStyle: 'default',
|
||||
title: APP_NAME,
|
||||
},
|
||||
formatDetection: {
|
||||
telephone: false,
|
||||
},
|
||||
icons: {
|
||||
icon: '/icon.svg',
|
||||
apple: '/apple-touch-icon-180x180.png',
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
siteName: APP_NAME,
|
||||
title: {
|
||||
default: APP_NAME,
|
||||
template: APP_TITLE_TEMPLATE,
|
||||
},
|
||||
description: APP_DESCRIPTION,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary',
|
||||
title: {
|
||||
default: APP_NAME,
|
||||
template: APP_TITLE_TEMPLATE,
|
||||
},
|
||||
description: APP_DESCRIPTION,
|
||||
},
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#0f172a',
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="fr">
|
||||
<body>
|
||||
<SerwistProvider swUrl="/serwist/sw.js" disable={process.env.NODE_ENV === 'development'}>
|
||||
{children}
|
||||
</SerwistProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: 'flashcards',
|
||||
short_name: 'flashcards',
|
||||
description: 'Application web PWA installable et utilisable hors connexion.',
|
||||
lang: 'fr',
|
||||
start_url: '/',
|
||||
scope: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#0f172a',
|
||||
theme_color: '#0f172a',
|
||||
orientation: 'portrait',
|
||||
icons: [
|
||||
{ src: '/pwa-64x64.png', sizes: '64x64', type: 'image/png' },
|
||||
{ src: '/pwa-192x192.png', sizes: '192x192', type: 'image/png' },
|
||||
{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png' },
|
||||
{
|
||||
src: '/maskable-icon-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import Home from './page'
|
||||
|
||||
describe('Home', () => {
|
||||
it('affiche le contenu minimal', () => {
|
||||
render(<Home />)
|
||||
expect(screen.getByRole('heading', { level: 1, name: 'Hello world' })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Hello world</h1>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { createSerwistRoute } from '@serwist/turbopack'
|
||||
|
||||
// Révision du cache : change à chaque commit pour invalider le precache.
|
||||
const revision =
|
||||
spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' }).stdout ?? crypto.randomUUID()
|
||||
|
||||
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } = createSerwistRoute(
|
||||
{
|
||||
additionalPrecacheEntries: [{ url: '/~offline', revision }],
|
||||
swSrc: 'src/app/sw.ts',
|
||||
useNativeEsbuild: true,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
/// <reference lib="webworker" />
|
||||
import { defaultCache } from '@serwist/turbopack/worker'
|
||||
import type { PrecacheEntry, RuntimeCaching, SerwistGlobalConfig } from 'serwist'
|
||||
import { NetworkOnly, Serwist } from 'serwist'
|
||||
|
||||
declare global {
|
||||
interface WorkerGlobalScope extends SerwistGlobalConfig {
|
||||
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined
|
||||
}
|
||||
}
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope
|
||||
|
||||
// Ne jamais mettre en cache les appels API
|
||||
const runtimeCaching: RuntimeCaching[] = [
|
||||
{
|
||||
matcher: ({ sameOrigin, url }) => sameOrigin && url.pathname.startsWith('/api/'),
|
||||
handler: new NetworkOnly(),
|
||||
},
|
||||
...defaultCache,
|
||||
]
|
||||
|
||||
const serwist = new Serwist({
|
||||
precacheEntries: self.__SW_MANIFEST,
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
navigationPreload: true,
|
||||
runtimeCaching,
|
||||
fallbacks: {
|
||||
entries: [
|
||||
{
|
||||
url: '/~offline',
|
||||
matcher({ request }) {
|
||||
return request.mode === 'navigate'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
serwist.addEventListeners()
|
||||
@@ -0,0 +1,11 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function OfflinePage() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Vous êtes hors connexion</h1>
|
||||
<p>Reconnectez-vous pour continuer.</p>
|
||||
<Link href="/">Accueil</Link>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { cleanup } from '@testing-library/react'
|
||||
import { afterEach } from 'vitest'
|
||||
|
||||
afterEach(() => cleanup())
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: (query: string) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: () => {},
|
||||
removeListener: () => {},
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
dispatchEvent: () => false,
|
||||
}),
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
include: ['src/**/*.test.{ts,tsx}'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'html', 'lcov'],
|
||||
include: ['src/**/*.{ts,tsx}'],
|
||||
exclude: [
|
||||
'src/**/*.test.{ts,tsx}',
|
||||
'src/app/layout.tsx',
|
||||
'src/app/manifest.ts',
|
||||
'src/app/sw.ts',
|
||||
'src/app/serwist/**',
|
||||
'src/test/**',
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||