Compare commits

..

No commits in common. "0525dec9b999647f46f89b8a93f72c4a31f921bc" and "f1edf3218a0e84e061940bf210bbb1e362ccee56" have entirely different histories.

8 changed files with 139 additions and 122 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__/
*.py[cod]

View File

@ -1,5 +0,0 @@
__pycache__/
*.pyc
README.md
forum.db
.gitignore

1
backend/.gitignore vendored
View File

@ -1 +0,0 @@
forum.db

View File

@ -1,4 +1,4 @@
FROM python:alpine
FROM python:bookworm
EXPOSE 8000
@ -7,5 +7,5 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--log-level", "debug", "--host", "0.0.0.0"]
COPY forum.py .
CMD ["uvicorn", "forum:app", "--log-level", "debug", "--host", "0.0.0.0"]

BIN
backend/forum.db Normal file

Binary file not shown.

131
backend/forum.py Normal file
View File

@ -0,0 +1,131 @@
from fastapi import FastAPI
import models
from database import engine, SessionLocal
from pydantic import BaseModel
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
class Post:
"""Post is a single forum post or reply"""
id: int
thread_id: int
author: str
title: str
content: str
def __init__(self, id, thread_id, author, title, content):
self.id = id
self.thread_id = thread_id
self.author = author
self.title = title
self.content = content
class PostCreate(BaseModel):
"""Used when creating posts and replies"""
author: str = 'anon'
title: str
content: str
class Thread:
"""Thread is a collection of a post and its replies"""
id: int
author: str
title: str
closed: bool
def __init__(self, id, author, title, closed=False):
self.id = id
self.author = author
self.title = title
self.closed = closed
POSTS = []
THREADS = []
@app.get("/")
async def get_threads():
return THREADS
@app.post("/post")
async def create_post(data: PostCreate):
id = len(POSTS) + 1
author = data.author
title = data.title
content = data.content
# Create thread
thread = Thread(
id=id,
author=author,
title=title
)
THREADS.append(thread)
# Create post
post = Post(
id=id,
thread_id=id,
author=author,
title=title,
content=content
)
POSTS.append(post)
return post
@app.get("/{thread_id}")
async def get_thread(thread_id: int):
result = []
for post in POSTS:
if post.thread_id == thread_id:
result.append(post)
if len(result) == 0:
return {'error': 'could not find parent thread'}
return result
@app.post("/{thread_id}/post")
async def reply_to_post(thread_id: int, data: PostCreate):
parent = None
for thread in THREADS:
if thread.id == thread_id:
parent = thread
if parent is None:
return {'error': 'could not find parent thread'}
id = len(POSTS) + 1
author = data.author
title = data.title
content = data.content
# Create post
post = Post(
id=id,
thread_id=parent.id,
author=author,
title=title,
content=content
)
POSTS.append(post)
return post

View File

@ -1,108 +0,0 @@
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException, Path
from sqlalchemy.orm import Session
from pydantic import BaseModel, Field
from starlette import status
from database import engine, SessionLocal
from models import Base, Post, Thread
app = FastAPI()
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db_dependency = Annotated[Session, Depends(get_db)]
class PostCreate(BaseModel):
author: str = Field('anon')
title: str = Field('')
content: str = Field('')
@app.get('/', status_code=status.HTTP_200_OK)
async def get_posts(db: db_dependency):
return db.query(Post).all()
@app.post('/', status_code=status.HTTP_201_CREATED)
async def create_thread(db: db_dependency, data: PostCreate):
try:
# Create the post
post = Post(
author=data.author,
title=data.title,
content=data.content
)
db.add(post)
db.flush()
# Create the thread
thread = Thread(
author=post.author,
title=post.title,
content=post.content
)
db.add(thread)
db.flush()
# Update the Post with thread_id
post.thread_id = thread.id
db.commit()
return {
'id': post.id,
'author': post.author,
'title': post.title,
'content': post.content
}
except Exception as e:
db.rollback()
raise HTTPException(status_code=400, detail=str(e))
@app.get('/{thread_id}', status_code=status.HTTP_200_OK)
async def get_thread_by_id(db: db_dependency, thread_id: int = Path(gt=0)):
posts = db.query(Post).filter(Post.thread_id == thread_id).all()
if posts:
return posts
raise HTTPException(404, f'Could not find thread')
@app.post('/{thread_id}', status_code=status.HTTP_201_CREATED)
async def create_reply(db: db_dependency, data: PostCreate, thread_id: int = Path(gt=0)):
thread = db.query(Thread).filter(Thread.id == thread_id).first()
if thread:
post = Post(
thread_id=thread_id,
author=data.author,
title=data.title,
content=data.content
)
db.add(post)
db.commit()
return {
'id': post.id,
'author': post.author,
'title': post.title,
'content': post.content
}
raise HTTPException(status_code=404, detail='Could not find thread')
@app.get('/catalog', status_code=status.HTTP_200_OK)
async def get_catalog(db: db_dependency):
return db.query(Thread).all()

View File

@ -1,10 +1,8 @@
from sqlalchemy.orm import relationship
from database import Base
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
class Post(Base):
class Posts(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
@ -14,10 +12,11 @@ class Post(Base):
content = Column(String)
class Thread(Base):
class Threads(Base):
__tablename__ = 'threads'
id = Column(Integer, primary_key=True)
author = Column(String)
title = Column(String)
content = Column(String)
is_closed = Column(Boolean, default=False)