import os import sqlite3 from fastapi import FastAPI, HTTPException, Request, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.openapi.docs import get_swagger_ui_html from fastapi.staticfiles import StaticFiles from typing import List, Optional from pydantic import BaseModel DB_PATH = "sns_api.db" OPENAPI_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "openapi.yaml") app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None) # Allow CORS from everywhere app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) def init_db(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() # Posts table c.execute(""" CREATE TABLE IF NOT EXISTS posts ( id TEXT PRIMARY KEY, username TEXT NOT NULL, content TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL ) """) # Comments table c.execute(""" CREATE TABLE IF NOT EXISTS comments ( id TEXT PRIMARY KEY, postId TEXT NOT NULL, username TEXT NOT NULL, content TEXT NOT NULL, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL ) """) # Likes table c.execute(""" CREATE TABLE IF NOT EXISTS likes ( postId TEXT NOT NULL, username TEXT NOT NULL, PRIMARY KEY (postId, username) ) """) conn.commit() conn.close() @app.on_event("startup") def startup_event(): init_db() # Serve Swagger UI at default endpoint # Root endpoint redirects to /docs from fastapi.responses import RedirectResponse @app.get("/", include_in_schema=False) def root(): return RedirectResponse(url="/docs") @app.get("/docs", include_in_schema=False) def custom_swagger_ui_html(): return get_swagger_ui_html(openapi_url="/openapi.yaml", title="Simple Social Media Application API") # Serve the exact openapi.yaml at default endpoint @app.get("/openapi.yaml", include_in_schema=False) def serve_openapi_yaml(): return FileResponse(OPENAPI_PATH, media_type="application/x-yaml") # ... API endpoints will be implemented here, strictly matching openapi.yaml ... # --- Data Models --- from uuid import uuid4 from datetime import datetime class Like(BaseModel): username: str class Comment(BaseModel): id: str postId: str username: str content: str createdAt: str updatedAt: str class Post(BaseModel): id: str username: str content: str createdAt: str updatedAt: str comments: List[Comment] = [] likes: List[Like] = [] class CreatePostRequest(BaseModel): username: str content: str class UpdatePostRequest(BaseModel): username: str content: str class CreateCommentRequest(BaseModel): username: str content: str class UpdateCommentRequest(BaseModel): username: str content: str class LikeRequest(BaseModel): username: str class Error(BaseModel): message: str # --- Helper Functions --- def get_db(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def get_post(post_id): conn = get_db() post = conn.execute("SELECT * FROM posts WHERE id=?", (post_id,)).fetchone() if not post: conn.close() return None comments = conn.execute("SELECT * FROM comments WHERE postId=?", (post_id,)).fetchall() likes = conn.execute("SELECT username FROM likes WHERE postId=?", (post_id,)).fetchall() conn.close() return Post( id=post["id"], username=post["username"], content=post["content"], createdAt=post["createdAt"], updatedAt=post["updatedAt"], comments=[Comment(**dict(c)) for c in comments], likes=[Like(username=l["username"]) for l in likes], ) # --- Endpoints --- @app.get("/posts", response_model=List[Post], responses={500: {"model": Error}}) def list_posts(): try: conn = get_db() posts = conn.execute("SELECT * FROM posts").fetchall() result = [] for post in posts: result.append(get_post(post["id"])) conn.close() return result except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/posts", response_model=Post, status_code=201, responses={400: {"model": Error}, 500: {"model": Error}}) def create_post(req: CreatePostRequest): if not req.username or not req.content: raise HTTPException(status_code=400, detail="username and content required") post_id = str(uuid4()) now = datetime.utcnow().isoformat() conn = get_db() try: conn.execute( "INSERT INTO posts (id, username, content, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?)", (post_id, req.username, req.content, now, now) ) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return get_post(post_id) @app.get("/posts/{postId}", response_model=Post, responses={404: {"model": Error}, 500: {"model": Error}}) def get_single_post(postId: str): post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") return post @app.patch("/posts/{postId}", response_model=Post, responses={400: {"model": Error}, 404: {"model": Error}, 500: {"model": Error}}) def update_post(postId: str, req: UpdatePostRequest): post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") if not req.username or not req.content: raise HTTPException(status_code=400, detail="username and content required") now = datetime.utcnow().isoformat() conn = get_db() try: conn.execute( "UPDATE posts SET username=?, content=?, updatedAt=? WHERE id=?", (req.username, req.content, now, postId) ) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return get_post(postId) @app.delete("/posts/{postId}", status_code=204, responses={404: {"model": Error}, 500: {"model": Error}}) def delete_post(postId: str): post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") conn = get_db() try: conn.execute("DELETE FROM posts WHERE id=?", (postId,)) conn.execute("DELETE FROM comments WHERE postId=?", (postId,)) conn.execute("DELETE FROM likes WHERE postId=?", (postId,)) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return Response(status_code=204) @app.get("/posts/{postId}/comments", response_model=List[Comment], responses={404: {"model": Error}, 500: {"model": Error}}) def list_comments(postId: str): post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") return post.comments @app.post("/posts/{postId}/comments", response_model=Comment, status_code=201, responses={400: {"model": Error}, 404: {"model": Error}, 500: {"model": Error}}) def create_comment(postId: str, req: CreateCommentRequest): post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") if not req.username or not req.content: raise HTTPException(status_code=400, detail="username and content required") comment_id = str(uuid4()) now = datetime.utcnow().isoformat() conn = get_db() try: conn.execute( "INSERT INTO comments (id, postId, username, content, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)", (comment_id, postId, req.username, req.content, now, now) ) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return Comment(id=comment_id, postId=postId, username=req.username, content=req.content, createdAt=now, updatedAt=now) @app.get("/posts/{postId}/comments/{commentId}", response_model=Comment, responses={404: {"model": Error}, 500: {"model": Error}}) def get_comment(postId: str, commentId: str): conn = get_db() comment = conn.execute("SELECT * FROM comments WHERE id=? AND postId=?", (commentId, postId)).fetchone() conn.close() if not comment: raise HTTPException(status_code=404, detail="Comment not found") return Comment(**dict(comment)) @app.patch("/posts/{postId}/comments/{commentId}", response_model=Comment, responses={400: {"model": Error}, 404: {"model": Error}, 500: {"model": Error}}) def update_comment(postId: str, commentId: str, req: UpdateCommentRequest): conn = get_db() comment = conn.execute("SELECT * FROM comments WHERE id=? AND postId=?", (commentId, postId)).fetchone() if not comment: conn.close() raise HTTPException(status_code=404, detail="Comment not found") if not req.username or not req.content: conn.close() raise HTTPException(status_code=400, detail="username and content required") now = datetime.utcnow().isoformat() try: conn.execute( "UPDATE comments SET username=?, content=?, updatedAt=? WHERE id=? AND postId=?", (req.username, req.content, now, commentId, postId) ) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) comment = conn.execute("SELECT * FROM comments WHERE id=? AND postId=?", (commentId, postId)).fetchone() conn.close() return Comment(**dict(comment)) @app.delete("/posts/{postId}/comments/{commentId}", status_code=204, responses={404: {"model": Error}, 500: {"model": Error}}) def delete_comment(postId: str, commentId: str): conn = get_db() comment = conn.execute("SELECT * FROM comments WHERE id=? AND postId=?", (commentId, postId)).fetchone() if not comment: conn.close() raise HTTPException(status_code=404, detail="Comment not found") try: conn.execute("DELETE FROM comments WHERE id=? AND postId=?", (commentId, postId)) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return Response(status_code=204) @app.post("/posts/{postId}/likes", status_code=201, responses={400: {"model": Error}, 404: {"model": Error}, 500: {"model": Error}}) def like_post(postId: str, req: LikeRequest): post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") if not req.username: raise HTTPException(status_code=400, detail="username required") conn = get_db() try: conn.execute("INSERT OR IGNORE INTO likes (postId, username) VALUES (?, ?)", (postId, req.username)) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return Response(status_code=201) @app.delete("/posts/{postId}/likes", status_code=204, responses={404: {"model": Error}, 500: {"model": Error}}) def unlike_post(postId: str, req: Request): # username must be provided in body for DELETE body = req.json() if hasattr(req, 'json') else None username = None if body and "username" in body: username = body["username"] if not username: raise HTTPException(status_code=400, detail="username required") post = get_post(postId) if not post: raise HTTPException(status_code=404, detail="Post not found") conn = get_db() try: conn.execute("DELETE FROM likes WHERE postId=? AND username=?", (postId, username)) conn.commit() except Exception as e: conn.close() raise HTTPException(status_code=500, detail=str(e)) conn.close() return Response(status_code=204) # Run the app on port 8000 if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)