package seed import ( "context" "fmt" "log" "math/rand" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // Seeder generates demo data for the school service type Seeder struct { pool *pgxpool.Pool teacherID uuid.UUID rng *rand.Rand } // NewSeeder creates a new seeder instance func NewSeeder(pool *pgxpool.Pool, teacherID uuid.UUID) *Seeder { return &Seeder{ pool: pool, teacherID: teacherID, rng: rand.New(rand.NewSource(time.Now().UnixNano())), } } // German first names for realistic demo data var firstNames = []string{ "Max", "Paul", "Leon", "Felix", "Jonas", "Lukas", "Tim", "Ben", "Finn", "Elias", "Emma", "Mia", "Hannah", "Sophia", "Lena", "Anna", "Marie", "Leonie", "Lara", "Laura", "Noah", "Luis", "David", "Moritz", "Jan", "Niklas", "Tom", "Simon", "Erik", "Jannik", "Lea", "Julia", "Lisa", "Sarah", "Clara", "Amelie", "Emily", "Maja", "Zoe", "Lina", "Alexander", "Maximilian", "Sebastian", "Philipp", "Julian", "Fabian", "Tobias", "Christian", "Katharina", "Christina", "Johanna", "Franziska", "Antonia", "Victoria", "Helena", "Charlotte", } // German last names for realistic demo data var lastNames = []string{ "Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann", "Koch", "Bauer", "Richter", "Klein", "Wolf", "Schroeder", "Neumann", "Schwarz", "Zimmermann", "Braun", "Krueger", "Hofmann", "Hartmann", "Lange", "Schmitt", "Werner", "Schmitz", "Krause", "Meier", "Lehmann", "Schmid", "Schulze", "Maier", "Koehler", "Herrmann", "Koenig", "Walter", "Mayer", "Huber", "Kaiser", "Peters", "Lang", "Scholz", "Moeller", "Gross", "Jung", "Friedrich", "Keller", } // Subjects with exam data type SubjectConfig struct { Name string ShortName string IsMain bool ExamsPerYear int WrittenWeight int } var subjects = []SubjectConfig{ {"Deutsch", "De", true, 4, 60}, {"Mathematik", "Ma", true, 4, 60}, {"Englisch", "En", true, 4, 60}, {"Biologie", "Bio", false, 3, 50}, {"Geschichte", "Ge", false, 2, 50}, {"Physik", "Ph", false, 3, 50}, {"Chemie", "Ch", false, 2, 50}, {"Sport", "Sp", false, 0, 30}, {"Kunst", "Ku", false, 1, 40}, {"Musik", "Mu", false, 1, 40}, } // SeedAll generates all demo data func (s *Seeder) SeedAll(ctx context.Context) error { log.Println("Starting seed data generation...") // 1. Create school years schoolYears, err := s.seedSchoolYears(ctx) if err != nil { return fmt.Errorf("seeding school years: %w", err) } log.Printf("Created %d school years", len(schoolYears)) // 2. Create subjects subjectIDs, err := s.seedSubjects(ctx) if err != nil { return fmt.Errorf("seeding subjects: %w", err) } log.Printf("Created %d subjects", len(subjectIDs)) // 3. Create classes with students for _, sy := range schoolYears { classes, err := s.seedClassesForYear(ctx, sy) if err != nil { return fmt.Errorf("seeding classes for year %s: %w", sy.Name, err) } for _, class := range classes { // Create students students, err := s.seedStudentsForClass(ctx, class.ID) if err != nil { return fmt.Errorf("seeding students for class %s: %w", class.Name, err) } log.Printf("Created %d students for class %s", len(students), class.Name) // Create exams and results for _, subj := range subjects { subjectID := subjectIDs[subj.Name] err := s.seedExamsAndResults(ctx, class.ID, subjectID, students, sy.ID, subj) if err != nil { return fmt.Errorf("seeding exams for class %s, subject %s: %w", class.Name, subj.Name, err) } } // Create attendance records err = s.seedAttendance(ctx, students, sy) if err != nil { return fmt.Errorf("seeding attendance for class %s: %w", class.Name, err) } } } log.Println("Seed data generation completed successfully!") return nil } // SchoolYearResult holds created school year info type SchoolYearResult struct { ID uuid.UUID Name string StartDate time.Time EndDate time.Time IsCurrent bool } func (s *Seeder) seedSchoolYears(ctx context.Context) ([]SchoolYearResult, error) { years := []struct { name string startYear int isCurrent bool }{ {"2022/23", 2022, false}, {"2023/24", 2023, false}, {"2024/25", 2024, true}, } var results []SchoolYearResult for _, y := range years { id := uuid.New() startDate := time.Date(y.startYear, time.August, 1, 0, 0, 0, 0, time.UTC) endDate := time.Date(y.startYear+1, time.July, 31, 0, 0, 0, 0, time.UTC) _, err := s.pool.Exec(ctx, ` INSERT INTO school_years (id, name, start_date, end_date, is_current, teacher_id) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING `, id, y.name, startDate, endDate, y.isCurrent, s.teacherID) if err != nil { return nil, err } results = append(results, SchoolYearResult{ ID: id, Name: y.name, StartDate: startDate, EndDate: endDate, IsCurrent: y.isCurrent, }) } return results, nil } func (s *Seeder) seedSubjects(ctx context.Context) (map[string]uuid.UUID, error) { subjectIDs := make(map[string]uuid.UUID) for _, subj := range subjects { id := uuid.New() _, err := s.pool.Exec(ctx, ` INSERT INTO subjects (id, teacher_id, name, short_name, is_main_subject) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (teacher_id, name) DO UPDATE SET id = subjects.id RETURNING id `, id, s.teacherID, subj.Name, subj.ShortName, subj.IsMain) if err != nil { return nil, err } subjectIDs[subj.Name] = id } return subjectIDs, nil } // ClassResult holds created class info type ClassResult struct { ID uuid.UUID Name string GradeLevel int } func (s *Seeder) seedClassesForYear(ctx context.Context, sy SchoolYearResult) ([]ClassResult, error) { // Classes: 5a, 5b, 6a, 6b, 7a, 7b classConfigs := []struct { name string gradeLevel int }{ {"5a", 5}, {"5b", 5}, {"6a", 6}, {"6b", 6}, {"7a", 7}, {"7b", 7}, } var results []ClassResult for _, c := range classConfigs { id := uuid.New() _, err := s.pool.Exec(ctx, ` INSERT INTO classes (id, teacher_id, school_year_id, name, grade_level, school_type, federal_state) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (teacher_id, school_year_id, name) DO UPDATE SET id = classes.id `, id, s.teacherID, sy.ID, c.name, c.gradeLevel, "gymnasium", "niedersachsen") if err != nil { return nil, err } results = append(results, ClassResult{ ID: id, Name: c.name, GradeLevel: c.gradeLevel, }) } return results, nil }