package main import ( "encoding/json" "flag" "fmt" "log" "os" "path/filepath" "git.godopu.com/lab/landing_page/backend/internal/config" "git.godopu.com/lab/landing_page/backend/internal/db" ) type SeedProject struct { Slug string `json:"slug"` Title string `json:"title"` Abstract string `json:"abstract"` Keywords []string `json:"keywords"` StandardsTrack *string `json:"standardsTrack"` StandardsOrg *string `json:"standardsOrg"` Period *string `json:"period"` Funder *string `json:"funder"` } type SeedArea struct { NameEn string `json:"name_en"` NameKr *string `json:"name_kr"` DisplayOrder int `json:"display_order"` } type SeedMember struct { NameKo string `json:"name_ko"` NameEn string `json:"name_en"` Degree string `json:"degree"` Affiliation *string `json:"affiliation"` Email *string `json:"email"` IsAdvisor bool `json:"is_advisor"` DisplayOrder int `json:"display_order"` } type SeedAlumnus struct { NameKo string `json:"name_ko"` NameEn string `json:"name_en"` Degree string `json:"degree"` GraduatedAt string `json:"graduated_at"` Major *string `json:"major"` CurrentPosition *string `json:"current_position"` } type SeedPublication struct { Category string `json:"category"` Title string `json:"title"` Authors *string `json:"authors"` Venue string `json:"venue"` Volume *string `json:"volume"` PublishedAt string `json:"published_at"` KCI bool `json:"kci"` DOI *string `json:"doi"` IsHighlight bool `json:"is_highlight"` } type SeedPatent struct { Title string `json:"title"` Inventors string `json:"inventors"` ApplicationNo string `json:"application_no"` ApplicationAt string `json:"application_at"` RegistrationNo *string `json:"registration_no"` RegistrationAt *string `json:"registration_at"` Country string `json:"country"` PublishedAt string `json:"published_at"` } type SeedCourse struct { Code string `json:"code"` En string `json:"en"` Note *string `json:"note"` } type SeedSemester struct { Term string `json:"term"` Year int `json:"year"` Courses []SeedCourse `json:"courses"` } type SeedDoc struct { Title string `json:"title"` Ref string `json:"ref"` Status string `json:"status"` PublishedAt *string `json:"publishedAt"` } type SeedStandardProject struct { WG string `json:"wg"` Name string `json:"name"` Documents []SeedDoc `json:"documents"` } type SeedStandardsBody struct { Org string `json:"org"` Full string `json:"full"` Scope string `json:"scope"` Period string `json:"period"` Role *string `json:"role"` Projects []SeedStandardProject `json:"projects"` } func readJSONFile[T any](path string) (T, error) { var result T data, err := os.ReadFile(path) if err != nil { return result, fmt.Errorf("read file %s: %w", path, err) } if err := json.Unmarshal(data, &result); err != nil { return result, fmt.Errorf("unmarshal %s: %w", path, err) } return result, nil } func main() { config.LoadEnvFile() defaultDB := "anl.db" if envDB := os.Getenv("DB_PATH"); envDB != "" { defaultDB = envDB } dbPath := flag.String("db", defaultDB, "SQLite database file path") seedDir := flag.String("seed-dir", "seed/data", "Directory containing seed JSON files") flag.Parse() log.Printf("Connecting to SQLite database at %s...", *dbPath) database, err := db.Connect(*dbPath) if err != nil { log.Fatalf("Database connection failed: %v", err) } defer database.Close() log.Println("Applying schema migrations...") if err := db.RunMigrations(database); err != nil { log.Fatalf("Migration failed: %v", err) } tx, err := database.Begin() if err != nil { log.Fatalf("Begin transaction failed: %v", err) } defer tx.Rollback() // Clear existing records in reverse FK order tables := []string{ "standard_documents", "standards_bodies", "courses", "semesters", "patents", "publications", "alumni", "members", "research_areas", "research_projects", } for _, t := range tables { if _, err := tx.Exec("DELETE FROM " + t); err != nil { log.Fatalf("Failed to clear table %s: %v", t, err) } } // 1. Research Projects projects, err := readJSONFile[[]SeedProject](filepath.Join(*seedDir, "research_projects.json")) if err != nil { log.Fatalf("Failed to read research projects: %v", err) } for _, p := range projects { kwJSON, _ := json.Marshal(p.Keywords) _, err := tx.Exec(` INSERT INTO research_projects (slug, title, abstract, keywords, organization, standards_org, period, funder) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, p.Slug, p.Title, p.Abstract, string(kwJSON), p.StandardsTrack, p.StandardsOrg, p.Period, p.Funder) if err != nil { log.Fatalf("Insert research project (%s) failed: %v", p.Slug, err) } } // 2. Research Areas areas, err := readJSONFile[[]SeedArea](filepath.Join(*seedDir, "research_areas.json")) if err != nil { log.Fatalf("Failed to read research areas: %v", err) } for _, a := range areas { _, err := tx.Exec(` INSERT INTO research_areas (name_en, name_kr, display_order) VALUES (?, ?, ?) `, a.NameEn, a.NameKr, a.DisplayOrder) if err != nil { log.Fatalf("Insert research area failed: %v", err) } } // 3. Members members, err := readJSONFile[[]SeedMember](filepath.Join(*seedDir, "members.json")) if err != nil { log.Fatalf("Failed to read members: %v", err) } for _, m := range members { _, err := tx.Exec(` INSERT INTO members (name_ko, name_en, degree, affiliation, email, is_advisor, display_order) VALUES (?, ?, ?, ?, ?, ?, ?) `, m.NameKo, m.NameEn, m.Degree, m.Affiliation, m.Email, m.IsAdvisor, m.DisplayOrder) if err != nil { log.Fatalf("Insert member (%s) failed: %v", m.NameEn, err) } } // 4. Alumni alumni, err := readJSONFile[[]SeedAlumnus](filepath.Join(*seedDir, "alumni.json")) if err != nil { log.Fatalf("Failed to read alumni: %v", err) } for _, a := range alumni { _, err := tx.Exec(` INSERT INTO alumni (name_ko, name_en, degree, graduated_at, major, current_position) VALUES (?, ?, ?, ?, ?, ?) `, a.NameKo, a.NameEn, a.Degree, a.GraduatedAt, a.Major, a.CurrentPosition) if err != nil { log.Fatalf("Insert alumnus (%s) failed: %v", a.NameEn, err) } } // 5. Publications pubs, err := readJSONFile[[]SeedPublication](filepath.Join(*seedDir, "publications.json")) if err != nil { log.Fatalf("Failed to read publications: %v", err) } for _, p := range pubs { _, err := tx.Exec(` INSERT INTO publications (category, title, authors, venue, volume, published_at, kci, doi, is_highlight) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, p.Category, p.Title, p.Authors, p.Venue, p.Volume, p.PublishedAt, p.KCI, p.DOI, p.IsHighlight) if err != nil { log.Fatalf("Insert publication (%s) failed: %v", p.Title, err) } } // 6. Patents patents, err := readJSONFile[[]SeedPatent](filepath.Join(*seedDir, "patents.json")) if err != nil { log.Fatalf("Failed to read patents: %v", err) } for _, pat := range patents { _, err := tx.Exec(` INSERT INTO patents (title, inventors, application_no, application_at, registration_no, registration_at, country, published_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, pat.Title, pat.Inventors, pat.ApplicationNo, pat.ApplicationAt, pat.RegistrationNo, pat.RegistrationAt, pat.Country, pat.PublishedAt) if err != nil { log.Fatalf("Insert patent (%s) failed: %v", pat.Title, err) } } // 7. Semesters & Courses semesters, err := readJSONFile[[]SeedSemester](filepath.Join(*seedDir, "lectures.json")) if err != nil { log.Fatalf("Failed to read lectures: %v", err) } for _, s := range semesters { res, err := tx.Exec(`INSERT INTO semesters (year, term) VALUES (?, ?)`, s.Year, s.Term) if err != nil { log.Fatalf("Insert semester (%d %s) failed: %v", s.Year, s.Term, err) } semID, _ := res.LastInsertId() for cIdx, c := range s.Courses { _, err := tx.Exec(` INSERT INTO courses (semester_id, code, name_en, name_kr, note, display_order) VALUES (?, ?, ?, ?, ?, ?) `, semID, c.Code, c.En, nil, c.Note, cIdx+1) if err != nil { log.Fatalf("Insert course (%s) failed: %v", c.Code, err) } } } // 8. Standards Bodies & Documents standards, err := readJSONFile[[]SeedStandardsBody](filepath.Join(*seedDir, "standards.json")) if err != nil { log.Fatalf("Failed to read standards: %v", err) } for bIdx, b := range standards { res, err := tx.Exec(` INSERT INTO standards_bodies (org, full_name, scope, period, role, display_order) VALUES (?, ?, ?, ?, ?, ?) `, b.Org, b.Full, b.Scope, b.Period, b.Role, bIdx+1) if err != nil { log.Fatalf("Insert standards body (%s) failed: %v", b.Org, err) } bodyID, _ := res.LastInsertId() for _, p := range b.Projects { for _, d := range p.Documents { _, err := tx.Exec(` INSERT INTO standard_documents (body_id, wg, project_name, title, doc_ref, status, published_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, bodyID, p.WG, p.Name, d.Title, d.Ref, d.Status, d.PublishedAt) if err != nil { log.Fatalf("Insert standard document (%s) failed: %v", d.Title, err) } } } } if err := tx.Commit(); err != nil { log.Fatalf("Transaction commit failed: %v", err) } log.Println("Transaction committed successfully.") // Verify Counts log.Println("=== Verifying Seeding Counts ===") checkCount := func(table string, expected int) { var cnt int if err := database.QueryRow("SELECT COUNT(*) FROM " + table).Scan(&cnt); err != nil { log.Fatalf("Count check failed for %s: %v", table, err) } if cnt != expected { log.Fatalf("Count mismatch for %s: expected %d, got %d", table, expected, cnt) } log.Printf("✓ %-20s : %3d rows (matches expected)", table, cnt) } checkCount("research_projects", 3) checkCount("research_areas", 14) checkCount("members", 16) checkCount("alumni", 60) checkCount("publications", 148) checkCount("patents", 75) checkCount("semesters", 45) checkCount("courses", 132) checkCount("standards_bodies", 6) checkCount("standard_documents", 44) // Explicit highlight assertion var hlCount int if err := database.QueryRow("SELECT COUNT(*) FROM publications WHERE is_highlight = 1").Scan(&hlCount); err != nil { log.Fatalf("Highlight count query failed: %v", err) } if hlCount != 5 { log.Fatalf("Highlight count mismatch: expected exactly 5, got %d", hlCount) } log.Printf("✓ %-20s : %3d rows (exactly 5 highlights)", "publications(highlight)", hlCount) log.Println("=== Seeding & Verification Completed Successfully! ===") }