Météo Exam v0.1.0 — calendrier des évaluations en bulletin météo
- API Express + SQLite : classes, évaluations, SSE, auth Bearer - Front Vite + TS + Tailwind : frise 7 jours, Exam Cards, modales - Capacitor Android : APK debug, CapacitorHttp natif - Seed : classe Terminale S2 (code TERMS2)
This commit is contained in:
commit
6d154ed5fb
92 changed files with 9574 additions and 0 deletions
130
server/src/seed.ts
Normal file
130
server/src/seed.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* 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}`)
|
||||
Loading…
Add table
Add a link
Reference in a new issue