Files
landing_page/backend/cmd/export-content/main.go
T
Godopu fb6881070c feat(backend,docker,docs): implement Go/Gin/SQLite REST API server, Docker containerization, and architecture specifications
- Implement standalone Go backend API server using Gin and pure-Go SQLite (modernc.org/sqlite)
- Add 11-table schema DDL migrations (000001_init.up.sql) based on DATA_TABLE.md
- Add seed data ingestion tool (cmd/seed) and static TypeScript content exporter (cmd/export-content)
- Add configuration loader (internal/config) supporting environment variables and .env files (HOST, PORT, DB_PATH, GIN_MODE, CORS_ALLOW_ORIGINS, AUTO_MIGRATE)
- Add lightweight multi-stage Dockerfile and docker-compose.yml with persistent SQLite volume
- Add comprehensive architecture specification (ARCHITECTURE.md) and REST API interface specification (API_INTERFACE.md)
- Harmonize frontend publications page to consume isHighlight flag from database-synced content
2026-08-24 17:34:51 +09:00

345 lines
11 KiB
Go

package main
import (
"bytes"
"context"
"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"
"git.godopu.com/lab/landing_page/backend/internal/repository"
)
func marshalJSONNoEscapeHTML(data any) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(data); err != nil {
return nil, err
}
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
func formatTSFile(importStmt, exportConstName, typeAnnotation string, data any) ([]byte, error) {
jsonData, err := marshalJSONNoEscapeHTML(data)
if err != nil {
return nil, fmt.Errorf("marshal JSON: %w", err)
}
header := "// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND\n"
content := fmt.Sprintf("%s%s\n\nexport const %s: %s = %s;\n", header, importStmt, exportConstName, typeAnnotation, string(jsonData))
return []byte(content), nil
}
func writeFileSafe(path string, data []byte) error {
return os.WriteFile(path, data, 0644)
}
func main() {
config.LoadEnvFile()
defaultDB := "anl.db"
if envDB := os.Getenv("DB_PATH"); envDB != "" {
defaultDB = envDB
}
dbPath := flag.String("db", defaultDB, "Path to SQLite database file")
targetDir := flag.String("out-dir", "../refer_landing_page/content", "Target content directory")
flag.Parse()
log.Printf("Exporting content from SQLite (%s) to TypeScript files in %s...", *dbPath, *targetDir)
database, err := db.Connect(*dbPath)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer database.Close()
ctx := context.Background()
homeRepo := repository.NewHomeRepository(database)
membersRepo := repository.NewMembersRepository(database)
pubsRepo := repository.NewPublicationsRepository(database)
lecturesRepo := repository.NewLecturesRepository(database)
standardsRepo := repository.NewStandardizationRepository(database)
// 1. Projects
projects, err := homeRepo.GetResearchProjects(ctx)
if err != nil {
log.Fatalf("Get research projects: %v", err)
}
type TSProject struct {
Slug string `json:"slug"`
Title string `json:"title"`
Abstract string `json:"abstract"`
Keywords []string `json:"keywords"`
StandardsTrack *string `json:"standardsTrack,omitempty"`
StandardsOrg *string `json:"standardsOrg,omitempty"`
Period *string `json:"period,omitempty"`
Deliverables []any `json:"deliverables"`
Funder *string `json:"funder,omitempty"`
}
var tsProjects []TSProject
for _, p := range projects {
tsProjects = append(tsProjects, TSProject{
Slug: p.Slug,
Title: p.Title,
Abstract: p.Abstract,
Keywords: p.Keywords,
StandardsTrack: p.Organization,
StandardsOrg: p.StandardsOrg,
Period: p.Period,
Deliverables: []any{},
Funder: p.Funder,
})
}
pBytes, err := formatTSFile(`import { ResearchProject } from "./types";`, "researchProjects", "ResearchProject[]", tsProjects)
if err != nil {
log.Fatalf("Format projects.ts failed: %v", err)
}
if err := writeFileSafe(filepath.Join(*targetDir, "projects.ts"), pBytes); err != nil {
log.Fatalf("Write projects.ts failed: %v", err)
}
log.Println("✓ Exported projects.ts")
// 2. Members & Alumni
members, err := membersRepo.GetMembers(ctx)
if err != nil {
log.Fatalf("Get members: %v", err)
}
type TSMember struct {
Ko string `json:"ko"`
En string `json:"en"`
Degree string `json:"degree"`
Affiliation *string `json:"affiliation,omitempty"`
}
var tsMembers []TSMember
for _, m := range members {
tsMembers = append(tsMembers, TSMember{
Ko: m.NameKo,
En: m.NameEn,
Degree: m.Degree,
Affiliation: m.Affiliation,
})
}
alumni, err := membersRepo.GetAlumni(ctx)
if err != nil {
log.Fatalf("Get alumni: %v", err)
}
type TSAlumnus struct {
Ko string `json:"ko"`
En string `json:"en"`
Degree string `json:"degree"`
GraduatedAt string `json:"graduatedAt"`
Major *string `json:"major,omitempty"`
CurrentPosition *string `json:"currentPosition,omitempty"`
}
var tsAlumni []TSAlumnus
for _, a := range alumni {
tsAlumni = append(tsAlumni, TSAlumnus{
Ko: a.NameKo,
En: a.NameEn,
Degree: a.Degree,
GraduatedAt: a.GraduatedAt,
Major: a.Major,
CurrentPosition: a.CurrentPosition,
})
}
mJSON, err := marshalJSONNoEscapeHTML(tsMembers)
if err != nil {
log.Fatalf("Marshal members failed: %v", err)
}
aJSON, err := marshalJSONNoEscapeHTML(tsAlumni)
if err != nil {
log.Fatalf("Marshal alumni failed: %v", err)
}
membersTS := fmt.Sprintf("// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND\nimport { Member, Alumnus } from \"./types\";\n\nexport const activeMembers: Member[] = %s;\n\nexport const alumni: Alumnus[] = %s;\n", string(mJSON), string(aJSON))
if err := writeFileSafe(filepath.Join(*targetDir, "members.ts"), []byte(membersTS)); err != nil {
log.Fatalf("Write members.ts failed: %v", err)
}
log.Println("✓ Exported members.ts")
// 3. Publications & Patents
pubs, err := pubsRepo.GetPublications(ctx, "")
if err != nil {
log.Fatalf("Get publications: %v", err)
}
type TSPublication struct {
Category string `json:"category"`
Title string `json:"title"`
Authors *string `json:"authors,omitempty"`
Venue string `json:"venue"`
Volume *string `json:"volume,omitempty"`
PublishedAt string `json:"publishedAt"`
KCI bool `json:"kci,omitempty"`
DOI *string `json:"doi,omitempty"`
IsHighlight bool `json:"isHighlight,omitempty"`
}
var tsPubs []TSPublication
for _, p := range pubs {
tsPubs = append(tsPubs, TSPublication{
Category: p.Category,
Title: p.Title,
Authors: p.Authors,
Venue: p.Venue,
Volume: p.Volume,
PublishedAt: p.PublishedAt,
KCI: p.KCI,
DOI: p.DOI,
IsHighlight: p.IsHighlight,
})
}
patents, err := pubsRepo.GetPatents(ctx)
if err != nil {
log.Fatalf("Get patents: %v", err)
}
type TSPatent struct {
Category string `json:"category"`
Title string `json:"title"`
Inventors string `json:"inventors"`
ApplicationNo string `json:"applicationNo"`
ApplicationAt string `json:"applicationAt"`
RegistrationNo *string `json:"registrationNo,omitempty"`
RegistrationAt *string `json:"registrationAt,omitempty"`
Country string `json:"country"`
PublishedAt string `json:"publishedAt"`
}
var tsPatents []TSPatent
for _, pat := range patents {
tsPatents = append(tsPatents, TSPatent{
Category: "patent",
Title: pat.Title,
Inventors: pat.Inventors,
ApplicationNo: pat.ApplicationNo,
ApplicationAt: pat.ApplicationAt,
RegistrationNo: pat.RegistrationNo,
RegistrationAt: pat.RegistrationAt,
Country: pat.Country,
PublishedAt: pat.PublishedAt,
})
}
pubsJSON, err := marshalJSONNoEscapeHTML(tsPubs)
if err != nil {
log.Fatalf("Marshal publications failed: %v", err)
}
patentsJSON, err := marshalJSONNoEscapeHTML(tsPatents)
if err != nil {
log.Fatalf("Marshal patents failed: %v", err)
}
pubsTS := fmt.Sprintf("// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND\nimport { Publication, Patent } from \"./types\";\n\nexport const publications: Publication[] = %s;\n\nexport const patents: Patent[] = %s;\n", string(pubsJSON), string(patentsJSON))
if err := writeFileSafe(filepath.Join(*targetDir, "publications.ts"), []byte(pubsTS)); err != nil {
log.Fatalf("Write publications.ts failed: %v", err)
}
log.Println("✓ Exported publications.ts (with isHighlight metadata & unescaped HTML entities)")
// 4. Lectures
semesters, err := lecturesRepo.GetSemestersWithCourses(ctx)
if err != nil {
log.Fatalf("Get semesters: %v", err)
}
type TSCourse struct {
Code string `json:"code"`
En string `json:"en"`
Note *string `json:"note,omitempty"`
}
type TSSemester struct {
Term string `json:"term"`
Year int `json:"year"`
Courses []TSCourse `json:"courses"`
}
var tsSemesters []TSSemester
for _, s := range semesters {
var tsCourses []TSCourse
for _, c := range s.Courses {
tsCourses = append(tsCourses, TSCourse{
Code: c.Code,
En: c.NameEn,
Note: c.Note,
})
}
tsSemesters = append(tsSemesters, TSSemester{
Term: s.Term,
Year: s.Year,
Courses: tsCourses,
})
}
lecturesBytes, err := formatTSFile(`import { Semester } from "./types";`, "semesters", "Semester[]", tsSemesters)
if err != nil {
log.Fatalf("Format lectures.ts failed: %v", err)
}
if err := writeFileSafe(filepath.Join(*targetDir, "lectures.ts"), lecturesBytes); err != nil {
log.Fatalf("Write lectures.ts failed: %v", err)
}
log.Println("✓ Exported lectures.ts")
// 5. Standards
standards, err := standardsRepo.GetStandardsBodiesWithProjects(ctx)
if err != nil {
log.Fatalf("Get standards bodies: %v", err)
}
type TSDoc struct {
Title string `json:"title"`
Ref string `json:"ref"`
Status string `json:"status"`
PublishedAt *string `json:"publishedAt,omitempty"`
}
type TSStandardProject struct {
WG string `json:"wg"`
Name string `json:"name"`
Documents []TSDoc `json:"documents"`
}
type TSStandardsBody struct {
Org string `json:"org"`
Full string `json:"full"`
Scope string `json:"scope"`
Period string `json:"period"`
Role *string `json:"role,omitempty"`
Projects []TSStandardProject `json:"projects"`
}
var tsStandards []TSStandardsBody
for _, b := range standards {
var tsProjs []TSStandardProject
for _, p := range b.Projects {
var tsDocs []TSDoc
for _, d := range p.Documents {
tsDocs = append(tsDocs, TSDoc{
Title: d.Title,
Ref: d.DocRef,
Status: d.Status,
PublishedAt: d.PublishedAt,
})
}
tsProjs = append(tsProjs, TSStandardProject{
WG: p.WG,
Name: p.Name,
Documents: tsDocs,
})
}
tsStandards = append(tsStandards, TSStandardsBody{
Org: b.Org,
Full: b.FullName,
Scope: b.Scope,
Period: b.Period,
Role: b.Role,
Projects: tsProjs,
})
}
standardsBytes, err := formatTSFile(`import { StandardsBody } from "./types";`, "standardsBodies", "StandardsBody[]", tsStandards)
if err != nil {
log.Fatalf("Format standards.ts failed: %v", err)
}
if err := writeFileSafe(filepath.Join(*targetDir, "standards.ts"), standardsBytes); err != nil {
log.Fatalf("Write standards.ts failed: %v", err)
}
log.Println("✓ Exported standards.ts")
log.Println("=== All content successfully exported from SQLite! ===")
}