diff --git a/docker/app.sh b/docker/app.sh index 856b4c5..8b9afc1 100644 --- a/docker/app.sh +++ b/docker/app.sh @@ -1,8 +1,8 @@ #!/bin/bash -# alembic revision --autogenerate -m "post" +#alembic revision --autogenerate -m "add_title_to_post_table" alembic upgrade head cd src -gunicorn main:app --workers 5 --worker-class uvicorn.workers.UvicornWorker --bind=0.0.0.0:8000 \ No newline at end of file +gunicorn main:app --workers 1 --worker-class uvicorn.workers.UvicornWorker --bind=0.0.0.0:8000 diff --git a/migrations/versions/2fc416358aee_add_title_to_post_table.py b/migrations/versions/2fc416358aee_add_title_to_post_table.py new file mode 100644 index 0000000..6749c0a --- /dev/null +++ b/migrations/versions/2fc416358aee_add_title_to_post_table.py @@ -0,0 +1,29 @@ +"""add_title_to_post_table + +Revision ID: 2fc416358aee +Revises: 75599d686560 +Create Date: 2023-06-19 23:46:01.609132 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision = '2fc416358aee' +down_revision = '75599d686560' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('post', sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('post', 'title') + # ### end Alembic commands ### diff --git a/src/blog/models.py b/src/blog/models.py index 6508077..eeaafa4 100644 --- a/src/blog/models.py +++ b/src/blog/models.py @@ -33,6 +33,7 @@ class PostTagLink(SQLModel, table=True): class PostBase(SQLModel): + title: str text: str @@ -45,6 +46,19 @@ class Post(PostBase, table=True): tags: List["Tag"] = Relationship(back_populates="posts", link_model=PostTagLink) +class UserRead(UserBase): + posts: List[Post] + + +class PostRead(PostBase): + id: int + author_id: int + + +class PostCreate(PostBase): + author_id: int + + class TagBase(SQLModel): name: str @@ -66,3 +80,13 @@ class Comment(CommentBase, table=True): author: User | None = Relationship(back_populates="comments") post: Post | None = Relationship(back_populates="comments") + + +class CommentRead(CommentBase): + id: int + author_id: int + post_id: int + + +class CommentCreate(CommentBase): + author_id: int diff --git a/src/blog/posts.py b/src/blog/posts.py new file mode 100644 index 0000000..7a3e752 --- /dev/null +++ b/src/blog/posts.py @@ -0,0 +1,84 @@ +from typing import Annotated, List + +from fastapi import APIRouter, Request, Depends, Header, HTTPException, status +from fastapi.templating import Jinja2Templates +from sqlmodel import Session, select + +from blog.models import Post, PostRead, PostCreate, CommentRead, CommentCreate, Comment +from database import get_session +from enums import HeaderAccept + +router = APIRouter(prefix='/posts', tags=["Posts"]) + +templates = Jinja2Templates(directory="templates") + + +@router.get("/", response_model=List[PostRead]) +def get_posts( + request: Request, + session: Session = Depends(get_session), + accept: Annotated[str | None, Header()] = HeaderAccept.json): + results = session.exec(select(Post)).all() + if HeaderAccept.html in accept: + return templates.TemplateResponse( + "blog/posts.html", + { + "request": request, + "posts": list(map(lambda x: x.dict(), results)) + } + ) + else: + return results + + +@router.get("/{post_id}", response_model=PostRead) +def get_post( + post_id: int, + request: Request, + session: Session = Depends(get_session), + accept: Annotated[str, Header()] = HeaderAccept.json): + post = session.get(Post, post_id) + if not post: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found") + if HeaderAccept.html in accept: + return templates.TemplateResponse( + "blog/post.html", + { + "request": request, + "post": post.dict(), + "author": post.author.username, + "comments": list(map(lambda x: dict(**x.dict(), author=x.author.username), post.comments)) + } + ) + else: + return post + + +@router.post("/", response_model=PostRead) +def create_post(post: PostCreate, session: Session = Depends(get_session)): + db_post = Post.from_orm(post) + session.add(db_post) + session.commit() + session.refresh(db_post) + return db_post + + +@router.get("/{post_id}/comments", response_model=List[CommentRead], tags=['Comments']) +def get_comments( + post_id: int, + session: Session = Depends(get_session)): + results = session.exec(select(Comment).where(Comment.post_id == post_id)).all() + return results + + +@router.post("/{post_id}/comments", response_model=CommentRead, tags=['Comments']) +def add_comment( + post_id: int, + comment: CommentCreate, + session: Session = Depends(get_session)): + db_comment = Comment.from_orm(comment) + db_comment.post_id = post_id + session.add(db_comment) + session.commit() + session.refresh(db_comment) + return db_comment diff --git a/src/blog/router.py b/src/blog/router.py index 98fe2a0..7560399 100644 --- a/src/blog/router.py +++ b/src/blog/router.py @@ -1,16 +1,37 @@ -from fastapi import APIRouter, Request +from typing import Annotated + +from fastapi import APIRouter, Request, Depends, Header, HTTPException, status from fastapi.templating import Jinja2Templates +from sqlmodel import Session + +from blog.models import UserRead, User +from blog.posts import router as posts_router +from database import get_session +from enums import HeaderAccept router = APIRouter() +router.include_router(router=posts_router) templates = Jinja2Templates(directory="templates") -@router.get('/') -def index(request: Request): - return templates.TemplateResponse( - "index.html", - { - "request": request - } - ) +@router.get("/users/{user_id}", response_model=UserRead, tags=['Users']) +def get_user( + user_id: int, + request: Request, + session: Session = Depends(get_session), + accept: Annotated[str, Header()] = HeaderAccept.json): + user = session.get(User, user_id) + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found") + if HeaderAccept.html in accept: + return templates.TemplateResponse( + "blog/user.html", + { + "request": request, + "user": user.dict(), + "posts": list(map(lambda x: x.dict(), user.posts)) + } + ) + else: + return user diff --git a/src/routes.py b/src/routes.py index f5e5934..b18e09c 100644 --- a/src/routes.py +++ b/src/routes.py @@ -5,6 +5,6 @@ from products.router import router as products_router from blog.router import router as blog_router main_router = APIRouter() -main_router.include_router(router=users_router) -main_router.include_router(router=products_router) +# main_router.include_router(router=users_router) +# main_router.include_router(router=products_router) main_router.include_router(router=blog_router) diff --git a/src/templates/blog/post.html b/src/templates/blog/post.html new file mode 100644 index 0000000..05c7717 --- /dev/null +++ b/src/templates/blog/post.html @@ -0,0 +1,75 @@ +{% extends 'base.html' %} + +{% block title %} {{ post.title }} {% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} + + +
+

