feat: ability to add and view blogs for a user

This commit is contained in:
2025-06-24 18:54:48 -04:00
parent 07c0977aa7
commit 7da38ddd8c
26 changed files with 1553 additions and 142 deletions
+60 -11
View File
@@ -1,26 +1,75 @@
from sqlalchemy.orm import Session
from app import models, schemas
from app.utils import hash_password, verify_password
from typing import List, Optional
def get_item(db: Session, item_id: int):
return db.query(models.Item).filter(models.Item.id == item_id).first()
def get_blog(db: Session, blog_id: int) -> Optional[models.Blog]:
return db.query(models.Blog).filter(models.Blog.id == blog_id).first()
def get_items(db: Session, skip: int = 0, limit: int = 10):
return db.query(models.Item).offset(skip).limit(limit).all()
def get_blogs(
db: Session,
skip: int = 0,
limit: int = 10,
author_id: Optional[int] = None,
visibility: Optional[str] = None,
) -> List[models.Blog]:
q = db.query(models.Blog)
if author_id is not None:
q = q.filter(models.Blog.author_id == author_id)
if visibility is not None:
q = q.filter(models.Blog.visibility == visibility)
return q.offset(skip).limit(limit).all()
def create_item(db: Session, item: schemas.Item):
db_item = models.Item(**item.model_dump())
db.add(db_item)
def create_blog(db: Session, blog_in: schemas.BlogCreate) -> models.Blog:
db_blog = models.Blog(**blog_in.model_dump())
db.add(db_blog)
db.commit()
db.refresh(db_item)
return db_item
db.refresh(db_blog)
return db_blog
def delete_item(db: Session, item_id: int):
item = db.query(models.Item).filter(models.Item.id == item_id).first()
def update_blog(
db: Session, blog_id: int, blog_in: schemas.BlogUpdate
) -> Optional[models.Blog]:
db_blog = get_blog(db, blog_id)
if not db_blog:
return None
update_data = blog_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(db_blog, field, value)
db.commit()
db.refresh(db_blog)
return db_blog
def delete_blog(db: Session, blog_id: int) -> Optional[models.Blog]:
db_blog = get_blog(db, blog_id)
if not db_blog:
return None
db.delete(db_blog)
db.commit()
return db_blog
def increment_view_count(db: Session, blog_id: int) -> None:
db.query(models.Blog).filter(models.Blog.id == blog_id).update(
{"view_count": models.Blog.view_count + 1}
)
db.commit()
def add_like(db: Session, blog_id: int) -> None:
db.query(models.Blog).filter(models.Blog.id == blog_id).update(
{"like_count": models.Blog.like_count + 1}
)
db.commit()
def delete_blogs(db: Session, item_id: int):
item = db.query(models.Blog).filter(models.Blog.id == item_id).first()
if item:
db.delete(item)
db.commit()
+51 -21
View File
@@ -2,6 +2,7 @@ from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from typing import Optional
from app.utils import create_access_token
from . import schemas, crud
@@ -34,35 +35,64 @@ def health_check():
return {"Health": "Super Healthy!"}
@app.post("/items/", response_model=schemas.Item)
def create_item(item: schemas.Item, db: Session = Depends(get_db)):
return crud.create_item(db, item)
@app.post("/blogs/", response_model=schemas.Blog)
def create_blog(
blog: schemas.BlogCreate,
db: Session = Depends(get_db),
):
return crud.create_blog(db, blog)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
return crud.get_items(db, skip, limit)
@app.get("/blogs/", response_model=list[schemas.Blog])
def read_blogs(
skip: int = 0,
limit: int = 10,
author_id: Optional[int] = None,
db: Session = Depends(get_db),
):
return crud.get_blogs(
db,
skip=skip,
limit=limit,
author_id=author_id,
)
@app.get("/items/{item_id}", response_model=schemas.Item)
def read_item(item_id: int, db: Session = Depends(get_db)):
db_item = crud.get_item(db, item_id)
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
@app.get("/blogs/{blog_id}", response_model=schemas.Blog)
def read_blog(
blog_id: int,
db: Session = Depends(get_db),
):
db_blog = crud.get_blog(db, blog_id)
if not db_blog:
raise HTTPException(status_code=404, detail="Blog not found")
return db_blog
@app.delete("/items/{item_id}", response_model=schemas.Item)
def delete_item(item_id: int, db: Session = Depends(get_db)):
item = crud.delete_item(db, item_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item
@app.put("/blogs/{blog_id}", response_model=schemas.Blog)
def update_blog(
blog_id: int,
blog_in: schemas.BlogUpdate,
db: Session = Depends(get_db),
):
updated = crud.update_blog(db, blog_id, blog_in)
if not updated:
raise HTTPException(status_code=404, detail="Blog not found")
return updated
@app.delete("/blogs/{blog_id}", response_model=schemas.Blog)
def delete_blog(
blog_id: int,
db: Session = Depends(get_db),
):
deleted = crud.delete_blog(db, blog_id)
if not deleted:
raise HTTPException(status_code=404, detail="Blog not found")
return deleted
# Users
@app.post("/login", response_model=schemas.Token)
def user_login(
form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)
@@ -75,7 +105,7 @@ def user_login(
headers={"WWW-Authenticate": "Bearer"},
)
access_token = create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}
return {"access_token": access_token, "token_type": "bearer", "user_id": user.id}
@app.post("/register", response_model=schemas.UserOut)
+30 -5
View File
@@ -1,14 +1,39 @@
from sqlalchemy import JSON, Boolean, Column, Integer, String
from sqlalchemy import (
JSON,
Boolean,
Column,
DateTime,
Integer,
String,
func,
)
from .database import Base
class Item(Base):
__tablename__ = "items"
class Blog(Base):
__tablename__ = "blogs"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
title = Column(String, index=True)
author_id = Column(Integer, nullable=False, index=True)
description = Column(String, nullable=True)
body = Column(JSON, nullable=False)
body = Column(String, nullable=False)
created_at = Column(
DateTime(timezone=True), server_default=func.now(), nullable=False
)
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
published_at = Column(DateTime(timezone=True), nullable=True)
# Document Meta Data
word_count = Column(Integer, nullable=True)
version = Column(Integer, nullable=True)
read_time = Column(Integer, nullable=True)
language = Column(String, nullable=True)
tags = Column(JSON, nullable=True)
# User Meta Data
view_count = Column(Integer, default=0, nullable=False)
like_count = Column(Integer, default=0, nullable=False)
class User(Base):
+48 -8
View File
@@ -1,17 +1,56 @@
from pydantic import BaseModel, EmailStr
from typing import Optional
from pydantic import AwareDatetime, BaseModel, EmailStr
# DB Schemas
class ItemBase(BaseModel):
name: str
description: str | None = None
class BlogBase(BaseModel):
title: str
author_id: int
description: Optional[str] = None
body: str
created_at: AwareDatetime
updated_at: AwareDatetime
published_at: AwareDatetime
# Document meta
word_count: Optional[int] = None
version: Optional[int] = None
read_time: Optional[int] = None
language: Optional[str] = None
tags: Optional[list[str]] = None
class Item(ItemBase):
class BlogCreate(BlogBase):
"""All fields required to create a new blog post."""
pass
class BlogUpdate(BaseModel):
"""All fields are optional for partial updates."""
title: Optional[str] = None
description: Optional[str] = None
body: Optional[str] = None
created_at: Optional[str] = None
updated_at: Optional[str] = None
published_at: Optional[str] = None
word_count: Optional[int] = None
version: Optional[int] = None
read_time: Optional[int] = None
language: Optional[str] = None
tags: Optional[list[str]] = None
view_count: Optional[int] = None
like_count: Optional[int] = None
class Blog(BlogBase):
"""Whats returned in responses."""
id: int
view_count: int
like_count: int
class Config:
from_attributes = True
@@ -41,6 +80,7 @@ class UserOut(UserBase):
class Token(BaseModel):
access_token: str
token_type: str
user_id: int
class TokenData(BaseModel):