2026-10-02 19:51:13 +02:00
/ * *
* Jeu de données de démonstration : la classe Terminale S2 avec deux semaines
* d ' é valuations . Idempotent — relancer ` npm run seed ` ne duplique rien .
* Usage : npm run seed [ -- -- reset ]
* /
import { existsSync , rmSync } from 'node:fs'
import { resolve } from 'node:path'
import { db , nowIso } from './db.js'
import { newToken } from './auth.js'
import type { WeatherKind } from '@meteo-exam/shared'
const DB_PATH = process . env [ 'METEO_DB' ] ? ? resolve ( process . cwd ( ) , 'data/meteo-exam.db' )
if ( process . argv . includes ( '--reset' ) ) {
for ( const suffix of [ '' , '-wal' , '-shm' ] ) {
const file = ` ${ DB_PATH } ${ suffix } `
if ( existsSync ( file ) ) rmSync ( file )
}
console . log ( 'Base effacée. Relancez `npm run seed`.' )
process . exit ( 0 )
}
const existing = db . prepare < [ ] , { n : number } > ( 'SELECT COUNT(*) AS n FROM classes' ) . get ( ) !
if ( existing . n > 0 ) {
console . log ( 'La base contient déjà des classes — seed ignoré. Utilisez --reset pour repartir de zéro.' )
process . exit ( 0 )
}
const CLASS_NAME = 'Terminale S2'
const LEVEL = 'Lycée'
const ADMIN_NICK = 'Thomas S.'
const CLASS_CODE = 'TERMS2'
const DAY_MS = 86 _400_000
const isWeekend = ( d : Date ) : boolean = > d . getDay ( ) === 0 || d . getDay ( ) === 6
const midnight = new Date ( )
midnight . setHours ( 0 , 0 , 0 , 0 )
/ * *
* ` dayOffset ` compte les jours * * scolaires * * : 0 = aujourd 'hui (même si c' est
* samedi , le seed reste lisible ) , puis on saute les weekends .
* /
function schoolDay ( offset : number ) : Date {
const date = new Date ( midnight )
let counted = 0
for ( let step = 0 ; step < 90 ; step ++ ) {
if ( counted === offset ) break
date . setTime ( date . getTime ( ) + DAY_MS )
if ( ! isWeekend ( date ) ) counted ++
}
return date
}
interface SeedExam {
dayOffset : number
hour : number
subject : string
teacher : string | null
room : string | null
chapter : string | null
durationMin : number
weight : number
kind : WeatherKind
note : string | null
}
const exams : SeedExam [ ] = [
{ dayOffset : 0 , hour : 8 , subject : 'Mathématiques' , teacher : 'M. Roussel' , room : 'B204' , chapter : 'Intégrales, primitives et dérivation' , durationMin : 120 , weight : 4 , kind : 'tempete' , note : 'Calculatrice autorisée, calculatrice graphique non.' } ,
{ dayOffset : 0 , hour : 14 , subject : 'Histoire-Géographie' , teacher : 'Mme Ferrand' , room : 'A112' , chapter : null , durationMin : 55 , weight : 1 , kind : 'vent' , note : 'Interro surprise probable en début de cours.' } ,
{ dayOffset : 1 , hour : 9 , subject : 'Physique-Chimie' , teacher : 'Mme Nguyen' , room : 'Labo 3' , chapter : 'Mécanique : second principe' , durationMin : 60 , weight : 2 , kind : 'orage' , note : 'Paquet de calculatrices fourni.' } ,
{ dayOffset : 1 , hour : 15 , subject : 'Anglais' , teacher : 'M. Whitfield' , room : 'C007' , chapter : 'Compréhension orale — Unit 4' , durationMin : 30 , weight : 1 , kind : 'vent' , note : 'Rumeur d’ une dictée surprise.' } ,
{ dayOffset : 2 , hour : 8 , subject : 'SVT' , teacher : 'Mme Perrin' , room : 'S215' , chapter : 'Génétique des populations' , durationMin : 90 , weight : 3 , kind : 'orage' , note : 'TP suivi d’ une synthèse écrite.' } ,
{ dayOffset : 3 , hour : 10 , subject : 'Philosophie' , teacher : 'M. Aubert' , room : 'A008' , chapter : 'La raison et le doute' , durationMin : 120 , weight : 3 , kind : 'orage' , note : 'Dissertation, sujet au choix.' } ,
{ dayOffset : 3 , hour : 16 , subject : 'EPS' , teacher : 'Mme Costa' , room : 'Gymnase' , chapter : null , durationMin : 60 , weight : 1 , kind : 'soleil' , note : 'Basket obligatoire.' } ,
{ dayOffset : 4 , hour : 9 , subject : 'Mathématiques' , teacher : 'M. Roussel' , room : 'B204' , chapter : 'Probabilités conditionnelles' , durationMin : 60 , weight : 2 , kind : 'orage' , note : null } ,
{ dayOffset : 5 , hour : 10 , subject : 'Français' , teacher : 'Mme Dumas' , room : 'A101' , chapter : 'Guerre froide — contexte' , durationMin : 60 , weight : 1 , kind : 'vent' , note : 'Lecture suivie d’ une étude de texte.' } ,
{ dayOffset : 6 , hour : 8 , subject : 'Examen blanc' , teacher : null , room : 'Amphi A' , chapter : 'Toutes matières' , durationMin : 180 , weight : 5 , kind : 'tempete' , note : 'Blanc général, convocation à 8h précises.' } ,
{ dayOffset : 7 , hour : 9 , subject : 'SVT' , teacher : 'Mme Perrin' , room : 'S215' , chapter : 'Écosystèmes et bilan de matière' , durationMin : 45 , weight : 1 , kind : 'brouillard' , note : 'Brouillard : TP évalué, date encore floue.' } ,
{ dayOffset : 8 , hour : 14 , subject : 'Anglais' , teacher : 'M. Whitfield' , room : 'C007' , chapter : 'Expression écrite — sujet d’ invention' , durationMin : 60 , weight : 2 , kind : 'orage' , note : null } ,
{ dayOffset : 9 , hour : 8 , subject : 'Histoire-Géographie' , teacher : 'Mme Ferrand' , room : 'A112' , chapter : 'La décolonisation' , durationMin : 60 , weight : 2 , kind : 'orage' , note : null } ,
{ dayOffset : 10 , hour : 10 , subject : 'Philosophie' , teacher : 'M. Aubert' , room : 'A008' , chapter : 'Le sujet et l’ autre' , durationMin : 60 , weight : 2 , kind : 'orage' , note : 'Plan imposé.' } ,
]
const seed = db . transaction ( ( ) = > {
v0.2.0 — config admin (météo forcée, flash info, édition classe) + PWA
- DB : table day_weather (override admin par jour), flash_message sur classes
- API : PUT/DELETE /api/config/day-weather, PUT /api/config/flash, PATCH /api/config/class
- SSE : day_weather.updated/deleted, flash.updated, class.updated
- Front : bandeau flash info, indicateur 'forcé' sur la frise, modale météo admin
- Onglet Plus : panneau admin (flash info + édition classe), tout admin modifiable
- PWA : manifest.webmanifest, service worker, meta Apple, icônes 192/512
- Web version accessible sur iOS via Safari (Ajouter à l'écran d'accueil)
2026-10-04 17:05:56 +02:00
db . prepare ( 'INSERT INTO classes (name, level, code, flash_message, created_at) VALUES (?, ?, ?, ?, ?)' )
. run ( CLASS_NAME , LEVEL , CLASS_CODE , 'Bon courage pour les révisions cette semaine ! 🌤️' , nowIso ( ) )
2026-10-02 19:51:13 +02:00
const classId = db . prepare < [ string ] , { id : number } > ( 'SELECT id FROM classes WHERE code = ?' )
. get ( CLASS_CODE ) ! . id
const adminToken = newToken ( )
const adminId = db
. prepare ( 'INSERT INTO members (class_id, nickname, role, token, created_at) VALUES (?, ?, ?, ?, ?)' )
. run ( classId , ADMIN_NICK , 'admin' , adminToken , nowIso ( ) ) . lastInsertRowid as number
// Quelques élèves : ils donneront du volume aux votes du radar plus tard.
for ( const nick of [ 'Léa M.' , 'Karim B.' , 'Chloé D.' , 'Inès R.' , 'Lucas P.' , 'Mehdi A.' ] ) {
db . prepare ( 'INSERT INTO members (class_id, nickname, role, token, created_at) VALUES (?, ?, ?, ?, ?)' )
. run ( classId , nick , 'student' , newToken ( ) , nowIso ( ) )
}
const insert = db . prepare (
` INSERT INTO exams
( class_id , subject , teacher , room , chapter , starts_at , duration_min , weight , kind , note , created_by , created_at , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ` ,
)
for ( const e of exams ) {
const date = schoolDay ( e . dayOffset )
date . setHours ( e . hour , 0 , 0 , 0 )
const now = nowIso ( )
insert . run (
classId , e . subject , e . teacher , e . room , e . chapter ,
date . toISOString ( ) , e . durationMin , e . weight , e . kind , e . note ,
adminId , now , now ,
)
}
return { classId , adminToken }
} )
const { adminToken } = seed ( )
console . log ( 'Seed terminé.' )
console . log ( ` Classe : ${ CLASS_NAME } ( ${ LEVEL } ) ` )
console . log ( ` Code invite : ${ CLASS_CODE } ` )
console . log ( ` Admin : ${ ADMIN_NICK } ` )
console . log ( ` Évaluations : ${ exams . length } ` )
console . log ( ` Jeton admin : ${ adminToken } ` )