{{post.title}}

+

Author: {{ author }}

+
{{ post.text }}
+
+ +
+

Comments

+
+
+
+ + + +
+
+
+ + {% for comment in comments | reverse %} +
+

Author: {{ comment.author }}

+
{{ comment.text }}
+
+ {% endfor %} +
+{% endblock %} diff --git a/src/templates/blog/posts.html b/src/templates/blog/posts.html new file mode 100644 index 0000000..159c1b8 --- /dev/null +++ b/src/templates/blog/posts.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} + +{% block title %} Posts {% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +{% for post in posts %} +
+

{{ post.title }}

+
{{ post.text }}
+
+{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/templates/blog/user.html b/src/templates/blog/user.html new file mode 100644 index 0000000..76b9e49 --- /dev/null +++ b/src/templates/blog/user.html @@ -0,0 +1,44 @@ +{% extends 'base.html' %} + +{% block title %} {{user.username}}{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+

{{user.username}}

+ {% if user.full_name %} +
Full Name: {{user.full_name}}
+ {% endif %} + + {% if user.email %} +
Email: {{user.email}}
+ {% endif %} +
+ +
+

Posts

+ {% for post in posts %} +
+ +
{{post.title}}
+
+
{{ post.text }}
+
+ {% endfor %} +
+ +{% endblock %}