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
This commit is contained in:
Executable
+175
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Exports curated data from refer_landing_page/content/*.ts and app/page.tsx into backend/seed/data/*.json.
|
||||
Ensures is_highlight is properly tagged on the 5 representative publications.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
CONTENT_DIR = os.path.join(REPO_ROOT, "refer_landing_page", "content")
|
||||
SEED_DATA_DIR = os.path.join(REPO_ROOT, "backend", "seed", "data")
|
||||
PAGE_TSX = os.path.join(REPO_ROOT, "refer_landing_page", "app", "page.tsx")
|
||||
|
||||
os.makedirs(SEED_DATA_DIR, exist_ok=True)
|
||||
|
||||
HIGHLIGHT_TITLES = {
|
||||
"Globally Integrated Trust Authority (GITA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||
"Application Level Trust Authority (APPLETA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||
"mQUIC: Use of QUIC for Handover Support with Connection Migration in Wireless/Mobile Networks",
|
||||
"Use of QUIC for CoAP Transport in IoT Networks",
|
||||
"Use of QUIC for Mobile-Oriented Future Internet (Q-MOFI)",
|
||||
}
|
||||
|
||||
def clean_json_str(s):
|
||||
# Remove trailing commas before ] or }
|
||||
s = re.sub(r',\s*\]', ']', s)
|
||||
s = re.sub(r',\s*\}', '}', s)
|
||||
return s
|
||||
|
||||
def extract_json_array_from_ts(filepath, var_name):
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Match export const <var_name>[: Type] = [ ... ];
|
||||
pattern = rf'export\s+const\s+{var_name}(?:\s*:\s*[^=]+)?\s*=\s*(\[\s*[\s\S]*?\s*\]);'
|
||||
match = re.search(pattern, content)
|
||||
if not match:
|
||||
raise ValueError(f"Could not find array literal for '{var_name}' in {filepath}")
|
||||
|
||||
array_str = clean_json_str(match.group(1))
|
||||
return json.loads(array_str)
|
||||
|
||||
def main():
|
||||
print("=== Extracting TypeScript content to JSON for SQLite seeding ===")
|
||||
|
||||
# 1. Projects
|
||||
projects_file = os.path.join(CONTENT_DIR, "projects.ts")
|
||||
projects = extract_json_array_from_ts(projects_file, "researchProjects")
|
||||
with open(os.path.join(SEED_DATA_DIR, "research_projects.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(projects, f, indent=2, ensure_ascii=False)
|
||||
print(f"✓ Extracted {len(projects)} research projects")
|
||||
|
||||
# 2. Research Areas (from app/page.tsx)
|
||||
with open(PAGE_TSX, "r", encoding="utf-8") as f:
|
||||
page_src = f.read()
|
||||
areas_match = re.search(r'const\s+RESEARCH_AREAS(?:\s*:\s*[^=]+)?\s*=\s*(\[\s*[\s\S]*?\s*\]);', page_src)
|
||||
if not areas_match:
|
||||
raise ValueError("Could not find RESEARCH_AREAS in app/page.tsx")
|
||||
areas_str = clean_json_str(areas_match.group(1))
|
||||
areas_list = json.loads(areas_str)
|
||||
research_areas = [{"name_en": name, "name_kr": None, "display_order": i + 1} for i, name in enumerate(areas_list)]
|
||||
with open(os.path.join(SEED_DATA_DIR, "research_areas.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(research_areas, f, indent=2, ensure_ascii=False)
|
||||
print(f"✓ Extracted {len(research_areas)} research areas")
|
||||
|
||||
# 3. Members & Alumni
|
||||
members_file = os.path.join(CONTENT_DIR, "members.ts")
|
||||
active_members = extract_json_array_from_ts(members_file, "activeMembers")
|
||||
alumni = extract_json_array_from_ts(members_file, "alumni")
|
||||
|
||||
# Format active members with is_advisor
|
||||
formatted_members = []
|
||||
for i, m in enumerate(active_members):
|
||||
formatted_members.append({
|
||||
"name_ko": m["ko"],
|
||||
"name_en": m["en"],
|
||||
"degree": m["degree"],
|
||||
"affiliation": m.get("affiliation"),
|
||||
"email": "sjkoh@knu.ac.kr" if m["degree"] == "Professor" else None,
|
||||
"is_advisor": m["degree"] == "Professor",
|
||||
"display_order": i + 1,
|
||||
})
|
||||
with open(os.path.join(SEED_DATA_DIR, "members.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(formatted_members, f, indent=2, ensure_ascii=False)
|
||||
print(f"✓ Extracted {len(formatted_members)} active members")
|
||||
|
||||
formatted_alumni = []
|
||||
for a in alumni:
|
||||
formatted_alumni.append({
|
||||
"name_ko": a["ko"],
|
||||
"name_en": a["en"],
|
||||
"degree": a["degree"],
|
||||
"graduated_at": a["graduatedAt"],
|
||||
"major": a.get("major"),
|
||||
"current_position": a.get("currentPosition"),
|
||||
})
|
||||
with open(os.path.join(SEED_DATA_DIR, "alumni.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(formatted_alumni, f, indent=2, ensure_ascii=False)
|
||||
print(f"✓ Extracted {len(formatted_alumni)} alumni")
|
||||
|
||||
# 4. Publications & Patents
|
||||
pubs_file = os.path.join(CONTENT_DIR, "publications.ts")
|
||||
raw_pubs = extract_json_array_from_ts(pubs_file, "publications")
|
||||
raw_patents = extract_json_array_from_ts(pubs_file, "patents")
|
||||
|
||||
matched_highlights = []
|
||||
formatted_pubs = []
|
||||
for p in raw_pubs:
|
||||
title_clean = p["title"].strip()
|
||||
is_hl = title_clean in HIGHLIGHT_TITLES
|
||||
if is_hl:
|
||||
matched_highlights.append(title_clean)
|
||||
formatted_pubs.append({
|
||||
"category": p["category"],
|
||||
"title": p["title"],
|
||||
"authors": p.get("authors"),
|
||||
"venue": p["venue"],
|
||||
"volume": p.get("volume"),
|
||||
"published_at": p["publishedAt"],
|
||||
"kci": bool(p.get("kci", False)),
|
||||
"doi": p.get("doi"),
|
||||
"is_highlight": is_hl,
|
||||
})
|
||||
|
||||
if len(matched_highlights) != 5:
|
||||
raise ValueError(f"Highlight matching error: expected exactly 5 matched highlights, found {len(matched_highlights)}: {matched_highlights}")
|
||||
|
||||
with open(os.path.join(SEED_DATA_DIR, "publications.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(formatted_pubs, f, indent=2, ensure_ascii=False)
|
||||
print(f"✓ Extracted {len(formatted_pubs)} publications (exactly {len(matched_highlights)} highlights tagged)")
|
||||
|
||||
formatted_patents = []
|
||||
for pat in raw_patents:
|
||||
formatted_patents.append({
|
||||
"title": pat["title"],
|
||||
"inventors": pat["inventors"],
|
||||
"application_no": pat["applicationNo"],
|
||||
"application_at": pat["applicationAt"],
|
||||
"registration_no": pat.get("registrationNo"),
|
||||
"registration_at": pat.get("registrationAt"),
|
||||
"country": pat["country"],
|
||||
"published_at": pat["publishedAt"],
|
||||
})
|
||||
with open(os.path.join(SEED_DATA_DIR, "patents.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(formatted_patents, f, indent=2, ensure_ascii=False)
|
||||
print(f"✓ Extracted {len(formatted_patents)} patents")
|
||||
|
||||
# 5. Lectures (Semesters & Courses)
|
||||
lectures_file = os.path.join(CONTENT_DIR, "lectures.ts")
|
||||
raw_semesters = extract_json_array_from_ts(lectures_file, "semesters")
|
||||
with open(os.path.join(SEED_DATA_DIR, "lectures.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(raw_semesters, f, indent=2, ensure_ascii=False)
|
||||
|
||||
total_courses = sum(len(s.get("courses", [])) for s in raw_semesters)
|
||||
print(f"✓ Extracted {len(raw_semesters)} semesters with {total_courses} courses")
|
||||
|
||||
# 6. Standardization
|
||||
standards_file = os.path.join(CONTENT_DIR, "standards.ts")
|
||||
raw_standards = extract_json_array_from_ts(standards_file, "standardsBodies")
|
||||
with open(os.path.join(SEED_DATA_DIR, "standards.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(raw_standards, f, indent=2, ensure_ascii=False)
|
||||
|
||||
total_docs = sum(
|
||||
sum(len(p.get("documents", [])) for p in b.get("projects", []))
|
||||
for b in raw_standards
|
||||
)
|
||||
print(f"✓ Extracted {len(raw_standards)} standards bodies with {total_docs} documents")
|
||||
|
||||
print("=== Extraction completed successfully ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user