From 68e9fc0a3260b3e7509f995ef51684fa51bf25b4 Mon Sep 17 00:00:00 2001 From: qorgh529 Date: Fri, 10 Apr 2026 19:58:43 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=20=ED=83=AD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/main.py | 102 +++++++++++++++++++++++++++++ frontend/index.html | 154 ++++++++++++++++++++++++++++++++++++++++++++ frontend/nginx.conf | 18 +++++- 3 files changed, 273 insertions(+), 1 deletion(-) diff --git a/backend/main.py b/backend/main.py index 97160a6..a0fbce8 100755 --- a/backend/main.py +++ b/backend/main.py @@ -501,3 +501,105 @@ def delete_reply(reply_id: int, token=Depends(verify_token), conn=Depends(get_db cur.execute("DELETE FROM board_replies WHERE id = %s", (reply_id,)) conn.commit() return {"ok": True} + +# ─── 공지사항 ────────────────────────────────────────────── +class NoticeCreate(BaseModel): + title: str + content: str + +class NoticeReplyCreate(BaseModel): + content: str + +def init_notice_db(): + conn = psycopg2.connect(**DB_CONFIG) + cur = conn.cursor() + cur.execute(""" + CREATE TABLE IF NOT EXISTS notice_posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(300) NOT NULL, + content TEXT NOT NULL, + author_id INTEGER REFERENCES users(id) ON DELETE SET NULL, + author_name VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT NOW() + ); + CREATE TABLE IF NOT EXISTS notice_replies ( + id SERIAL PRIMARY KEY, + post_id INTEGER REFERENCES notice_posts(id) ON DELETE CASCADE, + content TEXT NOT NULL, + author_id INTEGER REFERENCES users(id) ON DELETE SET NULL, + author_name VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT NOW() + ); + """) + conn.commit() + cur.close() + conn.close() + +@app.on_event("startup") +def startup_notice(): + import time + for _ in range(10): + try: + init_notice_db() + print("Notice DB initialized") + break + except Exception as e: + print(f"Notice DB not ready... {e}") + time.sleep(3) + +@app.get("/api/notice/posts") +def list_notice_posts(token=Depends(verify_token), conn=Depends(get_db)): + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute("SELECT id, title, author_name, created_at FROM notice_posts ORDER BY created_at DESC") + return cur.fetchall() + +@app.post("/api/notice/posts") +def create_notice_post(data: NoticeCreate, token=Depends(require_admin), conn=Depends(get_db)): + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute( + "INSERT INTO notice_posts (title, content, author_id, author_name) VALUES (%s, %s, %s, %s) RETURNING *", + (data.title, data.content, int(token["sub"]), token["username"]) + ) + conn.commit() + return cur.fetchone() + +@app.get("/api/notice/posts/{post_id}") +def get_notice_post(post_id: int, token=Depends(verify_token), conn=Depends(get_db)): + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute("SELECT * FROM notice_posts WHERE id = %s", (post_id,)) + post = cur.fetchone() + if not post: + raise HTTPException(status_code=404, detail="Post not found") + cur.execute("SELECT * FROM notice_replies WHERE post_id = %s ORDER BY created_at ASC", (post_id,)) + replies = cur.fetchall() + return {"post": post, "replies": replies} + +@app.delete("/api/notice/posts/{post_id}") +def delete_notice_post(post_id: int, token=Depends(require_admin), conn=Depends(get_db)): + cur = conn.cursor() + cur.execute("DELETE FROM notice_posts WHERE id = %s", (post_id,)) + conn.commit() + return {"ok": True} + +@app.post("/api/notice/posts/{post_id}/replies") +def create_notice_reply(post_id: int, data: NoticeReplyCreate, token=Depends(verify_token), conn=Depends(get_db)): + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute( + "INSERT INTO notice_replies (post_id, content, author_id, author_name) VALUES (%s, %s, %s, %s) RETURNING *", + (post_id, data.content, int(token["sub"]), token["username"]) + ) + conn.commit() + return cur.fetchone() + +@app.delete("/api/notice/replies/{reply_id}") +def delete_notice_reply(reply_id: int, token=Depends(verify_token), conn=Depends(get_db)): + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute("SELECT * FROM notice_replies WHERE id = %s", (reply_id,)) + reply = cur.fetchone() + if not reply: + raise HTTPException(status_code=404, detail="Reply not found") + if not token.get("is_admin") and reply["author_id"] != int(token["sub"]): + raise HTTPException(status_code=403, detail="Permission denied") + cur.execute("DELETE FROM notice_replies WHERE id = %s", (reply_id,)) + conn.commit() + return {"ok": True} diff --git a/frontend/index.html b/frontend/index.html index bab8f5e..7182acc 100755 --- a/frontend/index.html +++ b/frontend/index.html @@ -205,6 +205,7 @@