diff --git a/backend/main.py b/backend/main.py index 8af671e..f506166 100755 --- a/backend/main.py +++ b/backend/main.py @@ -802,3 +802,136 @@ def delete_notice_reply(reply_id: int, token=Depends(verify_token), conn=Depends cur.execute("DELETE FROM notice_replies WHERE id = %s", (reply_id,)) conn.commit() return {"ok": True} + +# ─── Homepage Cards ────────────────────────────────────────────────────────── +# 이 코드를 기존 main.py 맨 아래에 붙여넣으세요. +# init_db() 함수 안의 cur.execute("""...""") 블록에도 아래 테이블을 추가해야 합니다. +# (init_db 수정 방법은 아래 주석 참고) +# +# [init_db() 수정] 기존 CREATE TABLE ... 블록 마지막에 추가: +# +# CREATE TABLE IF NOT EXISTS homepage_cards ( +# id SERIAL PRIMARY KEY, +# title VARCHAR(100) NOT NULL, +# subtitle VARCHAR(200), +# description TEXT, +# url VARCHAR(500) NOT NULL, +# tag VARCHAR(20) DEFAULT 'LIVE', +# sort_order INTEGER DEFAULT 0, +# visible BOOLEAN DEFAULT TRUE, +# created_at TIMESTAMP DEFAULT NOW() +# ); +# +# ───────────────────────────────────────────────────────────────────────────── + +def init_homepage_cards_db(): + conn = psycopg2.connect(**DB_CONFIG) + cur = conn.cursor() + cur.execute(""" + CREATE TABLE IF NOT EXISTS homepage_cards ( + id SERIAL PRIMARY KEY, + title VARCHAR(100) NOT NULL, + subtitle VARCHAR(200), + description TEXT, + url VARCHAR(500) NOT NULL, + tag VARCHAR(20) DEFAULT 'LIVE', + sort_order INTEGER DEFAULT 0, + visible BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW() + ); + """) + # 기본 카드 초기 데이터 (최초 1회만) + cur.execute("SELECT COUNT(*) FROM homepage_cards") + count = cur.fetchone()[0] + if count == 0: + default_cards = [ + ("Web Portal", "내부 서비스 관리", "로그인 후 할당된 웹 서비스 목록을 확인하고 접속할 수 있는 통합 포털.", "/portal", "LIVE", 1), + ("King's Cup", "멀티플레이 술게임", "디스코드 화면공유로 친구들과 함께 즐기는 킹컵 카드 게임. 실시간 WebSocket 기반.", "/kingscup", "NEW", 2), + ("Gitea", "셀프호스티드 Git", "GitHub 대신 자체 서버에서 운영하는 Git 저장소. Container Registry 포함.", "https://gitea.cyanburu.com", "LIVE", 3), + ("ArgoCD", "GitOps 배포 엔진", "Gitea 저장소를 감시해 K8s 클러스터에 자동으로 배포하는 GitOps 도구.", "https://argo.cyanburu.com", "LIVE", 4), + ] + cur.executemany( + "INSERT INTO homepage_cards (title, subtitle, description, url, tag, sort_order) VALUES (%s,%s,%s,%s,%s,%s)", + default_cards + ) + conn.commit() + cur.close() + conn.close() + +@app.on_event("startup") +def startup_homepage_cards(): + import time + for _ in range(10): + try: + init_homepage_cards_db() + print("Homepage cards DB initialized") + break + except Exception as e: + print(f"Homepage cards DB not ready... {e}") + time.sleep(3) + +# ── Public API (인증 불필요 - 허브 홈페이지에서 호출) ────────────────────── +@app.get("/api/homepage/cards") +def get_homepage_cards(conn=Depends(get_db)): + """허브 홈페이지(cyanburu.com/)에서 카드 목록을 가져옵니다. 인증 불필요.""" + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute(""" + SELECT id, title, subtitle, description, url, tag, sort_order + FROM homepage_cards + WHERE visible = TRUE + ORDER BY sort_order ASC, id ASC + """) + return cur.fetchall() + +# ── Admin API (관리자 전용) ──────────────────────────────────────────────── +class HomepageCardCreate(BaseModel): + title: str + subtitle: Optional[str] = "" + description: Optional[str] = "" + url: str + tag: Optional[str] = "LIVE" + sort_order: Optional[int] = 0 + visible: Optional[bool] = True + +@app.get("/api/admin/homepage-cards") +def list_homepage_cards(token=Depends(require_admin), conn=Depends(get_db)): + """관리자: 전체 카드 목록 (비공개 포함)""" + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute("SELECT * FROM homepage_cards ORDER BY sort_order ASC, id ASC") + return cur.fetchall() + +@app.post("/api/admin/homepage-cards") +def create_homepage_card(data: HomepageCardCreate, token=Depends(require_admin), conn=Depends(get_db)): + """관리자: 카드 추가""" + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute( + """INSERT INTO homepage_cards (title, subtitle, description, url, tag, sort_order, visible) + VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING *""", + (data.title, data.subtitle, data.description, data.url, data.tag, data.sort_order, data.visible) + ) + conn.commit() + return cur.fetchone() + +@app.put("/api/admin/homepage-cards/{card_id}") +def update_homepage_card(card_id: int, data: HomepageCardCreate, token=Depends(require_admin), conn=Depends(get_db)): + """관리자: 카드 수정""" + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute( + """UPDATE homepage_cards + SET title=%s, subtitle=%s, description=%s, url=%s, tag=%s, sort_order=%s, visible=%s + WHERE id=%s RETURNING *""", + (data.title, data.subtitle, data.description, data.url, data.tag, data.sort_order, data.visible, card_id) + ) + result = cur.fetchone() + if not result: + raise HTTPException(status_code=404, detail="Card not found") + conn.commit() + return result + +@app.delete("/api/admin/homepage-cards/{card_id}") +def delete_homepage_card(card_id: int, token=Depends(require_admin), conn=Depends(get_db)): + """관리자: 카드 삭제""" + cur = conn.cursor() + cur.execute("DELETE FROM homepage_cards WHERE id=%s", (card_id,)) + conn.commit() + return {"ok": True} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 449a103..6fe5a1f 100755 --- a/frontend/index.html +++ b/frontend/index.html @@ -210,6 +210,7 @@ +
@@ -447,10 +448,60 @@ +
+ +
+ + + + + + + + + + + + + +
순서제목부제목URL태그공개관리
+
+
+
+