131 lines
6.5 KiB
TypeScript
131 lines
6.5 KiB
TypeScript
|
|
/**
|
|||
|
|
* 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(() => {
|
|||
|
|
db.prepare('INSERT INTO classes (name, level, code, created_at) VALUES (?, ?, ?, ?)')
|
|||
|
|
.run(CLASS_NAME, LEVEL, CLASS_CODE, nowIso())
|
|||
|
|
|
|||
|
|
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}`)
|