Compare commits

...

10 Commits

Author SHA1 Message Date
0525dec9b9 use global .gitignore 2024-04-05 19:47:57 -04:00
4838bcfe59 use global .gitignore 2024-04-05 19:47:43 -04:00
e4f0105a6e consolidate database transactions when creating new thread 2024-04-05 19:41:57 -04:00
b3feb1b7a0 use alpine instead of bookworm 2024-04-05 19:32:59 -04:00
e1ab344279 add /catalog route 2024-04-05 19:28:27 -04:00
5fd50065cb update Dockerfile 2024-04-05 19:21:39 -04:00
05104667c7 remove unused import 2024-04-05 19:07:23 -04:00
98439fd1e7 remove forum.py 2024-04-05 19:05:28 -04:00
4a1dd775eb add database support 2024-04-05 19:01:15 -04:00
528c474dc3 update .gitignore 2024-04-05 19:01:00 -04:00
8 changed files with 122 additions and 139 deletions

1
.gitignore vendored
View File

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

5
backend/.dockerignore Normal file
View File

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

1
backend/.gitignore vendored Normal file
View File

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

View File

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

Binary file not shown.

View File

@ -1,131 +0,0 @@
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

108
backend/main.py Normal file
View File

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