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:
@@ -0,0 +1,366 @@
|
|||||||
|
# AI Agent Networking Lab (ANL) — REST API Interface Specification
|
||||||
|
|
||||||
|
This document defines the RESTful API endpoints, request parameters, response schemas, and data structures provided by the ANL Go/Gin backend server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. General API Conventions
|
||||||
|
|
||||||
|
### 1.1 Base URL
|
||||||
|
- **Local Development**: `http://localhost:8080/api/v1`
|
||||||
|
- **Protocol**: HTTP/1.1 (JSON Content-Type: `application/json; charset=utf-8`)
|
||||||
|
|
||||||
|
### 1.2 Standard Response Envelope
|
||||||
|
|
||||||
|
All API endpoints strictly follow the uniform JSON response envelope structure.
|
||||||
|
|
||||||
|
#### Success Response (`200 OK`)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": <Payload Object | Array>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Error Response (`4xx / 5xx`)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"message": "Human-readable error description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 HTTP Status Codes
|
||||||
|
| Status Code | Description |
|
||||||
|
|---|---|
|
||||||
|
| `200 OK` | The request succeeded and returned the requested data. |
|
||||||
|
| `400 Bad Request` | Invalid query parameter (e.g., unsupported publication category). |
|
||||||
|
| `404 Not Found` | The requested resource was not found. |
|
||||||
|
| `500 Internal Server Error` | Unexpected server-side failure during database query or processing. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. API Endpoints Specification
|
||||||
|
|
||||||
|
### 2.1 System & Health
|
||||||
|
|
||||||
|
#### `GET /api/v1/health`
|
||||||
|
Checks backend API service health and database availability.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"service": "anl-backend-api",
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.2 Home & Overview
|
||||||
|
|
||||||
|
#### `GET /api/v1/research-projects`
|
||||||
|
Retrieves the list of all active research projects.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"slug": "iot-standards",
|
||||||
|
"title": "AIoT & AI-RAN",
|
||||||
|
"abstract": "AIoT & AI-RAN investigates agent-based IoT architectures in which autonomous AI agents are distributed across sensor, edge, and radio access layers. The project studies agent-to-agent coordination over high-performance transport (gRPC/QUIC) bridged with constrained edge protocols (MQTT/CoAP), targeting AI-RAN designs for distributed autonomous control.",
|
||||||
|
"keywords": [
|
||||||
|
"AIoT",
|
||||||
|
"agent-based IoT",
|
||||||
|
"AI-RAN",
|
||||||
|
"constrained edge protocols",
|
||||||
|
"distributed autonomous control"
|
||||||
|
],
|
||||||
|
"organization": "National Research Foundation of Korea (NRF)",
|
||||||
|
"standards_org": "NRF",
|
||||||
|
"period": "2026 ~ Present",
|
||||||
|
"funder": null,
|
||||||
|
"created_at": "2026-08-24T16:47:14Z",
|
||||||
|
"updated_at": "2026-08-24T16:47:14Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/research-areas`
|
||||||
|
Retrieves the list of 14 core research areas ordered by `display_order`.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name_en": "Quick UDP Internet Connection (QUIC)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name_en": "QUIC-based Mobility and Multi-Streaming Support",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/stats/summary`
|
||||||
|
Retrieves aggregated top-level metrics for the homepage hero section.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"intl_publications": 68,
|
||||||
|
"standardization_docs": 44,
|
||||||
|
"patents": 75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.3 Members & Alumni
|
||||||
|
|
||||||
|
#### `GET /api/v1/members`
|
||||||
|
Retrieves all 16 active members (including the professor and graduate researchers) ordered by `display_order`.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name_ko": "고석주",
|
||||||
|
"name_en": "Seok-Joo Koh",
|
||||||
|
"degree": "Professor",
|
||||||
|
"affiliation": "School of Computer Science and Engineering",
|
||||||
|
"email": "sjkoh@knu.ac.kr",
|
||||||
|
"is_advisor": true,
|
||||||
|
"display_order": 1,
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name_ko": "김경호",
|
||||||
|
"name_en": "Kyeong-Ho Kim",
|
||||||
|
"degree": "PhDStudent",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 2,
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/alumni`
|
||||||
|
Retrieves all 60 alumni sorted chronologically by `graduated_at DESC`.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name_ko": "도경진",
|
||||||
|
"name_en": "Gyeong-Jin Do",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2024-02",
|
||||||
|
"major": "School of Electronic and Electrical Engineering",
|
||||||
|
"current_position": "LIG Nex1, Senior Researcher",
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.4 Publications & Intellectual Property
|
||||||
|
|
||||||
|
#### `GET /api/v1/publications`
|
||||||
|
Retrieves list of academic publications. Supports filtering by publication category.
|
||||||
|
|
||||||
|
- **Query Parameters**:
|
||||||
|
- `category` (optional, string): Filter by category. Allowed values: `intl-journal-conf`, `domestic-journal-conf`.
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"category": "intl-journal-conf",
|
||||||
|
"title": "Globally Integrated Trust Authority (GITA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||||
|
"authors": "Seok-Joo Koh, ...",
|
||||||
|
"venue": "IEEE Transactions on Network and Service Management",
|
||||||
|
"volume": "Vol. 23, pp. 4490~4505, April 2026",
|
||||||
|
"published_at": "2026-04",
|
||||||
|
"kci": false,
|
||||||
|
"doi": "10.1109/TNSM.2026.10908861",
|
||||||
|
"is_highlight": true,
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/publications/highlights`
|
||||||
|
Retrieves the **5 designated top representative publications** (`WHERE is_highlight = 1`).
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"category": "intl-journal-conf",
|
||||||
|
"title": "Globally Integrated Trust Authority (GITA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||||
|
"authors": "Seok-Joo Koh, ...",
|
||||||
|
"venue": "IEEE Transactions on Network and Service Management",
|
||||||
|
"volume": "Vol. 23, pp. 4490~4505, April 2026",
|
||||||
|
"published_at": "2026-04",
|
||||||
|
"kci": false,
|
||||||
|
"doi": "10.1109/TNSM.2026.10908861",
|
||||||
|
"is_highlight": true,
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"category": "intl-journal-conf",
|
||||||
|
"title": "Application Level Trust Authority (APPLETA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||||
|
"authors": "Seok-Joo Koh, ...",
|
||||||
|
"venue": "IEEE Transactions on Consumer Electronics",
|
||||||
|
"volume": "Vol. 71, pp. 1120~1135, Jan. 2026",
|
||||||
|
"published_at": "2026-01",
|
||||||
|
"kci": false,
|
||||||
|
"doi": "10.1109/TCE.2025.3524673",
|
||||||
|
"is_highlight": true,
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/patents`
|
||||||
|
Retrieves all 75 registered and pending patents sorted by `published_at DESC`.
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "다중 인터페이스 기반 QUIC 핸드오버 지원 장치 및 방법",
|
||||||
|
"inventors": "고석주, 김경호",
|
||||||
|
"application_no": "10-2024-0012345",
|
||||||
|
"application_at": "2024-01-15",
|
||||||
|
"registration_no": "10-2567890",
|
||||||
|
"registration_at": "2025-05-20",
|
||||||
|
"country": "KR",
|
||||||
|
"published_at": "2025-05-20",
|
||||||
|
"created_at": "2026-08-24T16:47:14Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.5 Lectures & Curriculum
|
||||||
|
|
||||||
|
#### `GET /api/v1/semesters`
|
||||||
|
Retrieves all 45 academic semesters with nested course lists, ordered by `year DESC` and `term` (Fall before Spring).
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"year": 2026,
|
||||||
|
"term": "Spring",
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"semester_id": 1,
|
||||||
|
"code": "COMP0411",
|
||||||
|
"name_en": "Computer Networks",
|
||||||
|
"name_kr": null,
|
||||||
|
"note": "Undergraduate Core",
|
||||||
|
"display_order": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"year": 2025,
|
||||||
|
"term": "Fall",
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"semester_id": 2,
|
||||||
|
"code": "COMP0754",
|
||||||
|
"name_en": "Advanced Internet Protocol",
|
||||||
|
"name_kr": null,
|
||||||
|
"note": "Graduate Course",
|
||||||
|
"display_order": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.6 Standardization
|
||||||
|
|
||||||
|
#### `GET /api/v1/standards-bodies`
|
||||||
|
Retrieves all 6 standardization bodies with 3-tier hierarchy (`Body → Project (WG) → Documents[]`).
|
||||||
|
|
||||||
|
- **Response Payload**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"org": "IEC TC100",
|
||||||
|
"full_name": "International Electrotechnical Commission: Technical Committee 100",
|
||||||
|
"scope": "international",
|
||||||
|
"period": "2018 ~ Present",
|
||||||
|
"role": "Project Editor / Active Contributor",
|
||||||
|
"display_order": 1,
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "WG 12",
|
||||||
|
"name": "MCM: Multilateral and Collaborative Metaverse",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"body_id": 1,
|
||||||
|
"wg": "WG 12",
|
||||||
|
"project_name": "MCM: Multilateral and Collaborative Metaverse",
|
||||||
|
"title": "Conceptual model for multilateral and collaborative metaverse systems",
|
||||||
|
"doc_ref": "IEC 63430 ED1",
|
||||||
|
"status": "IS",
|
||||||
|
"published_at": "2026-03"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
# AI Agent Networking Lab (ANL) — System Architecture Specification
|
||||||
|
|
||||||
|
## 1. System Overview
|
||||||
|
|
||||||
|
AI Agent Networking Lab (ANL) 시스템은 연구실의 학술 실적(논문, 특허), 연구 과제, 구성원, 강의, 표준화 기고서 등의 데이터를 효율적으로 관리하고 제공하기 위한 **하이브리드 데이터 아키텍처(Decoupled Static-Export & Headless API Architecture)**로 설계되었습니다.
|
||||||
|
|
||||||
|
Next.js 기반의 고성능 정적 웹사이트(SSG)와 Go/Gin 및 SQLite 기반의 독립형 헤드리스(Headless) 백엔드 API 서버를 분리하여, **완벽한 정적 빌드 성능(0 Dynamic Route)**과 **단일 진실 공급원(Single Source of Truth) 기반의 데이터 관리 편의성**을 동시에 달성합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. High-Level Architecture Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph "Storage & Ingestion Layer"
|
||||||
|
SEED["Seed Data JSON<br/>(backend/seed/data/*.json)"] -->|cmd/seed| SQLITE[("SQLite Database<br/>(anl.db - WAL Mode)")]
|
||||||
|
SQLITE -->|cmd/export-content| TS_CONTENT["Static TypeScript Content<br/>(refer_landing_page/content/*.ts)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Backend API Serving Layer (Go / Gin)"
|
||||||
|
API_MAIN["API Server Binary<br/>(backend/cmd/api)"] --> ROUTER["Gin Engine & Router<br/>(internal/router)"]
|
||||||
|
ROUTER --> HANDLERS["Domain Handlers<br/>(internal/handlers)"]
|
||||||
|
HANDLERS --> REPO["Repository Layer<br/>(internal/repository)"]
|
||||||
|
REPO -->|database/sql + modernc.org/sqlite| SQLITE
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Frontend Presentation Layer (Next.js 14 SSG)"
|
||||||
|
TS_CONTENT --> NEXT_BUILD["Next.js Static Build Engine<br/>(○ Static / ● SSG)"]
|
||||||
|
NEXT_BUILD --> SSG_PAGES["Static HTML Routes<br/>(/, /members, /publications, etc.)"]
|
||||||
|
ADMIN["Future Admin Dashboard<br/>(CRUD Client)"] -.->|REST API /api/v1| ROUTER
|
||||||
|
CLIENT_BROWSER["End Users / Browsers"] -->|Fast CDN / Nginx Serving| SSG_PAGES
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Core Architectural Principles
|
||||||
|
|
||||||
|
### 3.1 Single Source of Truth (SSOT)
|
||||||
|
- 모든 연구실 데이터(과제, 멤버, 논문, 특허, 강의, 표준화)는 **SQLite 데이터베이스(`anl.db`)**를 단일 원천으로 관리합니다.
|
||||||
|
- 데이터 갱신 시 `cmd/export-content` 도구를 통해 프론트엔드의 `content/*.ts` 파일로 동기화되어 코드와 데이터의 정합성을 보장합니다.
|
||||||
|
|
||||||
|
### 3.2 Zero-CGO Pure Go & Portability
|
||||||
|
- SQLite 드라이버로 **`modernc.org/sqlite`**를 채택하여 CGO 의존성을 완전히 제거했습니다.
|
||||||
|
- C 컴파일러 없이도 단일 실행 바이너리(`bin/api`, `bin/seed`, `bin/export-content`)로 컴파일되며, macOS, Linux, Docker 컨테이너 등 어떤 환경에서도 즉시 구동됩니다.
|
||||||
|
|
||||||
|
### 3.3 High-Performance Read Optimization (WAL Mode)
|
||||||
|
- 랜딩페이지 특성상 읽기(Read) 요청이 99% 이상이므로, SQLite 연결 시 **WAL(Write-Ahead Logging)** 모드와 `busy_timeout=5000`을 적용하여 동시 다발적인 읽기 트랜잭션에서 잠금 경합 없는 고속 처리를 보장합니다.
|
||||||
|
|
||||||
|
### 3.4 Strict Static Build Compliance (DM-03 / AC-17)
|
||||||
|
- 프론트엔드 빌드 시 런타임 API 호출 의존성을 배제하여 Next.js 정적 사전 렌더링(`○ Static` / `● SSG`) 원칙을 100% 준수합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Layered Module Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── cmd/
|
||||||
|
│ ├── api/main.go # Gin HTTP REST API 서버 엔드포인트 (:8080)
|
||||||
|
│ ├── seed/main.go # SQLite DDL 마이그레이션 및 JSON 시드 데이터 인제스트 도구
|
||||||
|
│ └── export-content/main.go # SQLite DB 데이터를 프론트엔드 content/*.ts 파일로 내보내는 도구
|
||||||
|
├── internal/
|
||||||
|
│ ├── db/
|
||||||
|
│ │ ├── db.go # SQLite 커넥션 풀 관리 및 PRAGMA 설정 (WAL, foreign_keys)
|
||||||
|
│ │ └── migrations/ # golang-migrate SQL DDL 스크립트 (000001_init.up.sql / down.sql)
|
||||||
|
│ ├── models/ # 도메인별 데이터 구조체 모델
|
||||||
|
│ │ ├── home.go # ResearchProject, ResearchArea, StatsSummary
|
||||||
|
│ │ ├── members.go # Member, Alumnus
|
||||||
|
│ │ ├── publications.go # Publication, Patent
|
||||||
|
│ │ ├── lectures.go # Semester, Course
|
||||||
|
│ │ └── standardization.go # StandardsBody, StandardProject, StandardDocument
|
||||||
|
│ ├── repository/ # database/sql 기반 비즈니스 쿼리 계층 (N+1 방지 일괄 쿼리 최적화)
|
||||||
|
│ ├── handlers/ # Gin HTTP 요청 바인딩 및 JSON 응답 핸들러
|
||||||
|
│ ├── router/ # API 라우트 등록, CORS 미들웨어 및 통합 테스트
|
||||||
|
│ └── httpx/ # 표준 JSON 응답 봉투 ({ data: ... } / { error: ... })
|
||||||
|
├── seed/data/ # 손실 없이 추출된 원본 JSON 데이터셋
|
||||||
|
└── Makefile # 빌드, 테스트, 시드, 실행 자동화 타겟
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Data Flow Workflows
|
||||||
|
|
||||||
|
### 5.1 Initial Seeding & Database Bootstrap
|
||||||
|
1. `make seed` 실행
|
||||||
|
2. `cmd/seed`가 SQLite 데이터베이스 파일(`anl.db`)을 생성
|
||||||
|
3. 임베디드된 마이그레이션 DDL(`000001_init.up.sql`)을 실행하여 11개 테이블과 인덱스 생성
|
||||||
|
4. `seed/data/*.json` 파일들을 읽어 트랜잭션 내에서 일괄 삽입
|
||||||
|
5. 각 테이블별 행(Row) 개수 및 5대 대표 논문(`is_highlight=1`) 정합성을 자동 검증
|
||||||
|
|
||||||
|
### 5.2 Content Export & Frontend Build
|
||||||
|
1. 데이터베이스 변경 후 `make export-content` 실행
|
||||||
|
2. `cmd/export-content`가 SQLite에서 최신 데이터를 조회
|
||||||
|
3. `refer_landing_page/content/*.ts` 파일들을 타입 안전한 TypeScript 상수로 덮어쓰기 생성
|
||||||
|
4. Next.js 빌드(`npm run build`)를 통해 15개 정적 HTML 페이지가 100% SSG로 생성
|
||||||
|
|
||||||
|
### 5.3 Live REST API Serving
|
||||||
|
1. `make run` 또는 `./bin/api` 실행 (기본 포트 `:8080`)
|
||||||
|
2. 클라이언트가 `/api/v1/*` 엔드포인트로 HTTP GET 요청
|
||||||
|
3. Gin 라우터가 요청을 파싱하고 적절한 도메인 핸들러로 전달
|
||||||
|
4. 리포지토리 계층이 파라미터화된 SQL 쿼리로 SQLite 조회 후 일관된 JSON Envelope 형식으로 반환
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
# AI Agent Networking Lab (ANL) — Backend Database Schema Specification
|
||||||
|
|
||||||
|
This document defines the SQLite database schema and REST API entities to decouple data from the frontend landing page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. `/home` (Home & Overview)
|
||||||
|
|
||||||
|
### 1.1 `research_projects` (연구 과제)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `slug` (TEXT, UNIQUE, NOT NULL) — e.g. `iot-standards`, `ccis`, `vlc-iot`
|
||||||
|
- `title` (TEXT, NOT NULL) — e.g. `AIoT & AI-RAN`, `MCM`, `MVQM`
|
||||||
|
- `abstract` (TEXT, NOT NULL)
|
||||||
|
- `keywords` (TEXT, JSON Array) — e.g. `["AIoT", "agent-based IoT", "AI-RAN"]`
|
||||||
|
- `organization` (TEXT) — e.g. `National Research Foundation of Korea (NRF)`, `IEC/TC 100/WG 12 supported by KEIT`
|
||||||
|
- `standards_org` (TEXT) — e.g. `NRF`, `IEC TC100`
|
||||||
|
- `period` (TEXT) — e.g. `2026 ~ Present`
|
||||||
|
- `funder` (TEXT, OPTIONAL)
|
||||||
|
- `created_at` (DATETIME)
|
||||||
|
- `updated_at` (DATETIME)
|
||||||
|
|
||||||
|
### 1.2 `research_areas` (연구 분야)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `name_en` (TEXT, NOT NULL) — e.g. `AI Agent Networking`, `QUIC & Transport Protocol`
|
||||||
|
- `name_kr` (TEXT) — e.g. `AI 에이전트 네트워킹`
|
||||||
|
- `display_order` (INTEGER, DEFAULT 0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `/members` (구성원 및 졸업생)
|
||||||
|
|
||||||
|
### 2.1 `members` (현재 연구원)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `name_ko` (TEXT, NOT NULL) — e.g. `고석주`
|
||||||
|
- `name_en` (TEXT, NOT NULL) — e.g. `Seok-Joo Koh`
|
||||||
|
- `degree` (TEXT, NOT NULL) — `Professor`, `PhD`, `PhDCandidate`, `PhDStudent`, `MSCandidate`, `MSStudent`
|
||||||
|
- `affiliation` (TEXT) — e.g. `School of Computer Science and Engineering`
|
||||||
|
- `email` (TEXT)
|
||||||
|
- `is_advisor` (BOOLEAN, DEFAULT FALSE)
|
||||||
|
- `display_order` (INTEGER, DEFAULT 0)
|
||||||
|
- `created_at` (DATETIME)
|
||||||
|
|
||||||
|
### 2.2 `alumni` (졸업생)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `name_ko` (TEXT, NOT NULL)
|
||||||
|
- `name_en` (TEXT, NOT NULL)
|
||||||
|
- `degree` (TEXT, NOT NULL) — `PhD`, `MS`
|
||||||
|
- `graduated_at` (TEXT, NOT NULL) — `YYYY-MM` (e.g. `2024-02`)
|
||||||
|
- `major` (TEXT) — e.g. `Computer Science and Engineering`
|
||||||
|
- `current_position` (TEXT) — e.g. `Samsung Electronics, Senior Researcher`
|
||||||
|
- `created_at` (DATETIME)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `/publications` (연구 실적 & 지식재산권)
|
||||||
|
|
||||||
|
### 3.1 `publications` (학술 논문)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `category` (TEXT, NOT NULL) — `intl-journal-conf`, `domestic-journal-conf`
|
||||||
|
- `title` (TEXT, NOT NULL)
|
||||||
|
- `authors` (TEXT) — e.g. `Seok-Joo Koh, ...`
|
||||||
|
- `venue` (TEXT, NOT NULL) — e.g. `IEEE Transactions on Network and Service Management`
|
||||||
|
- `volume` (TEXT) — e.g. `Vol. 23, pp. 4490~4505, April 2026`
|
||||||
|
- `published_at` (TEXT, NOT NULL) — `YYYY-MM` or `YYYY-MM-DD`
|
||||||
|
- `kci` (BOOLEAN, DEFAULT FALSE)
|
||||||
|
- `doi` (TEXT)
|
||||||
|
- `is_highlight` (BOOLEAN, DEFAULT FALSE) — For Top Representative Highlights
|
||||||
|
- `created_at` (DATETIME)
|
||||||
|
|
||||||
|
### 3.2 `patents` (특허)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `title` (TEXT, NOT NULL)
|
||||||
|
- `inventors` (TEXT, NOT NULL)
|
||||||
|
- `application_no` (TEXT, NOT NULL)
|
||||||
|
- `application_at` (TEXT, NOT NULL) — `YYYY-MM-DD`
|
||||||
|
- `registration_no` (TEXT)
|
||||||
|
- `registration_at` (TEXT) — `YYYY-MM-DD`
|
||||||
|
- `country` (TEXT, NOT NULL) — e.g. `KR`, `US`
|
||||||
|
- `published_at` (TEXT, NOT NULL)
|
||||||
|
- `created_at` (DATETIME)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `/lectures` (강의)
|
||||||
|
|
||||||
|
### 4.1 `semesters` (학기)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `year` (INTEGER, NOT NULL) — e.g. `2026`
|
||||||
|
- `term` (TEXT, NOT NULL) — `Spring`, `Fall`
|
||||||
|
|
||||||
|
### 4.2 `courses` (개설 과목)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `semester_id` (INTEGER, NOT NULL, FK -> semesters.id)
|
||||||
|
- `code` (TEXT, NOT NULL) — e.g. `COMP0411`
|
||||||
|
- `name_en` (TEXT, NOT NULL) — e.g. `Computer Networks`
|
||||||
|
- `name_kr` (TEXT)
|
||||||
|
- `note` (TEXT) — e.g. `Graduate Course`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `/standardization` (국제 표준화)
|
||||||
|
|
||||||
|
### 5.1 `standards_bodies` (표준화 기구)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `org` (TEXT, NOT NULL) — e.g. `IEC TC100`, `ITU-T SG17`
|
||||||
|
- `full_name` (TEXT, NOT NULL)
|
||||||
|
- `scope` (TEXT, NOT NULL) — `international`, `domestic`
|
||||||
|
- `period` (TEXT, NOT NULL) — e.g. `2018 ~ Present`
|
||||||
|
- `role` (TEXT) — e.g. `Project Editor`
|
||||||
|
|
||||||
|
### 5.2 `standard_documents` (표준 문서 및 기고서)
|
||||||
|
- `id` (INTEGER, PK, AUTOINCREMENT)
|
||||||
|
- `body_id` (INTEGER, NOT NULL, FK -> standards_bodies.id)
|
||||||
|
- `wg` (TEXT, NOT NULL) — e.g. `WG 12`
|
||||||
|
- `project_name` (TEXT, NOT NULL)
|
||||||
|
- `title` (TEXT, NOT NULL)
|
||||||
|
- `doc_ref` (TEXT, NOT NULL) — e.g. `IEC 63430 ED1`
|
||||||
|
- `status` (TEXT, NOT NULL) — `IS`, `TS`, `TR`, `CDV`, `WDTR`, `InProgress`
|
||||||
|
- `published_at` (TEXT)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# AI Agent Networking Lab (ANL) Backend API Server Configuration
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Server binding host and port
|
||||||
|
HOST=0.0.0.0
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
# SQLite database file path (Inside container: /app/data/anl.db)
|
||||||
|
DB_PATH=anl.db
|
||||||
|
|
||||||
|
# Gin runtime mode: release | debug | test
|
||||||
|
GIN_MODE=release
|
||||||
|
|
||||||
|
# Allowed CORS Origins (Comma separated, or * for all)
|
||||||
|
CORS_ALLOW_ORIGINS=*
|
||||||
|
|
||||||
|
# Automatically run SQL schema migrations on startup (true | false)
|
||||||
|
AUTO_MIGRATE=true
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Binaries and build outputs
|
||||||
|
/bin/
|
||||||
|
|
||||||
|
# SQLite databases and WAL journal files
|
||||||
|
*.db
|
||||||
|
*.db-wal
|
||||||
|
*.db-shm
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Stage 1: Build the Go Binaries
|
||||||
|
# ==============================================================================
|
||||||
|
FROM golang:1.26-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install git and certificates
|
||||||
|
RUN apk add --no-cache git ca-certificates tzdata
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy backend source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build pure-Go static binaries
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /bin/api ./cmd/api && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /bin/seed ./cmd/seed && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /bin/export-content ./cmd/export-content
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Stage 2: Minimal Runtime Image
|
||||||
|
# ==============================================================================
|
||||||
|
FROM alpine:3.20
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install ca-certificates and sqlite cli for maintenance
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata sqlite curl && \
|
||||||
|
addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||||
|
|
||||||
|
# Create persistent database storage directory
|
||||||
|
RUN mkdir -p /app/data && chown -R appuser:appgroup /app/data
|
||||||
|
|
||||||
|
# Copy built binaries from builder
|
||||||
|
COPY --from=builder /bin/api /app/bin/api
|
||||||
|
COPY --from=builder /bin/seed /app/bin/seed
|
||||||
|
COPY --from=builder /bin/export-content /app/bin/export-content
|
||||||
|
|
||||||
|
# Copy seed data
|
||||||
|
COPY --from=builder /app/seed /app/seed
|
||||||
|
|
||||||
|
# Copy environment template
|
||||||
|
COPY --from=builder /app/.env.example /app/.env
|
||||||
|
|
||||||
|
# Set environment defaults for container
|
||||||
|
ENV HOST=0.0.0.0 \
|
||||||
|
PORT=8080 \
|
||||||
|
DB_PATH=/app/data/anl.db \
|
||||||
|
GIN_MODE=release \
|
||||||
|
CORS_ALLOW_ORIGINS=* \
|
||||||
|
AUTO_MIGRATE=true
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8080/api/v1/health || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/bin/api"]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.PHONY: all build run seed export-content test fmt fmt-check vet clean
|
||||||
|
|
||||||
|
BIN_DIR := bin
|
||||||
|
DB_PATH := anl.db
|
||||||
|
|
||||||
|
all: fmt-check vet build test
|
||||||
|
|
||||||
|
build:
|
||||||
|
@mkdir -p $(BIN_DIR)
|
||||||
|
@echo "Building api server..."
|
||||||
|
go build -o $(BIN_DIR)/api ./cmd/api
|
||||||
|
@echo "Building seeder..."
|
||||||
|
go build -o $(BIN_DIR)/seed ./cmd/seed
|
||||||
|
@echo "Building content exporter..."
|
||||||
|
go build -o $(BIN_DIR)/export-content ./cmd/export-content
|
||||||
|
|
||||||
|
run: build
|
||||||
|
./$(BIN_DIR)/api --db $(DB_PATH)
|
||||||
|
|
||||||
|
seed: build
|
||||||
|
./$(BIN_DIR)/seed --db $(DB_PATH) --seed-dir seed/data
|
||||||
|
|
||||||
|
export-content: build
|
||||||
|
./$(BIN_DIR)/export-content --db $(DB_PATH) --out-dir ../refer_landing_page/content
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -w .
|
||||||
|
|
||||||
|
fmt-check:
|
||||||
|
@test -z "$$(gofmt -l .)" || (echo "gofmt check failed on files:" && gofmt -l . && exit 1)
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
test: fmt-check vet
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BIN_DIR) *.db*
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
# AI Agent Networking Lab (ANL) — Go/Gin/SQLite Backend API
|
||||||
|
|
||||||
|
This module implements the standalone backend REST API server, SQLite database storage, seed ingestion pipeline, and static content exporter for the ANL landing page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏛 Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
+-------------------------------------------------------------+
|
||||||
|
| SQLite (anl.db) |
|
||||||
|
| 11 Relational Tables (Projects, Members, Pubs, Lectures) |
|
||||||
|
+-------------------------------------------------------------+
|
||||||
|
▲ ▲
|
||||||
|
│ (cmd/seed) │ (SQL query)
|
||||||
|
│ │
|
||||||
|
+-------------------------------+ +-------------------------+
|
||||||
|
| seed/data/*.json | | Gin REST API Server |
|
||||||
|
| (extracted from content/*.ts) | | (cmd/api, :8080) |
|
||||||
|
+-------------------------------+ +-------------------------+
|
||||||
|
│
|
||||||
|
+-------------------------+
|
||||||
|
| Content Exporter |
|
||||||
|
| (cmd/export-content) |
|
||||||
|
+-------------------------+
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
+-------------------------+
|
||||||
|
| Next.js Static SSG |
|
||||||
|
| (refer_landing_page/) |
|
||||||
|
+-------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### 1. Build Binaries
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
Creates `bin/api`, `bin/seed`, and `bin/export-content`.
|
||||||
|
|
||||||
|
### 2. Run Migrations & Seed Database
|
||||||
|
```bash
|
||||||
|
make seed
|
||||||
|
```
|
||||||
|
Creates `anl.db`, executes SQL DDL migrations, inserts seed data, and verifies all 11 table row counts and the 5 designated highlights.
|
||||||
|
|
||||||
|
### 3. Run Backend API Server
|
||||||
|
```bash
|
||||||
|
make run
|
||||||
|
# Or with options:
|
||||||
|
./bin/api --port 8080 --db anl.db
|
||||||
|
```
|
||||||
|
Server starts at `http://localhost:8080/api/v1`.
|
||||||
|
|
||||||
|
### 4. Run Unit & Integration Tests
|
||||||
|
```bash
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Export Content to Next.js Frontend
|
||||||
|
```bash
|
||||||
|
make export-content
|
||||||
|
```
|
||||||
|
Regenerates `refer_landing_page/content/*.ts` from SQLite with typed `isHighlight` metadata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📡 REST API Endpoints (`/api/v1`)
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/api/v1/health` | Service health status check |
|
||||||
|
| `GET` | `/api/v1/research-projects` | 3 research project entities |
|
||||||
|
| `GET` | `/api/v1/research-areas` | 14 core research areas ordered by `display_order` |
|
||||||
|
| `GET` | `/api/v1/stats/summary` | Aggregated counts (`intl_publications`, `standardization_docs`, `patents`) |
|
||||||
|
| `GET` | `/api/v1/members` | 16 active members (with `is_advisor` flag) |
|
||||||
|
| `GET` | `/api/v1/alumni` | 60 alumni ordered chronologically |
|
||||||
|
| `GET` | `/api/v1/publications` | 148 publications (supports `?category=intl-journal-conf` or `domestic-journal-conf`) |
|
||||||
|
| `GET` | `/api/v1/publications/highlights` | 5 designated top representative publications (`WHERE is_highlight = 1`) |
|
||||||
|
| `GET` | `/api/v1/patents` | 75 intellectual property patents |
|
||||||
|
| `GET` | `/api/v1/semesters` | 45 semesters with nested courses, ordered by year DESC and term |
|
||||||
|
| `GET` | `/api/v1/standards-bodies` | 6 standards bodies with nested projects and documents (deterministic ordering) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Environment Configuration (`.env`)
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env` to customize runtime settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `HOST` | `0.0.0.0` | Binding host IP address |
|
||||||
|
| `PORT` | `8080` | Server listening port |
|
||||||
|
| `DB_PATH` | `anl.db` | SQLite database file path |
|
||||||
|
| `GIN_MODE` | `release` | Gin runtime mode (`release`, `debug`, `test`) |
|
||||||
|
| `CORS_ALLOW_ORIGINS` | `*` | Allowed CORS origins (e.g. `https://anl.knu.ac.kr`) |
|
||||||
|
| `AUTO_MIGRATE` | `true` | Apply database migrations automatically on startup |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐳 Docker & Containerization
|
||||||
|
|
||||||
|
### Build & Run Container
|
||||||
|
```bash
|
||||||
|
docker build -t anl-backend-api .
|
||||||
|
docker run -p 8080:8080 -v anl-data:/app/data anl-backend-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
Run the full backend service with automated healthchecks and volume persistence from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Database Schema & Specifications
|
||||||
|
|
||||||
|
* **Architecture**: See [ARCHITECTURE.md](../ARCHITECTURE.md)
|
||||||
|
* **REST API Interfaces**: See [API_INTERFACE.md](../API_INTERFACE.md)
|
||||||
|
* **Database Schema Spec**: See [DATA_TABLE.md](../DATA_TABLE.md) and [000001_init.up.sql](internal/db/migrations/000001_init.up.sql)
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"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/router"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load environment variables & .env
|
||||||
|
cfg := config.Load()
|
||||||
|
|
||||||
|
// Optional CLI flags override environment defaults
|
||||||
|
portFlag := flag.Int("port", 0, "Port for the Gin server to listen on (overrides PORT)")
|
||||||
|
hostFlag := flag.String("host", "", "Host for the Gin server to bind on (overrides HOST)")
|
||||||
|
dbPathFlag := flag.String("db", "", "Path to SQLite database file (overrides DB_PATH)")
|
||||||
|
ginModeFlag := flag.String("mode", "", "Gin mode: release, debug, test (overrides GIN_MODE)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *portFlag > 0 {
|
||||||
|
cfg.Port = *portFlag
|
||||||
|
}
|
||||||
|
if *hostFlag != "" {
|
||||||
|
cfg.Host = *hostFlag
|
||||||
|
}
|
||||||
|
if *dbPathFlag != "" {
|
||||||
|
cfg.DBPath = *dbPathFlag
|
||||||
|
}
|
||||||
|
if *ginModeFlag != "" {
|
||||||
|
cfg.GinMode = *ginModeFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
gin.SetMode(cfg.GinMode)
|
||||||
|
|
||||||
|
log.Printf("Connecting to SQLite database at %s...", cfg.DBPath)
|
||||||
|
database, err := db.Connect(cfg.DBPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect to database: %v", err)
|
||||||
|
}
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
if cfg.AutoMigrate {
|
||||||
|
log.Println("Ensuring database schema migrations are applied...")
|
||||||
|
if err := db.RunMigrations(database); err != nil {
|
||||||
|
log.Fatalf("Migration failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := router.SetupRouter(database, cfg.CORSAllowOrigins)
|
||||||
|
|
||||||
|
addr := cfg.Address()
|
||||||
|
log.Printf("Starting ANL Backend API server on http://%s/api/v1 (mode: %s)...", addr, cfg.GinMode)
|
||||||
|
if err := r.Run(addr); err != nil {
|
||||||
|
log.Fatalf("Server failed to run: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
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! ===")
|
||||||
|
}
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
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! ===")
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
module git.godopu.com/lab/landing_page/backend
|
||||||
|
|
||||||
|
go 1.26.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.12.0
|
||||||
|
github.com/stretchr/testify v1.12.1
|
||||||
|
modernc.org/sqlite v1.57.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||||
|
github.com/bytedance/sonic v1.15.2 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.5.2 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.30.3 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.5.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||||
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.61.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.2 // indirect
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
|
golang.org/x/arch v0.30.0 // indirect
|
||||||
|
golang.org/x/crypto v0.55.0 // indirect
|
||||||
|
golang.org/x/net v0.58.0 // indirect
|
||||||
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
|
golang.org/x/text v0.41.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.12 // indirect
|
||||||
|
modernc.org/libc v1.75.4 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.12.1 // indirect
|
||||||
|
)
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||||
|
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||||
|
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
|
||||||
|
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
|
||||||
|
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
|
||||||
|
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||||
|
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||||
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
|
||||||
|
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||||
|
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
|
||||||
|
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
|
github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
|
||||||
|
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
|
||||||
|
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||||
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||||
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
|
github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
|
||||||
|
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc=
|
||||||
|
github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
|
golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU=
|
||||||
|
golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||||
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
|
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||||
|
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||||
|
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||||
|
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||||
|
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||||
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
|
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||||
|
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||||
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
|
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
|
||||||
|
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
|
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
|
||||||
|
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
|
||||||
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
|
||||||
|
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.75.4 h1:EHQJNYDC6LiDIOqM76862xe4frbDc7IzEOZTtGxZV8Q=
|
||||||
|
modernc.org/libc v1.75.4/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
||||||
|
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||||
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
|
||||||
|
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds all server configuration derived from environment variables and flags.
|
||||||
|
type Config struct {
|
||||||
|
Port int
|
||||||
|
Host string
|
||||||
|
DBPath string
|
||||||
|
GinMode string
|
||||||
|
CORSAllowOrigins string
|
||||||
|
AutoMigrate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadEnvFile reads a simple .env file if it exists and loads variables into environment without overriding existing ones.
|
||||||
|
func LoadEnvFile(filenames ...string) {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
filenames = []string{".env", "../.env"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, filename := range filenames {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
val := strings.TrimSpace(parts[1])
|
||||||
|
// Strip optional surrounding quotes
|
||||||
|
val = strings.Trim(val, `"'`)
|
||||||
|
|
||||||
|
// Only set if not already present in environment
|
||||||
|
if _, exists := os.LookupEnv(key); !exists {
|
||||||
|
_ = os.Setenv(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = file.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads and parses all configuration settings from the environment.
|
||||||
|
func Load() *Config {
|
||||||
|
LoadEnvFile()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Port: 8080,
|
||||||
|
Host: "0.0.0.0",
|
||||||
|
DBPath: "anl.db",
|
||||||
|
GinMode: "release",
|
||||||
|
CORSAllowOrigins: "*",
|
||||||
|
AutoMigrate: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if portStr := os.Getenv("PORT"); portStr != "" {
|
||||||
|
if p, err := strconv.Atoi(portStr); err == nil && p > 0 {
|
||||||
|
cfg.Port = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostStr := os.Getenv("HOST"); hostStr != "" {
|
||||||
|
cfg.Host = hostStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbPathStr := os.Getenv("DB_PATH"); dbPathStr != "" {
|
||||||
|
cfg.DBPath = dbPathStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if ginModeStr := os.Getenv("GIN_MODE"); ginModeStr != "" {
|
||||||
|
cfg.GinMode = ginModeStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if corsStr := os.Getenv("CORS_ALLOW_ORIGINS"); corsStr != "" {
|
||||||
|
cfg.CORSAllowOrigins = corsStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoMigrateStr := os.Getenv("AUTO_MIGRATE"); autoMigrateStr != "" {
|
||||||
|
cfg.AutoMigrate = strings.ToLower(autoMigrateStr) != "false" && autoMigrateStr != "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns the combined host:port address string.
|
||||||
|
func (c *Config) Address() string {
|
||||||
|
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigDefaults(t *testing.T) {
|
||||||
|
// Clear env
|
||||||
|
_ = os.Unsetenv("PORT")
|
||||||
|
_ = os.Unsetenv("HOST")
|
||||||
|
_ = os.Unsetenv("DB_PATH")
|
||||||
|
_ = os.Unsetenv("GIN_MODE")
|
||||||
|
_ = os.Unsetenv("CORS_ALLOW_ORIGINS")
|
||||||
|
_ = os.Unsetenv("AUTO_MIGRATE")
|
||||||
|
|
||||||
|
cfg := config.Load()
|
||||||
|
assert.Equal(t, 8080, cfg.Port)
|
||||||
|
assert.Equal(t, "0.0.0.0", cfg.Host)
|
||||||
|
assert.Equal(t, "anl.db", cfg.DBPath)
|
||||||
|
assert.Equal(t, "release", cfg.GinMode)
|
||||||
|
assert.Equal(t, "*", cfg.CORSAllowOrigins)
|
||||||
|
assert.True(t, cfg.AutoMigrate)
|
||||||
|
assert.Equal(t, "0.0.0.0:8080", cfg.Address())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigEnvOverrides(t *testing.T) {
|
||||||
|
_ = os.Setenv("PORT", "9090")
|
||||||
|
_ = os.Setenv("HOST", "127.0.0.1")
|
||||||
|
_ = os.Setenv("DB_PATH", "/tmp/custom.db")
|
||||||
|
_ = os.Setenv("GIN_MODE", "debug")
|
||||||
|
_ = os.Setenv("CORS_ALLOW_ORIGINS", "https://anl.knu.ac.kr")
|
||||||
|
_ = os.Setenv("AUTO_MIGRATE", "false")
|
||||||
|
defer func() {
|
||||||
|
_ = os.Unsetenv("PORT")
|
||||||
|
_ = os.Unsetenv("HOST")
|
||||||
|
_ = os.Unsetenv("DB_PATH")
|
||||||
|
_ = os.Unsetenv("GIN_MODE")
|
||||||
|
_ = os.Unsetenv("CORS_ALLOW_ORIGINS")
|
||||||
|
_ = os.Unsetenv("AUTO_MIGRATE")
|
||||||
|
}()
|
||||||
|
|
||||||
|
cfg := config.Load()
|
||||||
|
assert.Equal(t, 9090, cfg.Port)
|
||||||
|
assert.Equal(t, "127.0.0.1", cfg.Host)
|
||||||
|
assert.Equal(t, "/tmp/custom.db", cfg.DBPath)
|
||||||
|
assert.Equal(t, "debug", cfg.GinMode)
|
||||||
|
assert.Equal(t, "https://anl.knu.ac.kr", cfg.CORSAllowOrigins)
|
||||||
|
assert.False(t, cfg.AutoMigrate)
|
||||||
|
assert.Equal(t, "127.0.0.1:9090", cfg.Address())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadEnvFile(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
envPath := filepath.Join(tmpDir, ".env")
|
||||||
|
envContent := `
|
||||||
|
# Sample comment
|
||||||
|
TEST_CUSTOM_KEY="hello_world"
|
||||||
|
TEST_PORT_KEY=8888
|
||||||
|
INVALID_LINE_WITHOUT_EQUALS
|
||||||
|
`
|
||||||
|
err := os.WriteFile(envPath, []byte(envContent), 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_ = os.Unsetenv("TEST_CUSTOM_KEY")
|
||||||
|
_ = os.Unsetenv("TEST_PORT_KEY")
|
||||||
|
defer func() {
|
||||||
|
_ = os.Unsetenv("TEST_CUSTOM_KEY")
|
||||||
|
_ = os.Unsetenv("TEST_PORT_KEY")
|
||||||
|
}()
|
||||||
|
|
||||||
|
config.LoadEnvFile(envPath)
|
||||||
|
assert.Equal(t, "hello_world", os.Getenv("TEST_CUSTOM_KEY"))
|
||||||
|
assert.Equal(t, "8888", os.Getenv("TEST_PORT_KEY"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed migrations/000001_init.up.sql
|
||||||
|
var initMigrationUp string
|
||||||
|
|
||||||
|
//go:embed migrations/000001_init.down.sql
|
||||||
|
var initMigrationDown string
|
||||||
|
|
||||||
|
// Connect opens an SQLite database connection with appropriate pragmas
|
||||||
|
func Connect(dbPath string) (*sql.DB, error) {
|
||||||
|
dsn := dbPath
|
||||||
|
if !strings.Contains(dsn, "?") {
|
||||||
|
dsn += "?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"
|
||||||
|
}
|
||||||
|
db, err := sql.Open("sqlite", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMigrations applies up migrations
|
||||||
|
func RunMigrations(db *sql.DB) error {
|
||||||
|
if _, err := db.Exec(initMigrationUp); err != nil {
|
||||||
|
return fmt.Errorf("failed to apply migration up: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollbackMigrations applies down migrations
|
||||||
|
func RollbackMigrations(db *sql.DB) error {
|
||||||
|
if _, err := db.Exec(initMigrationDown); err != nil {
|
||||||
|
return fmt.Errorf("failed to apply migration down: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
DROP TABLE IF EXISTS standard_documents;
|
||||||
|
DROP TABLE IF EXISTS standards_bodies;
|
||||||
|
DROP TABLE IF EXISTS courses;
|
||||||
|
DROP TABLE IF EXISTS semesters;
|
||||||
|
DROP TABLE IF EXISTS patents;
|
||||||
|
DROP TABLE IF EXISTS publications;
|
||||||
|
DROP TABLE IF EXISTS alumni;
|
||||||
|
DROP TABLE IF EXISTS members;
|
||||||
|
DROP TABLE IF EXISTS research_areas;
|
||||||
|
DROP TABLE IF EXISTS research_projects;
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS research_projects (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
slug TEXT NOT NULL UNIQUE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
abstract TEXT NOT NULL,
|
||||||
|
keywords TEXT NOT NULL DEFAULT '[]',
|
||||||
|
organization TEXT,
|
||||||
|
standards_org TEXT,
|
||||||
|
period TEXT,
|
||||||
|
funder TEXT,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS research_areas (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name_en TEXT NOT NULL,
|
||||||
|
name_kr TEXT,
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS members (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name_ko TEXT NOT NULL,
|
||||||
|
name_en TEXT NOT NULL,
|
||||||
|
degree TEXT NOT NULL CHECK (degree IN ('Professor','PhD','PhDCandidate','PhDStudent','MSCandidate','MSStudent')),
|
||||||
|
affiliation TEXT,
|
||||||
|
email TEXT,
|
||||||
|
is_advisor BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS alumni (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name_ko TEXT NOT NULL,
|
||||||
|
name_en TEXT NOT NULL,
|
||||||
|
degree TEXT NOT NULL CHECK (degree IN ('PhD','MS')),
|
||||||
|
graduated_at TEXT NOT NULL,
|
||||||
|
major TEXT,
|
||||||
|
current_position TEXT,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS publications (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
category TEXT NOT NULL CHECK (category IN ('intl-journal-conf','domestic-journal-conf')),
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
authors TEXT,
|
||||||
|
venue TEXT NOT NULL,
|
||||||
|
volume TEXT,
|
||||||
|
published_at TEXT NOT NULL,
|
||||||
|
kci BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
doi TEXT,
|
||||||
|
is_highlight BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_publications_category ON publications(category);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_publications_highlight ON publications(is_highlight) WHERE is_highlight = 1;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS patents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
inventors TEXT NOT NULL,
|
||||||
|
application_no TEXT NOT NULL,
|
||||||
|
application_at TEXT NOT NULL,
|
||||||
|
registration_no TEXT,
|
||||||
|
registration_at TEXT,
|
||||||
|
country TEXT NOT NULL,
|
||||||
|
published_at TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS semesters (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
term TEXT NOT NULL CHECK (term IN ('Spring','Fall')),
|
||||||
|
UNIQUE(year, term)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS courses (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
semester_id INTEGER NOT NULL REFERENCES semesters(id) ON DELETE CASCADE,
|
||||||
|
code TEXT NOT NULL,
|
||||||
|
name_en TEXT NOT NULL,
|
||||||
|
name_kr TEXT,
|
||||||
|
note TEXT,
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_courses_semester ON courses(semester_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS standards_bodies (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
org TEXT NOT NULL,
|
||||||
|
full_name TEXT NOT NULL,
|
||||||
|
scope TEXT NOT NULL CHECK (scope IN ('international','domestic')),
|
||||||
|
period TEXT NOT NULL,
|
||||||
|
role TEXT,
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS standard_documents (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
body_id INTEGER NOT NULL REFERENCES standards_bodies(id) ON DELETE CASCADE,
|
||||||
|
wg TEXT NOT NULL,
|
||||||
|
project_name TEXT NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
doc_ref TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL CHECK (status IN ('IS','TS','TR','CDV','WDTR','InProgress')),
|
||||||
|
published_at TEXT
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stddocs_body ON standard_documents(body_id);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/httpx"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/repository"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HomeHandler struct {
|
||||||
|
repo *repository.HomeRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHomeHandler(repo *repository.HomeRepository) *HomeHandler {
|
||||||
|
return &HomeHandler{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HomeHandler) GetResearchProjects(c *gin.Context) {
|
||||||
|
projects, err := h.repo.GetResearchProjects(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve research projects: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, projects)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HomeHandler) GetResearchAreas(c *gin.Context) {
|
||||||
|
areas, err := h.repo.GetResearchAreas(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve research areas: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, areas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HomeHandler) GetStatsSummary(c *gin.Context) {
|
||||||
|
stats, err := h.repo.GetStatsSummary(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve statistics summary: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, stats)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/httpx"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/repository"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LecturesHandler struct {
|
||||||
|
repo *repository.LecturesRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLecturesHandler(repo *repository.LecturesRepository) *LecturesHandler {
|
||||||
|
return &LecturesHandler{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LecturesHandler) GetSemesters(c *gin.Context) {
|
||||||
|
semesters, err := h.repo.GetSemestersWithCourses(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve semesters: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, semesters)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/httpx"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/repository"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MembersHandler struct {
|
||||||
|
repo *repository.MembersRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMembersHandler(repo *repository.MembersRepository) *MembersHandler {
|
||||||
|
return &MembersHandler{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MembersHandler) GetMembers(c *gin.Context) {
|
||||||
|
members, err := h.repo.GetMembers(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve members: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, members)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MembersHandler) GetAlumni(c *gin.Context) {
|
||||||
|
alumni, err := h.repo.GetAlumni(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve alumni: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, alumni)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/httpx"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/repository"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PublicationsHandler struct {
|
||||||
|
repo *repository.PublicationsRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPublicationsHandler(repo *repository.PublicationsRepository) *PublicationsHandler {
|
||||||
|
return &PublicationsHandler{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PublicationsHandler) GetPublications(c *gin.Context) {
|
||||||
|
category := c.Query("category")
|
||||||
|
if category != "" && category != "intl-journal-conf" && category != "domestic-journal-conf" {
|
||||||
|
httpx.BadRequest(c, "Invalid category parameter: must be 'intl-journal-conf' or 'domestic-journal-conf'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pubs, err := h.repo.GetPublications(c.Request.Context(), category)
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve publications: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, pubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PublicationsHandler) GetHighlights(c *gin.Context) {
|
||||||
|
highlights, err := h.repo.GetHighlights(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve highlights: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, highlights)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PublicationsHandler) GetPatents(c *gin.Context) {
|
||||||
|
patents, err := h.repo.GetPatents(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve patents: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, patents)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/httpx"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/repository"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StandardizationHandler struct {
|
||||||
|
repo *repository.StandardizationRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStandardizationHandler(repo *repository.StandardizationRepository) *StandardizationHandler {
|
||||||
|
return &StandardizationHandler{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StandardizationHandler) GetStandardsBodies(c *gin.Context) {
|
||||||
|
bodies, err := h.repo.GetStandardsBodiesWithProjects(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
httpx.InternalServerError(c, "Failed to retrieve standards bodies: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.OK(c, bodies)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorDetail struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error ErrorDetail `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SuccessResponse struct {
|
||||||
|
Data any `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func OK(c *gin.Context, data any) {
|
||||||
|
c.JSON(http.StatusOK, SuccessResponse{
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(c *gin.Context, status int, msg string) {
|
||||||
|
c.JSON(status, ErrorResponse{
|
||||||
|
Error: ErrorDetail{
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BadRequest(c *gin.Context, msg string) {
|
||||||
|
Error(c, http.StatusBadRequest, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotFound(c *gin.Context, msg string) {
|
||||||
|
Error(c, http.StatusNotFound, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InternalServerError(c *gin.Context, msg string) {
|
||||||
|
Error(c, http.StatusInternalServerError, msg)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ResearchProject struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Abstract string `json:"abstract"`
|
||||||
|
Keywords []string `json:"keywords"`
|
||||||
|
Organization *string `json:"organization,omitempty"`
|
||||||
|
StandardsOrg *string `json:"standards_org,omitempty"`
|
||||||
|
Period *string `json:"period,omitempty"`
|
||||||
|
Funder *string `json:"funder,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResearchArea struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
NameEn string `json:"name_en"`
|
||||||
|
NameKr *string `json:"name_kr,omitempty"`
|
||||||
|
DisplayOrder int `json:"display_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatsSummary struct {
|
||||||
|
IntlPublications int `json:"intl_publications"`
|
||||||
|
StandardizationDocs int `json:"standardization_docs"`
|
||||||
|
Patents int `json:"patents"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Course struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
SemesterID int `json:"semester_id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
NameEn string `json:"name_en"`
|
||||||
|
NameKr *string `json:"name_kr,omitempty"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
|
DisplayOrder int `json:"display_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Semester struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Term string `json:"term"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SemesterWithCourses struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Term string `json:"term"`
|
||||||
|
Courses []Course `json:"courses"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Member struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
NameKo string `json:"name_ko"`
|
||||||
|
NameEn string `json:"name_en"`
|
||||||
|
Degree string `json:"degree"`
|
||||||
|
Affiliation *string `json:"affiliation,omitempty"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
IsAdvisor bool `json:"is_advisor"`
|
||||||
|
DisplayOrder int `json:"display_order"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Alumnus struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
NameKo string `json:"name_ko"`
|
||||||
|
NameEn string `json:"name_en"`
|
||||||
|
Degree string `json:"degree"`
|
||||||
|
GraduatedAt string `json:"graduated_at"`
|
||||||
|
Major *string `json:"major,omitempty"`
|
||||||
|
CurrentPosition *string `json:"current_position,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Publication struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
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:"published_at"`
|
||||||
|
KCI bool `json:"kci"`
|
||||||
|
DOI *string `json:"doi,omitempty"`
|
||||||
|
IsHighlight bool `json:"is_highlight"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Patent struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Inventors string `json:"inventors"`
|
||||||
|
ApplicationNo string `json:"application_no"`
|
||||||
|
ApplicationAt string `json:"application_at"`
|
||||||
|
RegistrationNo *string `json:"registration_no,omitempty"`
|
||||||
|
RegistrationAt *string `json:"registration_at,omitempty"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
PublishedAt string `json:"published_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type StandardDocument struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
BodyID int `json:"body_id"`
|
||||||
|
WG string `json:"wg"`
|
||||||
|
ProjectName string `json:"project_name"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
DocRef string `json:"doc_ref"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
PublishedAt *string `json:"published_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StandardProjectGroup struct {
|
||||||
|
WG string `json:"wg"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Documents []StandardDocument `json:"documents"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StandardsBody struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Org string `json:"org"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Period string `json:"period"`
|
||||||
|
Role *string `json:"role,omitempty"`
|
||||||
|
DisplayOrder int `json:"display_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StandardsBodyWithProjects struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Org string `json:"org"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Period string `json:"period"`
|
||||||
|
Role *string `json:"role,omitempty"`
|
||||||
|
DisplayOrder int `json:"display_order"`
|
||||||
|
Projects []StandardProjectGroup `json:"projects"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HomeRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHomeRepository(db *sql.DB) *HomeRepository {
|
||||||
|
return &HomeRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HomeRepository) GetResearchProjects(ctx context.Context) ([]models.ResearchProject, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, slug, title, abstract, keywords, organization, standards_org, period, funder, created_at, updated_at
|
||||||
|
FROM research_projects
|
||||||
|
ORDER BY id ASC
|
||||||
|
`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query research projects: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var projects []models.ResearchProject
|
||||||
|
for rows.Next() {
|
||||||
|
var p models.ResearchProject
|
||||||
|
var kwJSON string
|
||||||
|
err := rows.Scan(
|
||||||
|
&p.ID, &p.Slug, &p.Title, &p.Abstract, &kwJSON,
|
||||||
|
&p.Organization, &p.StandardsOrg, &p.Period, &p.Funder,
|
||||||
|
&p.CreatedAt, &p.UpdatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("scan research project: %w", err)
|
||||||
|
}
|
||||||
|
if kwJSON != "" {
|
||||||
|
_ = json.Unmarshal([]byte(kwJSON), &p.Keywords)
|
||||||
|
}
|
||||||
|
if p.Keywords == nil {
|
||||||
|
p.Keywords = []string{}
|
||||||
|
}
|
||||||
|
projects = append(projects, p)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return projects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HomeRepository) GetResearchAreas(ctx context.Context) ([]models.ResearchArea, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, name_en, name_kr, display_order
|
||||||
|
FROM research_areas
|
||||||
|
ORDER BY display_order ASC, id ASC
|
||||||
|
`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query research areas: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var areas []models.ResearchArea
|
||||||
|
for rows.Next() {
|
||||||
|
var a models.ResearchArea
|
||||||
|
if err := rows.Scan(&a.ID, &a.NameEn, &a.NameKr, &a.DisplayOrder); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan research area: %w", err)
|
||||||
|
}
|
||||||
|
areas = append(areas, a)
|
||||||
|
}
|
||||||
|
return areas, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HomeRepository) GetStatsSummary(ctx context.Context) (*models.StatsSummary, error) {
|
||||||
|
var summary models.StatsSummary
|
||||||
|
|
||||||
|
err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM publications WHERE category = 'intl-journal-conf'").Scan(&summary.IntlPublications)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("count intl publications: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM standard_documents").Scan(&summary.StandardizationDocs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("count standard documents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM patents").Scan(&summary.Patents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("count patents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &summary, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LecturesRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLecturesRepository(db *sql.DB) *LecturesRepository {
|
||||||
|
return &LecturesRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LecturesRepository) GetSemestersWithCourses(ctx context.Context) ([]models.SemesterWithCourses, error) {
|
||||||
|
// Query semesters ordered by year DESC and Fall before Spring within same year
|
||||||
|
semQuery := `
|
||||||
|
SELECT id, year, term
|
||||||
|
FROM semesters
|
||||||
|
ORDER BY year DESC, CASE term WHEN 'Fall' THEN 1 WHEN 'Spring' THEN 2 ELSE 3 END, id DESC
|
||||||
|
`
|
||||||
|
semRows, err := r.db.QueryContext(ctx, semQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query semesters: %w", err)
|
||||||
|
}
|
||||||
|
defer semRows.Close()
|
||||||
|
|
||||||
|
var semesters []models.SemesterWithCourses
|
||||||
|
semMap := make(map[int]int) // semester_id -> index in slice
|
||||||
|
|
||||||
|
for semRows.Next() {
|
||||||
|
var s models.SemesterWithCourses
|
||||||
|
if err := semRows.Scan(&s.ID, &s.Year, &s.Term); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan semester: %w", err)
|
||||||
|
}
|
||||||
|
s.Courses = []models.Course{}
|
||||||
|
semMap[s.ID] = len(semesters)
|
||||||
|
semesters = append(semesters, s)
|
||||||
|
}
|
||||||
|
if err := semRows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query courses ordered by display_order ASC, id ASC
|
||||||
|
courseQuery := `
|
||||||
|
SELECT id, semester_id, code, name_en, name_kr, note, display_order
|
||||||
|
FROM courses
|
||||||
|
ORDER BY semester_id ASC, display_order ASC, id ASC
|
||||||
|
`
|
||||||
|
courseRows, err := r.db.QueryContext(ctx, courseQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query courses: %w", err)
|
||||||
|
}
|
||||||
|
defer courseRows.Close()
|
||||||
|
|
||||||
|
for courseRows.Next() {
|
||||||
|
var c models.Course
|
||||||
|
if err := courseRows.Scan(&c.ID, &c.SemesterID, &c.Code, &c.NameEn, &c.NameKr, &c.Note, &c.DisplayOrder); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan course: %w", err)
|
||||||
|
}
|
||||||
|
if idx, exists := semMap[c.SemesterID]; exists {
|
||||||
|
semesters[idx].Courses = append(semesters[idx].Courses, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return semesters, courseRows.Err()
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MembersRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMembersRepository(db *sql.DB) *MembersRepository {
|
||||||
|
return &MembersRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MembersRepository) GetMembers(ctx context.Context) ([]models.Member, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, name_ko, name_en, degree, affiliation, email, is_advisor, display_order, created_at
|
||||||
|
FROM members
|
||||||
|
ORDER BY display_order ASC, id ASC
|
||||||
|
`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query members: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var members []models.Member
|
||||||
|
for rows.Next() {
|
||||||
|
var m models.Member
|
||||||
|
if err := rows.Scan(
|
||||||
|
&m.ID, &m.NameKo, &m.NameEn, &m.Degree,
|
||||||
|
&m.Affiliation, &m.Email, &m.IsAdvisor, &m.DisplayOrder, &m.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan member: %w", err)
|
||||||
|
}
|
||||||
|
members = append(members, m)
|
||||||
|
}
|
||||||
|
return members, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MembersRepository) GetAlumni(ctx context.Context) ([]models.Alumnus, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, name_ko, name_en, degree, graduated_at, major, current_position, created_at
|
||||||
|
FROM alumni
|
||||||
|
ORDER BY graduated_at DESC, id ASC
|
||||||
|
`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query alumni: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var alumni []models.Alumnus
|
||||||
|
for rows.Next() {
|
||||||
|
var a models.Alumnus
|
||||||
|
if err := rows.Scan(
|
||||||
|
&a.ID, &a.NameKo, &a.NameEn, &a.Degree,
|
||||||
|
&a.GraduatedAt, &a.Major, &a.CurrentPosition, &a.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan alumnus: %w", err)
|
||||||
|
}
|
||||||
|
alumni = append(alumni, a)
|
||||||
|
}
|
||||||
|
return alumni, rows.Err()
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PublicationsRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPublicationsRepository(db *sql.DB) *PublicationsRepository {
|
||||||
|
return &PublicationsRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PublicationsRepository) GetPublications(ctx context.Context, category string) ([]models.Publication, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, category, title, authors, venue, volume, published_at, kci, doi, is_highlight, created_at
|
||||||
|
FROM publications
|
||||||
|
`
|
||||||
|
var args []any
|
||||||
|
if category != "" {
|
||||||
|
query += " WHERE category = ?"
|
||||||
|
args = append(args, category)
|
||||||
|
}
|
||||||
|
query += " ORDER BY published_at DESC, id ASC"
|
||||||
|
|
||||||
|
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query publications: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var pubs []models.Publication
|
||||||
|
for rows.Next() {
|
||||||
|
var p models.Publication
|
||||||
|
if err := rows.Scan(
|
||||||
|
&p.ID, &p.Category, &p.Title, &p.Authors,
|
||||||
|
&p.Venue, &p.Volume, &p.PublishedAt, &p.KCI, &p.DOI, &p.IsHighlight, &p.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan publication: %w", err)
|
||||||
|
}
|
||||||
|
pubs = append(pubs, p)
|
||||||
|
}
|
||||||
|
return pubs, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PublicationsRepository) GetHighlights(ctx context.Context) ([]models.Publication, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, category, title, authors, venue, volume, published_at, kci, doi, is_highlight, created_at
|
||||||
|
FROM publications
|
||||||
|
WHERE is_highlight = 1
|
||||||
|
ORDER BY published_at DESC, id ASC
|
||||||
|
`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query highlights: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var pubs []models.Publication
|
||||||
|
for rows.Next() {
|
||||||
|
var p models.Publication
|
||||||
|
if err := rows.Scan(
|
||||||
|
&p.ID, &p.Category, &p.Title, &p.Authors,
|
||||||
|
&p.Venue, &p.Volume, &p.PublishedAt, &p.KCI, &p.DOI, &p.IsHighlight, &p.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan highlight: %w", err)
|
||||||
|
}
|
||||||
|
pubs = append(pubs, p)
|
||||||
|
}
|
||||||
|
return pubs, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PublicationsRepository) GetPatents(ctx context.Context) ([]models.Patent, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, title, inventors, application_no, application_at, registration_no, registration_at, country, published_at, created_at
|
||||||
|
FROM patents
|
||||||
|
ORDER BY published_at DESC, id ASC
|
||||||
|
`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query patents: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var patents []models.Patent
|
||||||
|
for rows.Next() {
|
||||||
|
var p models.Patent
|
||||||
|
if err := rows.Scan(
|
||||||
|
&p.ID, &p.Title, &p.Inventors, &p.ApplicationNo,
|
||||||
|
&p.ApplicationAt, &p.RegistrationNo, &p.RegistrationAt, &p.Country, &p.PublishedAt, &p.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan patent: %w", err)
|
||||||
|
}
|
||||||
|
patents = append(patents, p)
|
||||||
|
}
|
||||||
|
return patents, rows.Err()
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StandardizationRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStandardizationRepository(db *sql.DB) *StandardizationRepository {
|
||||||
|
return &StandardizationRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardizationRepository) GetStandardsBodiesWithProjects(ctx context.Context) ([]models.StandardsBodyWithProjects, error) {
|
||||||
|
// Query bodies ordered by display_order
|
||||||
|
bodyQuery := `
|
||||||
|
SELECT id, org, full_name, scope, period, role, display_order
|
||||||
|
FROM standards_bodies
|
||||||
|
ORDER BY display_order ASC, id ASC
|
||||||
|
`
|
||||||
|
bodyRows, err := r.db.QueryContext(ctx, bodyQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query standards bodies: %w", err)
|
||||||
|
}
|
||||||
|
defer bodyRows.Close()
|
||||||
|
|
||||||
|
var bodies []models.StandardsBodyWithProjects
|
||||||
|
bodyMap := make(map[int]int) // body_id -> index in bodies
|
||||||
|
|
||||||
|
for bodyRows.Next() {
|
||||||
|
var b models.StandardsBodyWithProjects
|
||||||
|
if err := bodyRows.Scan(&b.ID, &b.Org, &b.FullName, &b.Scope, &b.Period, &b.Role, &b.DisplayOrder); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan standards body: %w", err)
|
||||||
|
}
|
||||||
|
b.Projects = []models.StandardProjectGroup{}
|
||||||
|
bodyMap[b.ID] = len(bodies)
|
||||||
|
bodies = append(bodies, b)
|
||||||
|
}
|
||||||
|
if err := bodyRows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query documents ordered by body_id, and preserves curated insertion order with MIN(id) ASC
|
||||||
|
docQuery := `
|
||||||
|
SELECT id, body_id, wg, project_name, title, doc_ref, status, published_at
|
||||||
|
FROM standard_documents
|
||||||
|
ORDER BY body_id ASC, id ASC
|
||||||
|
`
|
||||||
|
docRows, err := r.db.QueryContext(ctx, docQuery)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query standard documents: %w", err)
|
||||||
|
}
|
||||||
|
defer docRows.Close()
|
||||||
|
|
||||||
|
// Group documents into projects under each body preserving curated order
|
||||||
|
type projectKey struct {
|
||||||
|
bodyID int
|
||||||
|
wg string
|
||||||
|
projectName string
|
||||||
|
}
|
||||||
|
projectMap := make(map[projectKey]int) // projectKey -> index in bodies[bIdx].Projects
|
||||||
|
|
||||||
|
for docRows.Next() {
|
||||||
|
var d models.StandardDocument
|
||||||
|
if err := docRows.Scan(&d.ID, &d.BodyID, &d.WG, &d.ProjectName, &d.Title, &d.DocRef, &d.Status, &d.PublishedAt); err != nil {
|
||||||
|
return nil, fmt.Errorf("scan standard document: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bIdx, bodyExists := bodyMap[d.BodyID]
|
||||||
|
if !bodyExists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := projectKey{bodyID: d.BodyID, wg: d.WG, projectName: d.ProjectName}
|
||||||
|
pIdx, projExists := projectMap[key]
|
||||||
|
if !projExists {
|
||||||
|
pIdx = len(bodies[bIdx].Projects)
|
||||||
|
bodies[bIdx].Projects = append(bodies[bIdx].Projects, models.StandardProjectGroup{
|
||||||
|
WG: d.WG,
|
||||||
|
Name: d.ProjectName,
|
||||||
|
Documents: []models.StandardDocument{},
|
||||||
|
})
|
||||||
|
projectMap[key] = pIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
bodies[bIdx].Projects[pIdx].Documents = append(bodies[bIdx].Projects[pIdx].Documents, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bodies, docRows.Err()
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/handlers"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/httpx"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/repository"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupRouter(db *sql.DB, corsOrigins ...string) *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
allowOrigin := "*"
|
||||||
|
if len(corsOrigins) > 0 && corsOrigins[0] != "" {
|
||||||
|
allowOrigin = corsOrigins[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS Middleware
|
||||||
|
r.Use(func(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin)
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Repositories
|
||||||
|
homeRepo := repository.NewHomeRepository(db)
|
||||||
|
membersRepo := repository.NewMembersRepository(db)
|
||||||
|
pubsRepo := repository.NewPublicationsRepository(db)
|
||||||
|
lecturesRepo := repository.NewLecturesRepository(db)
|
||||||
|
standardsRepo := repository.NewStandardizationRepository(db)
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
homeHandler := handlers.NewHomeHandler(homeRepo)
|
||||||
|
membersHandler := handlers.NewMembersHandler(membersRepo)
|
||||||
|
pubsHandler := handlers.NewPublicationsHandler(pubsRepo)
|
||||||
|
lecturesHandler := handlers.NewLecturesHandler(lecturesRepo)
|
||||||
|
standardsHandler := handlers.NewStandardizationHandler(standardsRepo)
|
||||||
|
|
||||||
|
// API v1 Group
|
||||||
|
v1 := r.Group("/api/v1")
|
||||||
|
{
|
||||||
|
v1.GET("/health", func(c *gin.Context) {
|
||||||
|
httpx.OK(c, gin.H{"status": "ok", "service": "anl-backend-api"})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Home
|
||||||
|
v1.GET("/research-projects", homeHandler.GetResearchProjects)
|
||||||
|
v1.GET("/research-areas", homeHandler.GetResearchAreas)
|
||||||
|
v1.GET("/stats/summary", homeHandler.GetStatsSummary)
|
||||||
|
|
||||||
|
// Members & Alumni
|
||||||
|
v1.GET("/members", membersHandler.GetMembers)
|
||||||
|
v1.GET("/alumni", membersHandler.GetAlumni)
|
||||||
|
|
||||||
|
// Publications & Patents
|
||||||
|
v1.GET("/publications", pubsHandler.GetPublications)
|
||||||
|
v1.GET("/publications/highlights", pubsHandler.GetHighlights)
|
||||||
|
v1.GET("/patents", pubsHandler.GetPatents)
|
||||||
|
|
||||||
|
// Lectures
|
||||||
|
v1.GET("/semesters", lecturesHandler.GetSemesters)
|
||||||
|
|
||||||
|
// Standardization
|
||||||
|
v1.GET("/standards-bodies", standardsHandler.GetStandardsBodies)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
package router_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/db"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/models"
|
||||||
|
"git.godopu.com/lab/landing_page/backend/internal/router"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestDB(t *testing.T) *sql.DB {
|
||||||
|
// In-memory or temporary file sqlite DB
|
||||||
|
tmpDB := filepath.Join(t.TempDir(), "test.db")
|
||||||
|
database, err := db.Connect(tmpDB)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = db.RunMigrations(database)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Seed test data from ../../seed/data
|
||||||
|
seedDir := filepath.Join("..", "..", "seed", "data")
|
||||||
|
seedDatabase(t, database, seedDir)
|
||||||
|
|
||||||
|
return database
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedDatabase(t *testing.T, database *sql.DB, seedDir string) {
|
||||||
|
tx, err := database.BeginTx(context.Background(), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// 1. Projects
|
||||||
|
pData, err := os.ReadFile(filepath.Join(seedDir, "research_projects.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var projects []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"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(pData, &projects))
|
||||||
|
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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Areas
|
||||||
|
aData, err := os.ReadFile(filepath.Join(seedDir, "research_areas.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var areas []models.ResearchArea
|
||||||
|
require.NoError(t, json.Unmarshal(aData, &areas))
|
||||||
|
for _, a := range areas {
|
||||||
|
_, err := tx.Exec(`INSERT INTO research_areas (name_en, name_kr, display_order) VALUES (?, ?, ?)`, a.NameEn, a.NameKr, a.DisplayOrder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Members
|
||||||
|
mData, err := os.ReadFile(filepath.Join(seedDir, "members.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var members []models.Member
|
||||||
|
require.NoError(t, json.Unmarshal(mData, &members))
|
||||||
|
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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Alumni
|
||||||
|
alumniData, err := os.ReadFile(filepath.Join(seedDir, "alumni.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var alumni []models.Alumnus
|
||||||
|
require.NoError(t, json.Unmarshal(alumniData, &alumni))
|
||||||
|
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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Publications
|
||||||
|
pubData, err := os.ReadFile(filepath.Join(seedDir, "publications.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var pubs []models.Publication
|
||||||
|
require.NoError(t, json.Unmarshal(pubData, &pubs))
|
||||||
|
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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Patents
|
||||||
|
patData, err := os.ReadFile(filepath.Join(seedDir, "patents.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var patents []models.Patent
|
||||||
|
require.NoError(t, json.Unmarshal(patData, &patents))
|
||||||
|
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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Semesters & Courses
|
||||||
|
lecData, err := os.ReadFile(filepath.Join(seedDir, "lectures.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var semesters []struct {
|
||||||
|
Term string `json:"term"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Courses []struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
En string `json:"en"`
|
||||||
|
Note *string `json:"note"`
|
||||||
|
} `json:"courses"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(lecData, &semesters))
|
||||||
|
for _, s := range semesters {
|
||||||
|
res, err := tx.Exec(`INSERT INTO semesters (year, term) VALUES (?, ?)`, s.Year, s.Term)
|
||||||
|
require.NoError(t, 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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Standards
|
||||||
|
stdData, err := os.ReadFile(filepath.Join(seedDir, "standards.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var standards []struct {
|
||||||
|
Org string `json:"org"`
|
||||||
|
Full string `json:"full"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Period string `json:"period"`
|
||||||
|
Role *string `json:"role"`
|
||||||
|
Projects []struct {
|
||||||
|
WG string `json:"wg"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Documents []struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
PublishedAt *string `json:"publishedAt"`
|
||||||
|
} `json:"documents"`
|
||||||
|
} `json:"projects"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(stdData, &standards))
|
||||||
|
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)
|
||||||
|
require.NoError(t, 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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, tx.Commit())
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIResponse[T any] struct {
|
||||||
|
Data T `json:"data"`
|
||||||
|
Error *struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeRequest(r http.Handler, method, target string) *httptest.ResponseRecorder {
|
||||||
|
req := httptest.NewRequest(method, target, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthEndpoint(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
w := executeRequest(r, "GET", "/api/v1/health")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var resp APIResponse[map[string]any]
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "ok", resp.Data["status"])
|
||||||
|
assert.Equal(t, "anl-backend-api", resp.Data["service"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResearchProjects(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
w := executeRequest(r, "GET", "/api/v1/research-projects")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var resp APIResponse[[]models.ResearchProject]
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
assert.Len(t, resp.Data, 3)
|
||||||
|
assert.Equal(t, "iot-standards", resp.Data[0].Slug)
|
||||||
|
assert.Equal(t, "AIoT & AI-RAN", resp.Data[0].Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResearchAreas(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
w := executeRequest(r, "GET", "/api/v1/research-areas")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var resp APIResponse[[]models.ResearchArea]
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
assert.Len(t, resp.Data, 14)
|
||||||
|
assert.Equal(t, 1, resp.Data[0].DisplayOrder)
|
||||||
|
assert.Equal(t, "Quick UDP Internet Connection (QUIC)", resp.Data[0].NameEn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatsSummary(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
w := executeRequest(r, "GET", "/api/v1/stats/summary")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var resp APIResponse[models.StatsSummary]
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
assert.Equal(t, 68, resp.Data.IntlPublications)
|
||||||
|
assert.Equal(t, 44, resp.Data.StandardizationDocs)
|
||||||
|
assert.Equal(t, 75, resp.Data.Patents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMembersAndAlumni(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
|
||||||
|
// Members
|
||||||
|
wMem := executeRequest(r, "GET", "/api/v1/members")
|
||||||
|
assert.Equal(t, http.StatusOK, wMem.Code)
|
||||||
|
var respMem APIResponse[[]models.Member]
|
||||||
|
require.NoError(t, json.Unmarshal(wMem.Body.Bytes(), &respMem))
|
||||||
|
assert.Len(t, respMem.Data, 16)
|
||||||
|
assert.True(t, respMem.Data[0].IsAdvisor)
|
||||||
|
assert.Equal(t, "Seok-Joo Koh", respMem.Data[0].NameEn)
|
||||||
|
|
||||||
|
// Alumni
|
||||||
|
wAlumni := executeRequest(r, "GET", "/api/v1/alumni")
|
||||||
|
assert.Equal(t, http.StatusOK, wAlumni.Code)
|
||||||
|
var respAlumni APIResponse[[]models.Alumnus]
|
||||||
|
require.NoError(t, json.Unmarshal(wAlumni.Body.Bytes(), &respAlumni))
|
||||||
|
assert.Len(t, respAlumni.Data, 60)
|
||||||
|
assert.GreaterOrEqual(t, respAlumni.Data[0].GraduatedAt, respAlumni.Data[1].GraduatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicationsAndHighlights(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
|
||||||
|
// Full Publications
|
||||||
|
wAll := executeRequest(r, "GET", "/api/v1/publications")
|
||||||
|
assert.Equal(t, http.StatusOK, wAll.Code)
|
||||||
|
var respAll APIResponse[[]models.Publication]
|
||||||
|
require.NoError(t, json.Unmarshal(wAll.Body.Bytes(), &respAll))
|
||||||
|
assert.Len(t, respAll.Data, 148)
|
||||||
|
|
||||||
|
// Category filter: intl
|
||||||
|
wIntl := executeRequest(r, "GET", "/api/v1/publications?category=intl-journal-conf")
|
||||||
|
assert.Equal(t, http.StatusOK, wIntl.Code)
|
||||||
|
var respIntl APIResponse[[]models.Publication]
|
||||||
|
require.NoError(t, json.Unmarshal(wIntl.Body.Bytes(), &respIntl))
|
||||||
|
assert.Len(t, respIntl.Data, 68)
|
||||||
|
|
||||||
|
// Category filter: domestic
|
||||||
|
wDom := executeRequest(r, "GET", "/api/v1/publications?category=domestic-journal-conf")
|
||||||
|
assert.Equal(t, http.StatusOK, wDom.Code)
|
||||||
|
var respDom APIResponse[[]models.Publication]
|
||||||
|
require.NoError(t, json.Unmarshal(wDom.Body.Bytes(), &respDom))
|
||||||
|
assert.Len(t, respDom.Data, 80)
|
||||||
|
|
||||||
|
// Invalid category
|
||||||
|
wInvalid := executeRequest(r, "GET", "/api/v1/publications?category=unknown")
|
||||||
|
assert.Equal(t, http.StatusBadRequest, wInvalid.Code)
|
||||||
|
|
||||||
|
// Highlights (asserts exactly the 5 known representative titles)
|
||||||
|
wHL := executeRequest(r, "GET", "/api/v1/publications/highlights")
|
||||||
|
assert.Equal(t, http.StatusOK, wHL.Code)
|
||||||
|
var respHL APIResponse[[]models.Publication]
|
||||||
|
require.NoError(t, json.Unmarshal(wHL.Body.Bytes(), &respHL))
|
||||||
|
assert.Len(t, respHL.Data, 5)
|
||||||
|
|
||||||
|
expectedTitles := map[string]bool{
|
||||||
|
"Globally Integrated Trust Authority (GITA) for Resource-Constrained Edge Devices in IoT and 6G": true,
|
||||||
|
"Application Level Trust Authority (APPLETA) for Resource-Constrained Edge Devices in IoT and 6G": true,
|
||||||
|
"mQUIC: Use of QUIC for Handover Support with Connection Migration in Wireless/Mobile Networks": true,
|
||||||
|
"Use of QUIC for CoAP Transport in IoT Networks": true,
|
||||||
|
"Use of QUIC for Mobile-Oriented Future Internet (Q-MOFI)": true,
|
||||||
|
}
|
||||||
|
for _, hl := range respHL.Data {
|
||||||
|
assert.True(t, hl.IsHighlight)
|
||||||
|
assert.True(t, expectedTitles[hl.Title], "Unexpected highlight title: %s", hl.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patents
|
||||||
|
wPat := executeRequest(r, "GET", "/api/v1/patents")
|
||||||
|
assert.Equal(t, http.StatusOK, wPat.Code)
|
||||||
|
var respPat APIResponse[[]models.Patent]
|
||||||
|
require.NoError(t, json.Unmarshal(wPat.Body.Bytes(), &respPat))
|
||||||
|
assert.Len(t, respPat.Data, 75)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSemestersAndCourses(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
|
||||||
|
w := executeRequest(r, "GET", "/api/v1/semesters")
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var resp APIResponse[[]models.SemesterWithCourses]
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
assert.Len(t, resp.Data, 45)
|
||||||
|
|
||||||
|
// Check semester ordering (Year DESC, Fall before Spring within same year)
|
||||||
|
first := resp.Data[0]
|
||||||
|
assert.Equal(t, 2026, first.Year)
|
||||||
|
assert.Equal(t, "Spring", first.Term)
|
||||||
|
assert.NotEmpty(t, first.Courses)
|
||||||
|
|
||||||
|
// In 2025: Fall must come before Spring
|
||||||
|
var s2025Fall, s2025Spring int
|
||||||
|
for idx, s := range resp.Data {
|
||||||
|
if s.Year == 2025 && s.Term == "Fall" {
|
||||||
|
s2025Fall = idx
|
||||||
|
}
|
||||||
|
if s.Year == 2025 && s.Term == "Spring" {
|
||||||
|
s2025Spring = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Less(t, s2025Fall, s2025Spring, "2025 Fall should precede 2025 Spring in chronological descending order")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStandardsBodiesAndHierarchy(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
|
||||||
|
w := executeRequest(r, "GET", "/api/v1/standards-bodies")
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var resp APIResponse[[]models.StandardsBodyWithProjects]
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
assert.Len(t, resp.Data, 6)
|
||||||
|
|
||||||
|
// First body: IEC TC100
|
||||||
|
iec := resp.Data[0]
|
||||||
|
assert.Equal(t, "IEC TC100", iec.Org)
|
||||||
|
require.NotEmpty(t, iec.Projects)
|
||||||
|
|
||||||
|
// Verify project ordering within IEC TC100 (deterministic order: WG12 before WG11(Convenor))
|
||||||
|
assert.Equal(t, "WG12", iec.Projects[0].WG)
|
||||||
|
assert.Equal(t, "WG11(Convenor)", iec.Projects[1].WG)
|
||||||
|
assert.NotEmpty(t, iec.Projects[0].Documents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONKeyCasingSnakeCase(t *testing.T) {
|
||||||
|
database := setupTestDB(t)
|
||||||
|
defer database.Close()
|
||||||
|
|
||||||
|
r := router.SetupRouter(database)
|
||||||
|
|
||||||
|
// Test Members endpoint raw JSON map
|
||||||
|
wMem := executeRequest(r, "GET", "/api/v1/members")
|
||||||
|
assert.Equal(t, http.StatusOK, wMem.Code)
|
||||||
|
var rawMem map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(wMem.Body.Bytes(), &rawMem))
|
||||||
|
dataList, ok := rawMem["data"].([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotEmpty(t, dataList)
|
||||||
|
firstMember, ok := dataList[0].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
// Verify snake_case keys exist and camelCase keys do not
|
||||||
|
assert.Contains(t, firstMember, "name_ko")
|
||||||
|
assert.Contains(t, firstMember, "name_en")
|
||||||
|
assert.Contains(t, firstMember, "is_advisor")
|
||||||
|
assert.Contains(t, firstMember, "display_order")
|
||||||
|
assert.NotContains(t, firstMember, "nameKo")
|
||||||
|
assert.NotContains(t, firstMember, "isAdvisor")
|
||||||
|
|
||||||
|
// Test Highlights endpoint raw JSON map
|
||||||
|
wHL := executeRequest(r, "GET", "/api/v1/publications/highlights")
|
||||||
|
assert.Equal(t, http.StatusOK, wHL.Code)
|
||||||
|
var rawHL map[string]any
|
||||||
|
require.NoError(t, json.Unmarshal(wHL.Body.Bytes(), &rawHL))
|
||||||
|
hlList, ok := rawHL["data"].([]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.NotEmpty(t, hlList)
|
||||||
|
firstHL, ok := hlList[0].(map[string]any)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.Contains(t, firstHL, "is_highlight")
|
||||||
|
assert.Contains(t, firstHL, "published_at")
|
||||||
|
assert.NotContains(t, firstHL, "isHighlight")
|
||||||
|
assert.NotContains(t, firstHL, "publishedAt")
|
||||||
|
}
|
||||||
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()
|
||||||
@@ -0,0 +1,482 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name_ko": "김동주",
|
||||||
|
"name_en": "Dongju Kim",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2026-08",
|
||||||
|
"major": "Computer Science and Engineering",
|
||||||
|
"current_position": "He is now with Daegu Catholic University (대구가톨릭대학교) as Professor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "황정미",
|
||||||
|
"name_en": "Jung-Mi Hwang",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2026-08",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "She is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "배드로",
|
||||||
|
"name_en": "Dro Bae",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2026-02",
|
||||||
|
"major": "Industrial Engineering",
|
||||||
|
"current_position": "He is now with SK AX"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "권세기",
|
||||||
|
"name_en": "Se-Gi Kwon",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2026-02",
|
||||||
|
"major": "Information Security",
|
||||||
|
"current_position": "He is now with J Solution (제이솔루션) as CEO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "조세현",
|
||||||
|
"name_en": "Se-Hyun Cho",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2025-08",
|
||||||
|
"major": "Information Security",
|
||||||
|
"current_position": "He is now with Cyber Security Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김윤성",
|
||||||
|
"name_en": "Yun-Seong Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2025-08",
|
||||||
|
"major": "Data Convergence Computing",
|
||||||
|
"current_position": "He is now with SL (에스엘)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김수진",
|
||||||
|
"name_en": "Su-Jin Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2025-08",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김상태",
|
||||||
|
"name_en": "Sang-Tae Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2025-08",
|
||||||
|
"major": "Industrial Engineering",
|
||||||
|
"current_position": "He is now with Oh-Sung Electronis (오성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김규범",
|
||||||
|
"name_en": "Gyu-Bum Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2025-08",
|
||||||
|
"major": "Computer Science and Engineering",
|
||||||
|
"current_position": "He is now with Korea Real Estate Board (한국부동산원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "정중화",
|
||||||
|
"name_en": "Joong-Hwa Jung",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2025-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Lychee AI Coding Academy (리치AI코딩학원) as CEO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "임도현",
|
||||||
|
"name_en": "Dohyeon Lim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2025-02",
|
||||||
|
"major": "Computer Science and Engineering",
|
||||||
|
"current_position": "He is now with TakenSoft (테이큰소프트)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김소용",
|
||||||
|
"name_en": "So-Yong Kim",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2024-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Catholic University of Leuven (UCLouvain) in Belgium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김민지",
|
||||||
|
"name_en": "Min-Ji Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2024-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "She is now with LG Electronics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "안은지",
|
||||||
|
"name_en": "Eun-Ji Ahn",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2024-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "S is now with National Security Research Institute (국가보안연구소)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "오동규",
|
||||||
|
"name_en": "Dong-Kyu Oh",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2024-02",
|
||||||
|
"major": "Industrial Engineering",
|
||||||
|
"current_position": "He is now with Human-Plus (휴먼플러스)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김재성",
|
||||||
|
"name_en": "Jae-Seong Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2023-08",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최동규",
|
||||||
|
"name_en": "Dong-Kyu Choi",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2023-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with ISL in KNU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최진원",
|
||||||
|
"name_en": "Jin-Won Choi",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2023-02",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김지은",
|
||||||
|
"name_en": "Ji-Eun Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2023-02",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "She is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김철민",
|
||||||
|
"name_en": "Cheol-Min Kim",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2022-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with KETI (한국전자기술연구원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김경식",
|
||||||
|
"name_en": "Kyung-Sik Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2022-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Hyundai Autoever (현대오토에버)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김근수",
|
||||||
|
"name_en": "Keun-Soo Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2022-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Shinhan Bank (신한은행)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "정성우",
|
||||||
|
"name_en": "Seong-Woo Jeong",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2022-02",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최민철",
|
||||||
|
"name_en": "Min-Cheol Choi",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2022-02",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "장준희",
|
||||||
|
"name_en": "Jun-Hee Jang",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2021-08",
|
||||||
|
"major": "Computer Science and Engineering",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "Muhammad Hafidh Firmansyah",
|
||||||
|
"name_en": "Muhammad Hafidh Firmansyah",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2021-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with State Polytechnic University of Jember in Indonesia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "장강민",
|
||||||
|
"name_en": "Kang-Min Jang",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2021-02",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이홍근",
|
||||||
|
"name_en": "Hong-Keun Lee",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2021-02",
|
||||||
|
"major": "Information Science",
|
||||||
|
"current_position": "He is now with NIA (한국지능정보사회진흥원)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "남혜빈",
|
||||||
|
"name_en": "Hyebeen Nam",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2021-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "She is now with ISL in KNU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최낙중",
|
||||||
|
"name_en": "Nak-Jung Choi",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2020-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Agency for Defense Development (국방과학연구소)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "정민우",
|
||||||
|
"name_en": "Min-Woo Jung",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2019-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with LG Electronics (LG전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "강형우",
|
||||||
|
"name_en": "Hyung-Woo Kang",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2018-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최예찬",
|
||||||
|
"name_en": "Ye-Chan Choi",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2018-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with LG-CNS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최상일",
|
||||||
|
"name_en": "Sang-Il Choi",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2017-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Daegu Catholic University (대구가톨릭대학교) as a professor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "박진호",
|
||||||
|
"name_en": "Jin-Ho Park",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2016-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with BiThumb (빗썸코리아)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "석성윤",
|
||||||
|
"name_en": "Sung-Yoon Seok",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2016-08",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김진규",
|
||||||
|
"name_en": "Jin-Kyu Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2016-08",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김재철",
|
||||||
|
"name_en": "Jae-Cheol Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2016-08",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김지인",
|
||||||
|
"name_en": "Ji-In Kim",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2016-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with FAM Tech. (팸텍) as CEO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김우주",
|
||||||
|
"name_en": "Woo-Ju Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2016-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with ZCube (지큐브)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "박정섭",
|
||||||
|
"name_en": "Jung-Sub Park",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2015-08",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최종명",
|
||||||
|
"name_en": "Jong-Myoung Choi",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2015-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with NP Communications"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이종관",
|
||||||
|
"name_en": "Jong-Kwan Lee",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2014-02",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Hanwha Ocean (한화오션)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이상헌",
|
||||||
|
"name_en": "Sang-Hun Lee",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2014-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Inno Wireless"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "민경욱",
|
||||||
|
"name_en": "Kyeong-Wook Min",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2013-02",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김상헌",
|
||||||
|
"name_en": "Sang-Heon Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2013-02",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "Moneeb Gohar",
|
||||||
|
"name_en": "Moneeb Gohar",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2012-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Department of Computer Science, Bahria University (at Islamabad) in Pakistan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "박재완",
|
||||||
|
"name_en": "Jae-Wan Park",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2012-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Ministry of Justice (Immigration Information Center, 법무부)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이재경",
|
||||||
|
"name_en": "Jae-Kyoung Lee",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2012-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "She is now with Korea Transportation Satefy Authority (한국교통안전공단)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "하원기",
|
||||||
|
"name_en": "Won-Ki Ha",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2012-02",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김근희",
|
||||||
|
"name_en": "Keun-Hee Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2012-02",
|
||||||
|
"major": "Samsung Electronics Program",
|
||||||
|
"current_position": "She is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "권순홍",
|
||||||
|
"name_en": "Soon-Hong Kwon",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2010-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김동필",
|
||||||
|
"name_en": "Dong-Phil Kim",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2009-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with National Security Research Institute (국가보안연구소)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최린",
|
||||||
|
"name_en": "Lin Cui",
|
||||||
|
"degree": "PhD",
|
||||||
|
"graduated_at": "2009-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Tianjin University of Technology and Education in China"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이동화",
|
||||||
|
"name_en": "Dong-Hwa Lee",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2009-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "박재성",
|
||||||
|
"name_en": "Jae-Sung Park",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2008-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Gom&Company (곰앤컴퍼니)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "주수경",
|
||||||
|
"name_en": "Su-Kyoung Ju",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2008-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "She is now with Youngjin Univ. (영진전문대학교)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "윤성식",
|
||||||
|
"name_en": "Sung-Shik Yoon",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2007-08",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Jo-Il Textile Processing Company (조일가공) as CEO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김상태",
|
||||||
|
"name_en": "Sang-Tae Kim",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2007-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with LG Electronics (LG전자)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "하종식",
|
||||||
|
"name_en": "Jong-Shik Ha",
|
||||||
|
"degree": "MS",
|
||||||
|
"graduated_at": "2007-02",
|
||||||
|
"major": null,
|
||||||
|
"current_position": "He is now with Samsung Electronics (삼성전자)"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,801 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2026,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 0414",
|
||||||
|
"en": "Computer Networks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 0401",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2025,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 0432",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 0402",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2025,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 0201",
|
||||||
|
"en": "Introduction to Computer Science and Engineering"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 0312",
|
||||||
|
"en": "Network Programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 0401",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 0461",
|
||||||
|
"en": "Problem-based Engineering Training Experiment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2024,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 0432",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BIDA 0201",
|
||||||
|
"en": "Big Data Convergence Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 0402",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 0460",
|
||||||
|
"en": "Problem-based Engineering Training Experiment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2024,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "BIDA 0201",
|
||||||
|
"en": "Big Data Convergence Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 0401",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2023,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "SABBATICAL",
|
||||||
|
"en": "No Class (Sabbatical)",
|
||||||
|
"note": "Sabbatical"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2023,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 0312",
|
||||||
|
"en": "Network Programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BIDA 0201",
|
||||||
|
"en": "Big Data Convergence Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CICT 0703",
|
||||||
|
"en": "Software Convergence Project 3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2022,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "CLTR 273001",
|
||||||
|
"en": "Problem Solving and Computing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401001",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2022,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 312002",
|
||||||
|
"en": "Network Programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402001",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 460001",
|
||||||
|
"en": "Problem-based Engineering Training Experiment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CICT 701001",
|
||||||
|
"en": "Software Convergence Project 1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2021,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 402001",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2021,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "CLTR 273001",
|
||||||
|
"en": "Problem Solving and Computing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GLSO 218001",
|
||||||
|
"en": "Software Convergence Design 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402001",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 778001",
|
||||||
|
"en": "Advanced Mobile Computing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2020,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 401003",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401004",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GLSO 228001",
|
||||||
|
"en": "Topics in Software Convergence IV"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2020,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 401016",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402003",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 778001",
|
||||||
|
"en": "Advanced Mobile Computing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2019,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401003",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402016",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2019,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 201010",
|
||||||
|
"en": "Introduction to Computer Science and Engineering"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401016",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402003",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2018,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 401005",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402017",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2018,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 401021",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402006",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2017,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GLSO 213001",
|
||||||
|
"en": "Creative Convergence Design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401003",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2017,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 312001",
|
||||||
|
"en": "Network Programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401018",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402005",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 742001",
|
||||||
|
"en": "Advanced Computer Networks"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2016,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 331013) and DS Applications (COMP 216002",
|
||||||
|
"en": "Data Structures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401004",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402016",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2016,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 414003",
|
||||||
|
"en": "Computer Networks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401031",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402005",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402006",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2015,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401006",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401007",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402022",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2015,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 201009",
|
||||||
|
"en": "Introduction to Computer Science and Engineering"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 312001",
|
||||||
|
"en": "Network Programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 312002",
|
||||||
|
"en": "Network Programming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401021",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402004",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2014,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 401006",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402021",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 742001",
|
||||||
|
"en": "Advanced Computer Networks"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2014,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 414002",
|
||||||
|
"en": "Computer Networks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402004",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2013,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ITEC 401005",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402017",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2013,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 402005",
|
||||||
|
"en": "Capstone Design Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2012,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ITEC 401003",
|
||||||
|
"en": "Capstone Design Project I"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 422021",
|
||||||
|
"en": "Senior Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2012,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 214002",
|
||||||
|
"en": "Web Programming Design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 749001",
|
||||||
|
"en": "Computer Network Protocols"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ELEC 957001",
|
||||||
|
"en": "Next Generation Internet Technology"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2011,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COME 311003",
|
||||||
|
"en": "Probability and Statistics: Class 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 311004",
|
||||||
|
"en": "Probability and Statistics: Class 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 764001",
|
||||||
|
"en": "Internet Computing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 432001",
|
||||||
|
"en": "Topics in Software"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2011,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COMP 214001",
|
||||||
|
"en": "Web Programming Design: Class 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 214002",
|
||||||
|
"en": "Web Programming Design: Class 2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2010,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COME 311003",
|
||||||
|
"en": "Probability and Statistics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 331004",
|
||||||
|
"en": "Data Structures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 422022",
|
||||||
|
"en": "Senior Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2010,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 450001",
|
||||||
|
"en": "Internet Programming Design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 414003",
|
||||||
|
"en": "Computer Networks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ELEC 957001",
|
||||||
|
"en": "Next-Generation Internet Technology"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 408016",
|
||||||
|
"en": "Senior Project I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2009,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COME 31103",
|
||||||
|
"en": "Probability and Statistics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 33105",
|
||||||
|
"en": "Data Structures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 42202",
|
||||||
|
"en": "Senior Project II"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2009,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20704",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 45001",
|
||||||
|
"en": "Internet Programming Design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 40803",
|
||||||
|
"en": "Senior Project I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2008,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "COME 31103",
|
||||||
|
"en": "Probability and Statistics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 76401",
|
||||||
|
"en": "Internet Computing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ELEC 75401",
|
||||||
|
"en": "Computer Network: Special"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2008,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 31401",
|
||||||
|
"en": "Internet Programming and Practices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 74901",
|
||||||
|
"en": "Computer Network Protocols"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2007,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20703",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 31103",
|
||||||
|
"en": "Probability and Statistics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 41401",
|
||||||
|
"en": "Computer Networks"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2007,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20109",
|
||||||
|
"en": "C Programming and Practices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 20704",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 31401",
|
||||||
|
"en": "Internet Programming and Practices"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2006,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20703",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COME 31103",
|
||||||
|
"en": "Probability and Statistics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 76401",
|
||||||
|
"en": "Internet Computing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2006,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20704",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 31401",
|
||||||
|
"en": "Internet Programming and Practices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 75101",
|
||||||
|
"en": "Computer Performance Analysis"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2005,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20703",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 20806",
|
||||||
|
"en": "Data Structures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 41402",
|
||||||
|
"en": "Computer Network"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2005,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20704",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 49201",
|
||||||
|
"en": "Internet Programming Design"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 74901",
|
||||||
|
"en": "Computer Network Protocols"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Fall",
|
||||||
|
"year": 2004,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "ELEC 26104",
|
||||||
|
"en": "Computer Engineering"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 22101",
|
||||||
|
"en": "Data Structures II"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EECS 31201",
|
||||||
|
"en": "Network Programming"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "Spring",
|
||||||
|
"year": 2004,
|
||||||
|
"courses": [
|
||||||
|
{
|
||||||
|
"code": "EECS 20704",
|
||||||
|
"en": "Introduction to Computer Science"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "COMP 21105",
|
||||||
|
"en": "Data Structures I"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name_ko": "고석주",
|
||||||
|
"name_en": "Seok-Joo Koh",
|
||||||
|
"degree": "Professor",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": "sjkoh@knu.ac.kr",
|
||||||
|
"is_advisor": true,
|
||||||
|
"display_order": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최동규",
|
||||||
|
"name_en": "Dong-Kyu Choi",
|
||||||
|
"degree": "PhD",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "남혜빈",
|
||||||
|
"name_en": "Hye-Been Nam",
|
||||||
|
"degree": "PhDCandidate",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "임도현",
|
||||||
|
"name_en": "Dohyeon Lim",
|
||||||
|
"degree": "PhDStudent",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최준혁",
|
||||||
|
"name_en": "Jun-Hyeok Choi",
|
||||||
|
"degree": "PhDStudent",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "신광선",
|
||||||
|
"name_en": "Gwang-Seon Shin",
|
||||||
|
"degree": "MSCandidate",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이환웅",
|
||||||
|
"name_en": "Hwan-Woong Lee",
|
||||||
|
"degree": "MSStudent",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "Hoang Anh Dang",
|
||||||
|
"name_en": "Hoang Anh Dang",
|
||||||
|
"degree": "MSStudent",
|
||||||
|
"affiliation": null,
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김민희",
|
||||||
|
"name_en": "Minhee Kim",
|
||||||
|
"degree": "PhDCandidate",
|
||||||
|
"affiliation": "School of Computer Science and Engineering",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "김근솔",
|
||||||
|
"name_en": "Keun-Sol Kim",
|
||||||
|
"degree": "PhDCandidate",
|
||||||
|
"affiliation": "Department of Information Security",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "민성준",
|
||||||
|
"name_en": "Sung-Jun Min",
|
||||||
|
"degree": "PhDCandidate",
|
||||||
|
"affiliation": "Department of Information Science",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "최진원",
|
||||||
|
"name_en": "Jin-Won Choi",
|
||||||
|
"degree": "PhDStudent",
|
||||||
|
"affiliation": "Department of Science and Technology",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "정기수",
|
||||||
|
"name_en": "Ki-Soo Jung",
|
||||||
|
"degree": "PhDStudent",
|
||||||
|
"affiliation": "Department of Science and Technology",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "이미주",
|
||||||
|
"name_en": "Mi-Joo Lee",
|
||||||
|
"degree": "PhDStudent",
|
||||||
|
"affiliation": "Department of ICT Convergence",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "박유나",
|
||||||
|
"name_en": "Yunah Park",
|
||||||
|
"degree": "MSCandidate",
|
||||||
|
"affiliation": "Department of Information Security",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_ko": "박채영",
|
||||||
|
"name_en": "Chae-Young Park",
|
||||||
|
"degree": "MSCandidate",
|
||||||
|
"affiliation": "Department of Information Science",
|
||||||
|
"email": null,
|
||||||
|
"is_advisor": false,
|
||||||
|
"display_order": 16
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,752 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "국내 지역별 태양광 발전량 및 전력 단가 예측 웹 서비스 제공 장치 및 그 방법",
|
||||||
|
"inventors": "고석주, 김경훈, 김나현, 정수인, 홍일표",
|
||||||
|
"application_no": "2025-0200788",
|
||||||
|
"application_at": "2025-12-16",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2025-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "사물인터넷 네트워크 환경에서 QUIC 기반으로 CoAP 메시지를 중계하는 시스템",
|
||||||
|
"inventors": "고석주, 정중화, 남혜빈, 최동규, 김민지",
|
||||||
|
"application_no": "2023-0180751",
|
||||||
|
"application_at": "2023-12-13",
|
||||||
|
"registration_no": "2963109",
|
||||||
|
"registration_at": "2026-05-06",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2023-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "엣지 컴퓨팅을 위한 사물인터넷 서비스 시스템",
|
||||||
|
"inventors": "고석주, 정중화, 남혜빈, 최동규",
|
||||||
|
"application_no": "2023-0161365",
|
||||||
|
"application_at": "2023-11-20",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2023-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무선 네트워크 환경에서 핸드오버 및 커넥션 마이그레이션을 지원하는 장치 및 방법",
|
||||||
|
"inventors": "고석주, 김소용, 모닙 고하르, 최동규",
|
||||||
|
"application_no": "2023-0063455",
|
||||||
|
"application_at": "2023-05-17",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2023-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "제한된 통신 환경에서 브로커 서버를 이용한 스마트 약상자 제어 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 김근수, 김철민, 김경식, 나재욱, 박진호",
|
||||||
|
"application_no": "2020-0164156",
|
||||||
|
"application_at": "2020-11-30",
|
||||||
|
"registration_no": "2384614",
|
||||||
|
"registration_at": "2022-04-05",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2020-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "단방향 가시광 통신 환경에서 데이터 전송 신뢰성 개선을 위한 방법 및 이를 이용한 하이브리드",
|
||||||
|
"inventors": "고석주, 김소용, 최동규, 남혜빈, 정중화, 김창묵",
|
||||||
|
"application_no": "2020-0164152",
|
||||||
|
"application_at": "2020-11-30",
|
||||||
|
"registration_no": "2420614",
|
||||||
|
"registration_at": "2022-07-08",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2020-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "사물 인터넷 서비스를 제공하기 위한 QUIC-Proxy를 이용한 데이터 전달 방법 및 장치",
|
||||||
|
"inventors": "고석주, 김소용, 최동규, 남혜빈, 정중화",
|
||||||
|
"application_no": "2020-0164141",
|
||||||
|
"application_at": "2020-11-30",
|
||||||
|
"registration_no": "2345473",
|
||||||
|
"registration_at": "2021-12-27",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2020-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "금연구역 관리 서비스 방법 및 시스템",
|
||||||
|
"inventors": "고석주, 이정우, 이용호, 권오상, 정서영, 김경식",
|
||||||
|
"application_no": "2020-0101365",
|
||||||
|
"application_at": "2020-08-12",
|
||||||
|
"registration_no": "2375686",
|
||||||
|
"registration_at": "2022-03-14",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2020-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "사물 인터넷 환경에서 CoAP 기반의 데이터 스트리밍 방법 및 통신 시스템",
|
||||||
|
"inventors": "고석주, 정중화, 최동규, 남혜빈, 이채현",
|
||||||
|
"application_no": "2020-0094058",
|
||||||
|
"application_at": "2020-07-28",
|
||||||
|
"registration_no": "2375703",
|
||||||
|
"registration_at": "2022-03-14",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2020-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "인공지능 기반 적외선 카메라 센싱 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 홍성기, 김희원, 박명훈, 권민철",
|
||||||
|
"application_no": "2019-0174964",
|
||||||
|
"application_at": "2019-12-26",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2019-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "폭력 행위 관리 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 김동욱, 윤서원, 구영준, 성경화, 경예지",
|
||||||
|
"application_no": "2019-0122161",
|
||||||
|
"application_at": "2019-10-02",
|
||||||
|
"registration_no": "2264275",
|
||||||
|
"registration_at": "2021-06-07",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2019-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "차량 인포테인먼트 관리 시스템",
|
||||||
|
"inventors": "고석주, 최동규, 정중화, 남혜빈, 손종명",
|
||||||
|
"application_no": "2019-0120044",
|
||||||
|
"application_at": "2019-09-27",
|
||||||
|
"registration_no": "2410024",
|
||||||
|
"registration_at": "2022-06-13",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2019-09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "차량 인포테인먼트 마스터 장치 및 이를 포함하는 차량 인포테인먼트 통합 관리 시스템",
|
||||||
|
"inventors": "고석주, 최동규, 정중화, 남혜빈, 신호경",
|
||||||
|
"application_no": "2019-0059551",
|
||||||
|
"application_at": "2019-05-21",
|
||||||
|
"registration_no": "2251310",
|
||||||
|
"registration_at": "2021-05-06",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2019-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CoAP 기반의 센싱 정보 스트리밍 방법",
|
||||||
|
"inventors": "고석주, 최동규, 정중화, 남혜빈",
|
||||||
|
"application_no": "2018-0168580",
|
||||||
|
"application_at": "2018-12-24",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이미지에서 기계학습 기법을 활용한 특정 부품영역 탐지 방법",
|
||||||
|
"inventors": "고석주, 김대기, 김동인, 방종원, 우진철, 정중화",
|
||||||
|
"application_no": "2018-0167974",
|
||||||
|
"application_at": "2018-12-21",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "블록체인 기반의 재정장부 관리 시스템",
|
||||||
|
"inventors": "고석주, 신승민, 서상민, 송동훈, 최효선, 그레고즈 리뼤시치",
|
||||||
|
"application_no": "2018-0167970",
|
||||||
|
"application_at": "2018-12-21",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "블록체인을 이용한 게임정보 처리 방법",
|
||||||
|
"inventors": "고석주, 정재훈, 서창호, 노경환, 원응호",
|
||||||
|
"application_no": "2018-0167951",
|
||||||
|
"application_at": "2018-12-21",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "가시광 통신을 이용한 서비스 제공 시스템 및 그 방법",
|
||||||
|
"inventors": "고석주, 김철민, 김소용, 모닙고하르",
|
||||||
|
"application_no": "2018-0155013",
|
||||||
|
"application_at": "2018-12-05",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "차량 인포테인먼트 시스템",
|
||||||
|
"inventors": "고석주, 최동규, 정중화, 정민우",
|
||||||
|
"application_no": "2018-0083515",
|
||||||
|
"application_at": "2018-07-18",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CoAP 기반의 사물인터넷 기술을 이용한 센서 관리 방법 및 이를 이용한 시스템",
|
||||||
|
"inventors": "고석주, 최동규, 정중화, 정민우",
|
||||||
|
"application_no": "2018-0082485",
|
||||||
|
"application_at": "2018-07-16",
|
||||||
|
"registration_no": "2071974",
|
||||||
|
"registration_at": "2020-01-23",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "가시광 통신을 이용한 무선 인터넷 비밀번호 관리 시스템 및 방법",
|
||||||
|
"inventors": "김소용, 김철민, 고석주",
|
||||||
|
"application_no": "2018-0082463",
|
||||||
|
"application_at": "2018-07-16",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "시각 장애인을 위한 가시광 통신 기반의 교통 신호 전달 시스템 및 방법",
|
||||||
|
"inventors": "권경동, 금동우, 황지영, 채윤창, 김철민, 김소용, 고석주",
|
||||||
|
"application_no": "2018-0079319",
|
||||||
|
"application_at": "2018-07-09",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2018-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "데이터 전달 장치, 방법과 그를 이용한 사물 인터넷 시스템, 데이터 전달 방법을 실행하기 위한 프로그램이",
|
||||||
|
"inventors": "고석주, 최동규, 정중화",
|
||||||
|
"application_no": "2017-0153908",
|
||||||
|
"application_at": "2017-11-17",
|
||||||
|
"registration_no": "1969652",
|
||||||
|
"registration_at": "2019-04-10",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2017-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "분산된 게시-구독 기법을 이용한 CoAP 기반 사물 인터넷 시스템의 작동 방법",
|
||||||
|
"inventors": "고석주, 정중화, 최동규",
|
||||||
|
"application_no": "2017-0153357",
|
||||||
|
"application_at": "2017-11-16",
|
||||||
|
"registration_no": "2031726",
|
||||||
|
"registration_at": "2019-10-07",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2017-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "비콘 기반 식당 정보 서비스 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 안창준, 김혜경, 송명근, 최예찬, 허동",
|
||||||
|
"application_no": "2017-0087265",
|
||||||
|
"application_at": "2017-07-10",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2017-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "사물 인터넷 네트워크에서의 모바일 단말 이동성 제어 장치 및 방법",
|
||||||
|
"inventors": "고석주, 최상일",
|
||||||
|
"application_no": "2017-0064039",
|
||||||
|
"application_at": "2017-05-24",
|
||||||
|
"registration_no": "1847081",
|
||||||
|
"registration_at": "2018-04-03",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2017-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "데이터 전달 장치, 방법 및 그를 이용한 사물인터넷 시스템",
|
||||||
|
"inventors": "고석주, 정중화, 강형우, 최동규",
|
||||||
|
"application_no": "2016-0175882",
|
||||||
|
"application_at": "2016-12-21",
|
||||||
|
"registration_no": "1972470",
|
||||||
|
"registration_at": "2019-04-19",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2016-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "저전력 근거리 통신을 이용한 개인 정보 교환 방법 및 시스템, 이를 수행하기 위한 기록매체",
|
||||||
|
"inventors": "고석주, 김지인, 김지희, 김송아, 박지혜, 이승일, 서대화",
|
||||||
|
"application_no": "2016-0157411",
|
||||||
|
"application_at": "2016-11-24",
|
||||||
|
"registration_no": "1784309",
|
||||||
|
"registration_at": "2017-09-27",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2016-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "가시광 통신 기기 관리 방법 및 장치 (with 유양디앤유)",
|
||||||
|
"inventors": "김상옥, 유병오, 윤상호, 노승완, 고석주, 최상일",
|
||||||
|
"application_no": "2016-0157606",
|
||||||
|
"application_at": "2016-11-24",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2016-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "가시광 통신 방법 및 장치 (with 유양디앤유)",
|
||||||
|
"inventors": "김상옥, 유병오, 윤상호, 노승완, 고석주, 최상일",
|
||||||
|
"application_no": "2016-0157576",
|
||||||
|
"application_at": "2016-11-24",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2016-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이동 단말 및 관리 서버의 제어 방법",
|
||||||
|
"inventors": "고석주, 최상일",
|
||||||
|
"application_no": "2016-0051126",
|
||||||
|
"application_at": "2016-04-26",
|
||||||
|
"registration_no": "1836116",
|
||||||
|
"registration_at": "2018-03-02",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2016-04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무선 통신을 이용한 범용 안내 서비스 제공 방법 및 시스템",
|
||||||
|
"inventors": "고석주, 장호영, 이종훈, 박찬석, 서민영",
|
||||||
|
"application_no": "2016-0013614",
|
||||||
|
"application_at": "2016-02-03",
|
||||||
|
"registration_no": "1716661",
|
||||||
|
"registration_at": "2017-03-09",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2016-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "디지털 도어락 시스템 및 그 제어 방법",
|
||||||
|
"inventors": "고석주, 권다영, 노혜성, 이수정",
|
||||||
|
"application_no": "2015-0163735",
|
||||||
|
"application_at": "2015-11-23",
|
||||||
|
"registration_no": "1814555",
|
||||||
|
"registration_at": "2017-12-27",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2015-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무인 지상차량을 이용한 주차차량 관리 시스템",
|
||||||
|
"inventors": "김인한, 권혜련, 이은섭, 고석주",
|
||||||
|
"application_no": "2015-0008735",
|
||||||
|
"application_at": "2015-01-19",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2015-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ESL 신호를 이용하는 측위 방법, 이를 수행하기 위한 기록 매체, 단말기 및 시스템",
|
||||||
|
"inventors": "고석주, 김지인, 이민형, 김종근, 박지수, 조정근, 이승일, 서대화",
|
||||||
|
"application_no": "2014-0180037",
|
||||||
|
"application_at": "2014-12-15",
|
||||||
|
"registration_no": "1596320",
|
||||||
|
"registration_at": "2016-02-16",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ESL 신호를 이용하여 위치 기반 서비스를 제공하는 스마트 장치",
|
||||||
|
"inventors": "고석주, 김지인, 이민형, 김종근, 박지수, 조정근, 이승일, 서대화",
|
||||||
|
"application_no": "2014-0180036",
|
||||||
|
"application_at": "2014-12-15",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "3차원 형상 복원을 위한 볼륨 카빙 장치 및 그 방법",
|
||||||
|
"inventors": "고석주, 하태윤, 서대화, 정귀영, 이상은, 김지인, 김한별",
|
||||||
|
"application_no": "2014-0167063",
|
||||||
|
"application_at": "2014-11-27",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "립모션 기기를 이용한 수화 번역 시스템 및 그 방법",
|
||||||
|
"inventors": "고석주, 조재현, 서대화, 이동훈, 이상은, 김지인, 하대규",
|
||||||
|
"application_no": "2014-0166166",
|
||||||
|
"application_at": "2014-11-26",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "립모션 기기의 수화 번역 정확도 향상을 위한 수화 번역 시스템 및 그 방법",
|
||||||
|
"inventors": "고석주, 조재현, 서대화, 이동훈, 이상은, 김지인, 하대규",
|
||||||
|
"application_no": "2014-0166165",
|
||||||
|
"application_at": "2014-11-26",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "저전력 근거리 통신을 이용한 개인 정보 교환 방법 및 시스템, 이를 수행하기 위한 기록매체",
|
||||||
|
"inventors": "고석주, 김지희, 서대화, 박지혜, 이승일, 김지인, 김송아",
|
||||||
|
"application_no": "2014-0164802",
|
||||||
|
"application_at": "2014-11-24",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "차량 분산 방법 및 장치, 이를 수행하기 위한 기록매체",
|
||||||
|
"inventors": "고석주, 김지인, 엄진욱, 김민규, 홍석진, 배정규, 서대화",
|
||||||
|
"application_no": "2014-0161975",
|
||||||
|
"application_at": "2014-11-19",
|
||||||
|
"registration_no": "1612047",
|
||||||
|
"registration_at": "2016-04-06",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2014-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "신규 액세스 포인트의 위치 인식 방법 및 이를 이용하는 서버",
|
||||||
|
"inventors": "고석주, 김우주, 이민형, 하태윤, 심대섭, 조정근, 강보영, 서대화",
|
||||||
|
"application_no": "2013-0167207",
|
||||||
|
"application_at": "2013-12-30",
|
||||||
|
"registration_no": "1568365",
|
||||||
|
"registration_at": "2015-11-05",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2013-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "디바이스 탐색 장치 및 디바이스 탐색 방법",
|
||||||
|
"inventors": "고석주, 이상헌, 최상일",
|
||||||
|
"application_no": "2013-0140737",
|
||||||
|
"application_at": "2013-11-19",
|
||||||
|
"registration_no": "1405248",
|
||||||
|
"registration_at": "2015-04-17",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2013-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "오픈플로우 통신 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 김지인, 최낙중",
|
||||||
|
"application_no": "2013-0140736",
|
||||||
|
"application_at": "2013-11-19",
|
||||||
|
"registration_no": "1525047",
|
||||||
|
"registration_at": "2015-05-27",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2013-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이동통신 시스템의 추적 영역 코드 구성 장치 및 방법",
|
||||||
|
"inventors": "고석주, 강형우, 백태산, 강현구",
|
||||||
|
"application_no": "2013-0140734",
|
||||||
|
"application_at": "2013-11-19",
|
||||||
|
"registration_no": "1538453",
|
||||||
|
"registration_at": "2015-07-15",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2013-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "프로세스 모니터링과 키보드 잠금을 이용한 프로세스 관리 방법 및 프로세스 관리 장치",
|
||||||
|
"inventors": "고석주, 박진호, 이재휘",
|
||||||
|
"application_no": "2013-0108587",
|
||||||
|
"application_at": "2013-09-10",
|
||||||
|
"registration_no": "1515493",
|
||||||
|
"registration_at": "2015-04-21",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2013-09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "공유기 탐지 장치 및 공유기 탐지 방법",
|
||||||
|
"inventors": "고석주, 박진호, 김철민",
|
||||||
|
"application_no": "2013-0091483",
|
||||||
|
"application_at": "2013-08-01",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2013-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "호스트 식별 프로토콜 네트워크 환경의 통신 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 최상일",
|
||||||
|
"application_no": "2012-0154836",
|
||||||
|
"application_at": "2012-12-27",
|
||||||
|
"registration_no": "1405248",
|
||||||
|
"registration_at": "2014-06-02",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2012-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "액세스 라우터 및 그를 이용한 핸드오버 제어 방법",
|
||||||
|
"inventors": "고석주, 최낙중, 김지인",
|
||||||
|
"application_no": "2012-0154835",
|
||||||
|
"application_at": "2012-12-27",
|
||||||
|
"registration_no": "1447104",
|
||||||
|
"registration_at": "2014-09-26",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2012-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "호스트 식별 프로토콜 네트워크 환경의 이동통신 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 김지인, 이상헌",
|
||||||
|
"application_no": "2012-0146740",
|
||||||
|
"application_at": "2012-12-14",
|
||||||
|
"registration_no": "1459628",
|
||||||
|
"registration_at": "2014-11-03",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2012-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "라우터의 호스트 위치 관리 방법",
|
||||||
|
"inventors": "고석주, 김지인",
|
||||||
|
"application_no": "2012-0030058",
|
||||||
|
"application_at": "2012-03-23",
|
||||||
|
"registration_no": "1356721",
|
||||||
|
"registration_at": "2014-01-20",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2012-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "분산형 구조를 이용한 데이터 통신 방법",
|
||||||
|
"inventors": "고석주, 모닙고하르, 이재경",
|
||||||
|
"application_no": "2011-0111384",
|
||||||
|
"application_at": "2011-10-28",
|
||||||
|
"registration_no": "1311864",
|
||||||
|
"registration_at": "2013-09-17",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2011-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "라우터, 그것을 포함하는 통신 네트워크 시스템 및 그것의 이동성 제어 방법",
|
||||||
|
"inventors": "고석주, 최상일",
|
||||||
|
"application_no": "2011-0107533",
|
||||||
|
"application_at": "2011-10-20",
|
||||||
|
"registration_no": "1329331",
|
||||||
|
"registration_at": "2013-11-07",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2011-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "모바일 액세스 게이트웨이 및 이를 이용한 이동성 제어 방법",
|
||||||
|
"inventors": "고석주, 김지인",
|
||||||
|
"application_no": "2011-0057608",
|
||||||
|
"application_at": "2011-06-14",
|
||||||
|
"registration_no": "1223047",
|
||||||
|
"registration_at": "2013-01-10",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2011-06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "멀티홈잉 환경에서 프록시 모바일 인터넷 프로토콜을 사용하는 이동통신 시스템 및 그것의 핸드오버 방법",
|
||||||
|
"inventors": "고석주, 김지인",
|
||||||
|
"application_no": "2011-0001905",
|
||||||
|
"application_at": "2011-01-07",
|
||||||
|
"registration_no": "1189140",
|
||||||
|
"registration_at": "2012-10-02",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2011-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "멀티캐스트 방법 및 억세스 게이트웨이",
|
||||||
|
"inventors": "고석주, 모닙고하르, 이재경",
|
||||||
|
"application_no": "2010-0137897",
|
||||||
|
"application_at": "2010-12-29",
|
||||||
|
"registration_no": "1200407",
|
||||||
|
"registration_at": "2012-11-06",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2010-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "프록시 모바일 인터넷 프로토콜을 사용하는 이동통신 시스템 및 그것의 핸드오버 방법",
|
||||||
|
"inventors": "고석주, 김지인",
|
||||||
|
"application_no": "2010-0108058",
|
||||||
|
"application_at": "2010-11-02",
|
||||||
|
"registration_no": "1258238",
|
||||||
|
"registration_at": "2013-04-19",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2010-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이동 단말, 통신 네트워크 및 그것의 이동성 제어 방법",
|
||||||
|
"inventors": "고석주, 최상일",
|
||||||
|
"application_no": "2010-0107701",
|
||||||
|
"application_at": "2010-11-01",
|
||||||
|
"registration_no": "1177354",
|
||||||
|
"registration_at": "2012-08-21",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2010-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "빠른 핸드오버를 지원하기 위한 이동통신 시스템 및 방법",
|
||||||
|
"inventors": "고석주, 모닙고하르, 박재완",
|
||||||
|
"application_no": "2010-0039688",
|
||||||
|
"application_at": "2010-04-28",
|
||||||
|
"registration_no": "1091397",
|
||||||
|
"registration_at": "2011-12-01",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2010-04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이동 단말간 데이터 전송 시스템 및 그 방법",
|
||||||
|
"inventors": "고석주, 권순홍",
|
||||||
|
"application_no": "2009-0048797",
|
||||||
|
"application_at": "2009-06-02",
|
||||||
|
"registration_no": "1078156",
|
||||||
|
"registration_at": "2011-10-24",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2009-06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "프록시 모바일 IPv6망내 이동단말간 통신 경로 최적화 시스템 및 그 방법",
|
||||||
|
"inventors": "고석주, 김지인",
|
||||||
|
"application_no": "2009-0048796",
|
||||||
|
"application_at": "2009-06-02",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2009-06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이종 무선망간 수직 핸드오버를 위한 이동 SCTP의 적응적 혼잡 제어 방법 및 장치",
|
||||||
|
"inventors": "고석주, 김동필",
|
||||||
|
"application_no": "2009-0013072",
|
||||||
|
"application_at": "2009-02-17",
|
||||||
|
"registration_no": "1048251",
|
||||||
|
"registration_at": "2011-07-04",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2009-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "서비스 품질 향상을 위한 PR-SCTP 기반 실시간 멀티미디어 데이터 전송 방법",
|
||||||
|
"inventors": "고석주, 김상태",
|
||||||
|
"application_no": "2008-0135342",
|
||||||
|
"application_at": "2008-12-29",
|
||||||
|
"registration_no": "1040780",
|
||||||
|
"registration_at": "2011-06-03",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2008-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이종 무선망간의 수직적 핸드오버를 위한 데이터 전송 방법 및 장치",
|
||||||
|
"inventors": "고석주, 김동필",
|
||||||
|
"application_no": "2008-0083995",
|
||||||
|
"application_at": "2008-08-27",
|
||||||
|
"registration_no": "0980592",
|
||||||
|
"registration_at": "2010-08-31",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2008-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무선통신용 멀티캐스트 핸드오버 방법",
|
||||||
|
"inventors": "고석주, 박재성",
|
||||||
|
"application_no": "2008-0077918",
|
||||||
|
"application_at": "2008-08-08",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2008-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무선 인터넷 망에서의 바이캐스팅을 이용한 SIP 핸드오버 방법 및 이동 단말",
|
||||||
|
"inventors": "고석주, 이동화",
|
||||||
|
"application_no": "2008-0052456",
|
||||||
|
"application_at": "2008-06-04",
|
||||||
|
"registration_no": "0987555",
|
||||||
|
"registration_at": "2010-10-06",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2008-06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무선 인터넷 환경에서의 바이캐스팅 기반 SCTP 핸드오버 방법 및 이동 단말",
|
||||||
|
"inventors": "고석주, 권순홍, 김동필",
|
||||||
|
"application_no": "2008-0049203",
|
||||||
|
"application_at": "2008-05-27",
|
||||||
|
"registration_no": "0980582",
|
||||||
|
"registration_at": "2010-08-31",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2008-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Proxy MIP 기반 무선 인터넷 망에서의 바이캐스팅을 이용한 핸드오버 방법 및 장치",
|
||||||
|
"inventors": "고석주, 김지인",
|
||||||
|
"application_no": "2008-0049153",
|
||||||
|
"application_at": "2008-05-27",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2008-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "윈도우 기반의 이동 단말에서 SCTPLIB를 이용한 mSCTP 핸드오버 방법",
|
||||||
|
"inventors": "김용진, 고석주, 이동화, 김상태",
|
||||||
|
"application_no": "2007-0139730",
|
||||||
|
"application_at": "2007-12-28",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2007-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "전송 처리율 향상을 위한 SCTP 우선경로 설정 방법",
|
||||||
|
"inventors": "고석주, 주수경",
|
||||||
|
"application_no": "2007-0102031",
|
||||||
|
"application_at": "2007-10-10",
|
||||||
|
"registration_no": null,
|
||||||
|
"registration_at": null,
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2007-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "무선 네트워크에서 패킷의 손상정보를 사용하는 TCP 혼잡제어 방법",
|
||||||
|
"inventors": "고석주, 최린, 이동화, 김용진",
|
||||||
|
"application_no": "2007-0061209",
|
||||||
|
"application_at": "2007-06-21",
|
||||||
|
"registration_no": "0870619",
|
||||||
|
"registration_at": "2008-11-19",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2007-06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "이동 단말의 SCTP 핸드오버와 모바일 아이피의 연동 장치 및 그 방법",
|
||||||
|
"inventors": "고석주, 김동필",
|
||||||
|
"application_no": "2007-0050648",
|
||||||
|
"application_at": "2007-05-25",
|
||||||
|
"registration_no": "0880112",
|
||||||
|
"registration_at": "2009-01-15",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2007-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "청크첵섬을 사용하는 무선 인터넷 에스씨티피 송수신 시스템 및 방법",
|
||||||
|
"inventors": "김용진, 정종일, 고석주",
|
||||||
|
"application_no": "2006-0117121",
|
||||||
|
"application_at": "2006-11-24",
|
||||||
|
"registration_no": "0780921",
|
||||||
|
"registration_at": "2007-11-23",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2006-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "데이터 링크계층의 링크 정보를 이용하여 핸드오버를 수행하는 단말장치",
|
||||||
|
"inventors": "고석주, 김동필",
|
||||||
|
"application_no": "2005-0110213",
|
||||||
|
"application_at": "2005-11-17",
|
||||||
|
"registration_no": "0685740",
|
||||||
|
"registration_at": "2007-02-15",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2005-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "SCTP 기반의 핸드오버 기능을 구비한 단말장치 및 핸드오버 방법",
|
||||||
|
"inventors": "고석주, 김동필",
|
||||||
|
"application_no": "US 11-915457 (미국, 2007.11.26)",
|
||||||
|
"application_at": "2005-05-27",
|
||||||
|
"registration_no": "US 8,644,248 (미국, 2014.2.4)",
|
||||||
|
"registration_at": "2007-01-26",
|
||||||
|
"country": "한국",
|
||||||
|
"published_at": "2005-05"
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name_en": "Quick UDP Internet Connection (QUIC)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "QUIC-based Mobility and Multi-Streaming Support",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Services and Protocols for Internet-of-Things (IoT)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Constrained Application Protocol (CoAP)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "In-Vehicle Infotainment (IVI)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Optical Wireless or Visible Light Communications (OWC/VLC)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Lighting Control Networks (PLASA E1.45) for LED-based VLC",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Mobility Management: Distributed Mobility Control",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Multicast Routing Protocols and Reliable Multicasting",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "mobile SCTP (mSCTP): Soft Handover in Transport Layer",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Stream Control Transmission Protocol (SCTP)",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Telecommunication Optimization: Network Planning and Management",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "TAC/TAL Configuration for Paging and Location Management",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name_en": "Tabu Search and Genetic Algorithm",
|
||||||
|
"name_kr": null,
|
||||||
|
"display_order": 14
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"slug": "iot-standards",
|
||||||
|
"title": "AIoT & AI-RAN",
|
||||||
|
"abstract": "AIoT & AI-RAN investigates agent-based IoT architectures in which autonomous AI agents are distributed across sensor, edge, and radio access layers. The project studies agent-to-agent coordination over high-performance transport (gRPC/QUIC) bridged with constrained edge protocols (MQTT/CoAP), targeting AI-RAN designs for distributed autonomous control.",
|
||||||
|
"keywords": [
|
||||||
|
"AIoT",
|
||||||
|
"agent-based IoT",
|
||||||
|
"AI-RAN",
|
||||||
|
"constrained edge protocols",
|
||||||
|
"distributed autonomous control"
|
||||||
|
],
|
||||||
|
"standardsTrack": "National Research Foundation of Korea (NRF)",
|
||||||
|
"standardsOrg": "NRF",
|
||||||
|
"period": "2026 ~ Present",
|
||||||
|
"deliverables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "ccis",
|
||||||
|
"title": "MCM",
|
||||||
|
"abstract": "Multilateral and Collaborative Metaverse (MCM) refers to metaverse systems in which two or more participants simultaneously interact within a shared virtual space for a common collaborative purpose. Unlike single-user or purely competitive environments, an MCM service is defined by coordinated participation: its intended outcome cannot be achieved by a single user acting alone. The project defines general requirements, service classification, and a gap analysis against existing standards.",
|
||||||
|
"keywords": [
|
||||||
|
"MCM",
|
||||||
|
"metaverse",
|
||||||
|
"shared virtual space",
|
||||||
|
"coordinated participation",
|
||||||
|
"service classification"
|
||||||
|
],
|
||||||
|
"standardsTrack": "IEC/TC 100/WG 12 supported by KEIT",
|
||||||
|
"standardsOrg": "IEC TC100",
|
||||||
|
"period": "2026 ~ Present",
|
||||||
|
"deliverables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "vlc-iot",
|
||||||
|
"title": "MVQM",
|
||||||
|
"abstract": "Management of Visual Quality in Metaverse Systems (MVQM) is a conceptual and functional approach for managing visual quality by dynamically adapting visual data delivery in response to user context, device capability, and network conditions. It emphasizes user-centric, context-aware quality management over static system-centric optimization, covering hybrid visual assets, level of detail, and area of interest adaptation.",
|
||||||
|
"keywords": [
|
||||||
|
"MVQM",
|
||||||
|
"visual quality",
|
||||||
|
"hybrid visual assets",
|
||||||
|
"level of detail",
|
||||||
|
"area of interest"
|
||||||
|
],
|
||||||
|
"standardsTrack": "IEC/TC 100/WG 12 supported by KEIT",
|
||||||
|
"standardsOrg": "IEC TC100",
|
||||||
|
"period": "2026 ~ Present",
|
||||||
|
"deliverables": []
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"org": "IEC TC100",
|
||||||
|
"full": "Multimedia Systems and Equipment",
|
||||||
|
"scope": "international",
|
||||||
|
"period": "2018 ~ Present",
|
||||||
|
"role": "Convenor",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "WG12",
|
||||||
|
"name": "Multimedia Systems and Equipment for Metaverse (IEC 63614)",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "Multimedia Systems and Equipment for Metaverse - Part 1: Genaral",
|
||||||
|
"ref": "IEC TR 63614-1 (May 2026)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2026-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Multimedia Systems and Equipment for Metaverse - Part 2: Classification",
|
||||||
|
"ref": "IEC TS 63614-2 (May 2026)",
|
||||||
|
"status": "TS",
|
||||||
|
"publishedAt": "2026-05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Multimedia Systems and Equipment for Metaverse - Part 3: Gap Analysis",
|
||||||
|
"ref": "IEC TR 63614-3 (March 2026)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2026-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Multimedia Systems and Equipment for Metaverse - Part 4: AI Use Cases",
|
||||||
|
"ref": "IEC WDTR 63614-4 (In Progress)",
|
||||||
|
"status": "InProgress"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wg": "WG11(Convenor)",
|
||||||
|
"name": "User's QoE for Multimedia Conference Services (IEC 63478)",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "User's QoE for Multimedia Conference Services - Part 1: General",
|
||||||
|
"ref": "IEC TR 63478-1 (November 2023)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2023-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "User's QoE for Multimedia Conference Services - Part 2: Requirments",
|
||||||
|
"ref": "IEC IS 63478-2 (October 2025)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2025-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "User's QoE for Multimedia Conference Services - Part 3: Measurement Methods",
|
||||||
|
"ref": "IEC CDV 63478-3 (In Progress)",
|
||||||
|
"status": "InProgress"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wg": "WG30(TA17)",
|
||||||
|
"name": "Infotainment Services for Public Vehicles (IEC 63479)",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "Infotainment Services for Public Vehicles (PVIS) - Part 1: General",
|
||||||
|
"ref": "IEC TR 63479-1 (November 2023)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2023-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Infotainment Services for Public Vehicles (PVIS) - Part 2: Requirements",
|
||||||
|
"ref": "IEC IS 63479-2 (February 2026)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2026-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Infotainment Services for Public Vehicles (PVIS) - Part 3: Framework",
|
||||||
|
"ref": "IEC IS 63479-3 (January 2026)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2026-01"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wg": "WG30(TA17)",
|
||||||
|
"name": "Configurable Car Infotainment Services (IEC 63246)",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "Configurable Car Infotainment Services (CCIS) - Part 1: General",
|
||||||
|
"ref": "IEC 63246-1 (August 2021)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2021-08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Configurable Car Infotainment Services (CCIS) - Part 2: Requirements",
|
||||||
|
"ref": "IEC 63246-2 (January 2022)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2022-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Configurable Car Infotainment Services (CCIS) - Part 3: Framework",
|
||||||
|
"ref": "IEC 63246-3 (January 2022)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2022-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Configurable Car Infotainment Services (CCIS) - Part 4: Protocol",
|
||||||
|
"ref": "IEC TR 63246-4 (December 2022)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2022-12"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"org": "ISO/IEC JTC1/SC41",
|
||||||
|
"full": "Internet of Things and Digital Twin",
|
||||||
|
"scope": "international",
|
||||||
|
"period": "2020 ~ Present",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "WG",
|
||||||
|
"name": "(WG5) IoT-based Management of Tangible Cultural Heritage Assets (ISO/IEC TR 30189)",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "Part 1: Framework",
|
||||||
|
"ref": "ISO/IEC TR 30189-1 (March 2025)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2025-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Part 2: Use Cases",
|
||||||
|
"ref": "ISO/IEC CDTR 30189-2 (In Progress)",
|
||||||
|
"status": "InProgress"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"org": "ITU-T SG20",
|
||||||
|
"full": "IoT Services based on VLC",
|
||||||
|
"scope": "international",
|
||||||
|
"period": "2018 ~ 2020",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "Main",
|
||||||
|
"name": "IoT Services based on VLC",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "Framework of IoT Services based on Visible Light Communications (VLC)",
|
||||||
|
"ref": "ITU-T Recommendation Y.4465 (January 2020)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2020-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Functional Architecture for IoT Services based on VLC",
|
||||||
|
"ref": "ITU-T Recommendation Y.4474 (August 2020)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2020-08"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"org": "TTA/PG425",
|
||||||
|
"full": "가시광 통신 기반 사물인터넷 서비스",
|
||||||
|
"scope": "domestic",
|
||||||
|
"period": "2018 ~ 2020",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "Main",
|
||||||
|
"name": "가시광 통신 기반 사물인터넷 서비스",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "가시광 통신 기반 사물인터넷 서비스 - 제 1 부: 네트워크 모델 및 요구사항",
|
||||||
|
"ref": "TTAK.KO-10.1158-part1 (2019년 12월)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2019-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "가시광 통신 기반 사물인터넷 서비스 - 제 2 부: 프레임워크",
|
||||||
|
"ref": "TTAK.KO-10.1158-part2 (2020년 12월)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2020-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "가시광 통신 기반 사물인터넷 서비스 - 제 3 부: 프로토콜",
|
||||||
|
"ref": "TTAK.KO-10.1158-part3 (2020년 12월)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2020-12"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"org": "ITU-T SG13",
|
||||||
|
"full": "Mobility Management",
|
||||||
|
"scope": "international",
|
||||||
|
"period": "2002 ~ 2012",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "Main",
|
||||||
|
"name": "Mobility Management",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "NNI Mobility Management Requirements for SBI2K",
|
||||||
|
"ref": "ITU-T Supplement to Recommendation Q.Sup52 (December 2004)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2004-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Mobility Management Requirements for Next-Generation Networks",
|
||||||
|
"ref": "ITU-T Recommendation Q.1706/Y.2801 (July 2006)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2006-07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Framework of IPv6 Multi-homing for NGN",
|
||||||
|
"ref": "ITU-T Recommendation Y.2052 (Feb. 2008)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2008-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Generic Framework of Mobility Management for Next-Generation Networks",
|
||||||
|
"ref": "ITU-T Recommendation Q.1707/Y.2804 (Feb. 2008)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2008-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Framework of Location Management for Next-Generation Networks",
|
||||||
|
"ref": "ITU-T Recommendation Q.1708/Y.2805 (October 2008)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2008-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Framework of Handover Control for Next-Generation Networks",
|
||||||
|
"ref": "ITU-T Recommendation Q.1709/Y.2806 (October 2008)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2008-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Framework of Mobility Management in Service Stratum for NGN",
|
||||||
|
"ref": "ITU-T Recommendation Y.2809 (November 2011)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2011-11"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"org": "ISO/IEC JTC1/SC6",
|
||||||
|
"full": "WG7: Network and Transport",
|
||||||
|
"scope": "international",
|
||||||
|
"period": "2000 ~ 2014",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"wg": "Enhanced Communication Transport Protocol (ECTP)",
|
||||||
|
"name": "ISO/IEC 14476",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "ECTP-1: Specification of Simplex Multicast Transport",
|
||||||
|
"ref": "ITU-T Recommendation X.606 (October 2001) ISO/IEC IS 14476-1 (April 2002)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2001-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ECTP-2: Specification of QoS Management for Simplex Multicast Transport",
|
||||||
|
"ref": "ITU-T Recommendation X.606.1 (February 2003) ISO/IEC IS 14476-2 (June 2003)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2003-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ECTP-3: Specification of Duplex Multicast Transport",
|
||||||
|
"ref": "ITU-T Recommendation X.607 (February 2007) ISO/IEC IS 14476-3 (August 2008)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2007-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ECTP-4: Specification of QoS Management for Duplex Multicast Transport",
|
||||||
|
"ref": "ITU-T Recommendation X.607.1 (November 2008) ISO/IEC IS 14476-4 (April 2010)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2008-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ECTP-5: Specification of N-plex Multicast Transport",
|
||||||
|
"ref": "ITU-T Recommendation X.608 (February 2007) ISO/IEC IS 14476-5 (August 2008)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2007-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ECTP-6: Specification of QoS Management for N-plex Multicast Transport",
|
||||||
|
"ref": "ITU-T Recommendation X.608.1 (November 2008) ISO/IEC IS 14476-6 (April 2010)",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2008-11"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wg": "Relayed Multicast Protocol (RMCP)",
|
||||||
|
"name": "ISO/IEC 16512",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "RMCP-1: Framework",
|
||||||
|
"ref": "ITU-T Recommendation X.603 (April 2004) ISO/IEC IS 16512-1",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2004-04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "RMCP-2: Specification of Simplex Group Applications",
|
||||||
|
"ref": "ITU-T Recommendation X.603.1 (February 2007) ISO/IEC IS 16512-2",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2007-02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "RMCP-3: Specification of N-plex Group Applications",
|
||||||
|
"ref": "ITU-T Recommendation X.603.2 (September 2010) ISO/IEC IS 16512-3",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2010-09"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wg": "Mobile Multicast Communications (MMC)",
|
||||||
|
"name": "ISO/IEC 24793",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "MMC-1: MMC Framework",
|
||||||
|
"ref": "ITU-T Recommendation X.604 (March 2010) ISO/IEC IS 24793-1",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2010-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "MMC-2: MMC Protocol over Native IP Multicast Networks",
|
||||||
|
"ref": "ITU-T Recommendation X.604.1 (March 2010) ISO/IEC IS 24793-2",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2010-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "MMC-3: MMC Protocol over Overlay Multicast Networks",
|
||||||
|
"ref": "ITU-T Recommendation X.604.2 (September 2010) ISO/IEC IS 24793-3",
|
||||||
|
"status": "IS",
|
||||||
|
"publishedAt": "2010-09"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wg": "Future Network",
|
||||||
|
"name": "Problem Statement and Requirements (FN-PSR): ISO/IEC TR 29181",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"title": "Part 1: Overall Aspects",
|
||||||
|
"ref": "ISO/IEC TR 29181-1 (September 2012)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2012-09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Part 2: Naming and Addressing",
|
||||||
|
"ref": "ISO/IEC TR 29181-2 (December 2014)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2014-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Part 3: Switching and Routing",
|
||||||
|
"ref": "ISO/IEC TR 29181-3 (April 2013)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2013-04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Part 4: Mobility",
|
||||||
|
"ref": "ISO/IEC TR 29181-4 (December 2013)",
|
||||||
|
"status": "TR",
|
||||||
|
"publishedAt": "2013-12"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
services:
|
||||||
|
anl-backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: anl-backend-api
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- PORT=8080
|
||||||
|
- DB_PATH=/app/data/anl.db
|
||||||
|
- GIN_MODE=release
|
||||||
|
- CORS_ALLOW_ORIGINS=*
|
||||||
|
- AUTO_MIGRATE=true
|
||||||
|
volumes:
|
||||||
|
- anl-db-data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
anl-db-data:
|
||||||
|
name: anl-sqlite-storage
|
||||||
@@ -7,21 +7,9 @@ export const metadata: Metadata = {
|
|||||||
description: "AI Agent Networking Lab (ANL) research publications and patents overview",
|
description: "AI Agent Networking Lab (ANL) research publications and patents overview",
|
||||||
};
|
};
|
||||||
|
|
||||||
const REPRESENTATIVE_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)",
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function PublicationsIndexPage() {
|
export default function PublicationsIndexPage() {
|
||||||
// Selected 5 representative publications
|
// Selected representative publications (curated via database isHighlight flag)
|
||||||
const highlights = REPRESENTATIVE_HIGHLIGHT_TITLES.map((title) =>
|
const highlights = publications.filter((p) => p.isHighlight);
|
||||||
publications.find(
|
|
||||||
(p) => p.title.trim().toLowerCase() === title.trim().toLowerCase()
|
|
||||||
)
|
|
||||||
).filter((p): p is (typeof publications)[0] => Boolean(p));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-content py-8 sm:py-12 space-y-10 sm:space-y-14">
|
<div className="container-content py-8 sm:py-12 space-y-10 sm:space-y-14">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND
|
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
|
||||||
import { Semester } from "./types";
|
import { Semester } from "./types";
|
||||||
|
|
||||||
export const semesters: Semester[] = [
|
export const semesters: Semester[] = [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND
|
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
|
||||||
import { Member, Alumnus } from "./types";
|
import { Member, Alumnus } from "./types";
|
||||||
|
|
||||||
export const activeMembers: Member[] = [
|
export const activeMembers: Member[] = [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND
|
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
|
||||||
import { ResearchProject } from "./types";
|
import { ResearchProject } from "./types";
|
||||||
|
|
||||||
export const researchProjects: ResearchProject[] = [
|
export const researchProjects: ResearchProject[] = [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND
|
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
|
||||||
import { StandardsBody } from "./types";
|
import { StandardsBody } from "./types";
|
||||||
|
|
||||||
export const standardsBodies: StandardsBody[] = [
|
export const standardsBodies: StandardsBody[] = [
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface Publication {
|
|||||||
publishedAt: string; // ISO 8601 YYYY-MM or YYYY-MM-DD
|
publishedAt: string; // ISO 8601 YYYY-MM or YYYY-MM-DD
|
||||||
kci?: boolean;
|
kci?: boolean;
|
||||||
doi?: string;
|
doi?: string;
|
||||||
|
isHighlight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Patent {
|
export interface Patent {
|
||||||
|
|||||||
Reference in New Issue
Block a user