From 11add1929b7b381d6324f42b7ada0148239a1484 Mon Sep 17 00:00:00 2001 From: Idris Abdurrahman Date: Fri, 21 Mar 2025 09:48:28 +0100 Subject: [PATCH] Update generated backend for blog_app with entities: posts, comments, tags, user --- app/api/core/dependencies/dependencies.py | 8 +++ app/api/core/middleware/activity_tracker.py | 12 +++++ app/api/db/database.py | 7 +++ app/api/v1/models/comments.py | 28 ++++++++++ app/api/v1/models/posts.py | 31 +++++++++++ app/api/v1/models/tags.py | 27 ++++++++++ app/api/v1/models/user.py | 30 +++++++++++ app/api/v1/routes/__init__.py | 2 + app/api/v1/routes/comments.py | 55 +++++++++++++++++++ app/api/v1/routes/posts.py | 57 ++++++++++++++++++++ app/api/v1/routes/tags.py | 59 ++++++++++++++++++++ app/api/v1/routes/user.py | 60 +++++++++++++++++++++ app/api/v1/schemas/comments.py | 38 +++++++++++++ app/api/v1/schemas/posts.py | 39 ++++++++++++++ app/api/v1/schemas/tags.py | 34 ++++++++++++ app/api/v1/schemas/user.py | 35 ++++++++++++ main.py | 15 +++--- requirements.txt | 5 +- 18 files changed, 532 insertions(+), 10 deletions(-) create mode 100644 app/api/core/dependencies/dependencies.py create mode 100644 app/api/core/middleware/activity_tracker.py create mode 100644 app/api/db/database.py create mode 100644 app/api/v1/models/comments.py create mode 100644 app/api/v1/models/posts.py create mode 100644 app/api/v1/models/tags.py create mode 100644 app/api/v1/models/user.py create mode 100644 app/api/v1/routes/comments.py create mode 100644 app/api/v1/routes/posts.py create mode 100644 app/api/v1/routes/tags.py create mode 100644 app/api/v1/routes/user.py create mode 100644 app/api/v1/schemas/comments.py create mode 100644 app/api/v1/schemas/posts.py create mode 100644 app/api/v1/schemas/tags.py create mode 100644 app/api/v1/schemas/user.py diff --git a/app/api/core/dependencies/dependencies.py b/app/api/core/dependencies/dependencies.py new file mode 100644 index 0000000..5383c0b --- /dev/null +++ b/app/api/core/dependencies/dependencies.py @@ -0,0 +1,8 @@ +from sqlalchemy.orm import Session +from app.api.db.database import SessionLocal +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/app/api/core/middleware/activity_tracker.py b/app/api/core/middleware/activity_tracker.py new file mode 100644 index 0000000..d4996e2 --- /dev/null +++ b/app/api/core/middleware/activity_tracker.py @@ -0,0 +1,12 @@ +import time +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.requests import Request +from starlette.responses import Response +from loguru import logger +class ActivityTrackerMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + start_time = time.time() + response = await call_next(request) + process_time = time.time() - start_time + logger.info(f'{request.method} {request.url} - Process Time: {process_time:.6f} seconds') + return response diff --git a/app/api/db/database.py b/app/api/db/database.py new file mode 100644 index 0000000..f327867 --- /dev/null +++ b/app/api/db/database.py @@ -0,0 +1,7 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +SQLALCHEMY_DATABASE_URL = 'sqlite:///./blog_app.db' +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={'check_same_thread': False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() diff --git a/app/api/v1/models/comments.py b/app/api/v1/models/comments.py new file mode 100644 index 0000000..2d6208a --- /dev/null +++ b/app/api/v1/models/comments.py @@ -0,0 +1,28 @@ +Here's the `comments.py` file for the `app/api/v1/models/` directory of the `blog_app_svkgt` FastAPI backend, defining a SQLAlchemy model for comments: + +from sqlalchemy import Column, ForeignKey, Integer, String, Text +from sqlalchemy.orm import relationship + +from app.db import Base + +class Comment(Base): + __tablename__ = "comments" + + id = Column(Integer, primary_key=True, index=True) + content = Column(Text, nullable=False) + author = Column(String, nullable=False) + post_id = Column(Integer, ForeignKey("posts.id"), nullable=False) + + post = relationship("Post", back_populates="comments") + + def __repr__(self): + return f"Comment(id={self.id}, content='{self.content[:20]}...', author='{self.author}', post_id={self.post_id})" + +This code defines a `Comment` model that inherits from the `Base` class provided by SQLAlchemy. The `Comment` model has the following fields: + + + + +This model assumes that there is a `Post` model defined elsewhere in the project, which has a corresponding one-to-many relationship with the `Comment` model (i.e., one post can have multiple comments). + +Make sure to import the necessary modules (`sqlalchemy` and `app.db`) at the beginning of the file, and update the import paths if necessary to match your project structure. \ No newline at end of file diff --git a/app/api/v1/models/posts.py b/app/api/v1/models/posts.py new file mode 100644 index 0000000..7c142e1 --- /dev/null +++ b/app/api/v1/models/posts.py @@ -0,0 +1,31 @@ +Here's the `posts.py` file for the `blog_app_svkgt` FastAPI backend, defining a SQLAlchemy model for posts: + + +from sqlalchemy import Column, Integer, String, Text, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.sql.sqltypes import TIMESTAMP +from sqlalchemy.sql import func + +from app.db import Base + +class Post(Base): + __tablename__ = "posts" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String, nullable=False) + content = Column(Text, nullable=False) + created_at = Column(TIMESTAMP, server_default=func.now()) + updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) + user_id = Column(Integer, ForeignKey("users.id")) + + user = relationship("User", back_populates="posts") + + def __repr__(self): + return f"Post(id={self.id}, title='{self.title}', content='{self.content[:20]}...')" + +Explanation: + +5. The `title` column is a `String` column marked as `nullable=False`, meaning it cannot be null. +6. The `content` column is a `Text` column marked as `nullable=False`, allowing for longer text content. +7. The `created_at` column is a `TIMESTAMP` column with a default value set to the current server time using `func.now()`. +8. The `updated_at` column is a `TIMESTAMP` column with a default value set to the current server time using `func.now()`, and it will be updated with the current time whenever the row is updated using `onupdate=func.now()`. \ No newline at end of file diff --git a/app/api/v1/models/tags.py b/app/api/v1/models/tags.py new file mode 100644 index 0000000..b104146 --- /dev/null +++ b/app/api/v1/models/tags.py @@ -0,0 +1,27 @@ +Here's the `tags.py` file with a SQLAlchemy model for tags: + + +from sqlalchemy import Column, Integer, String +from app.db.base_class import Base + +class Tag(Base): + __tablename__ = "tags" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=True, index=True) + description = Column(String, nullable=True) + + def __repr__(self): + return f"Tag(id={self.id}, name='{self.name}', description='{self.description}')" + +Explanation: + +1. We import the necessary modules from SQLAlchemy: `Column`, `Integer`, and `String`. +2. We also import the `Base` class from `app.db.base_class`, which is a common pattern in FastAPI projects using SQLAlchemy. This `Base` class is typically defined in a separate file (`base_class.py`) and serves as the base class for all SQLAlchemy models. +5. We define three columns for the `Tag` model: + - `id`: An integer primary key and index column for the tag's unique identifier. + - `name`: A string column for the tag's name, which must be unique and indexed. + - `description`: An optional string column for the tag's description, which can be `None`. + + +Note: Make sure to place this `tags.py` file in the `app/api/v1/models/` directory, following the specified project structure. \ No newline at end of file diff --git a/app/api/v1/models/user.py b/app/api/v1/models/user.py new file mode 100644 index 0000000..a66c703 --- /dev/null +++ b/app/api/v1/models/user.py @@ -0,0 +1,30 @@ +Here's the `user.py` file for the `app/api/v1/models/` directory, defining a SQLAlchemy model for the User entity in the `blog_app_svkgt` FastAPI backend: + + +from sqlalchemy import Column, Integer, String, Boolean +from app.db.base_class import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String, unique=True, index=True, nullable=False) + hashed_password = Column(String, nullable=False) + is_active = Column(Boolean, default=True) + is_superuser = Column(Boolean, default=False) + full_name = Column(String, index=True) + + def __repr__(self): + return f"User(id={self.id}, email={self.email}, full_name={self.full_name})" + +Explanation: + +3. The model has the following columns: + - `id`: An integer primary key column for uniquely identifying each user. + - `email`: A string column for storing the user's email address. It is set as unique and indexed for efficient lookups. + - `hashed_password`: A string column for storing the hashed password of the user. + - `is_active`: A boolean column indicating whether the user account is active or not. By default, it is set to `True`. + - `is_superuser`: A boolean column indicating whether the user has superuser privileges or not. By default, it is set to `False`. + - `full_name`: A string column for storing the user's full name. It is indexed for efficient lookups. + +Note: This model assumes that you have a `Base` class defined in `app.db.base_class` module, which is typically created using the SQLAlchemy declarative base. You may need to adjust the import statement and the `Base` class according to your project's structure and database configuration. \ No newline at end of file diff --git a/app/api/v1/routes/__init__.py b/app/api/v1/routes/__init__.py index e69de29..da060ab 100644 --- a/app/api/v1/routes/__init__.py +++ b/app/api/v1/routes/__init__.py @@ -0,0 +1,2 @@ +from fastapi import APIRouter +router = APIRouter() diff --git a/app/api/v1/routes/comments.py b/app/api/v1/routes/comments.py new file mode 100644 index 0000000..3faf16b --- /dev/null +++ b/app/api/v1/routes/comments.py @@ -0,0 +1,55 @@ +from typing import List +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from app.db import get_db +from app.models import Comment +from app.schemas import CommentCreate, CommentResponse + +router = APIRouter( + prefix="/comments", + tags=["Comments"], + +@router.post("/", response_model=CommentResponse, status_code=status.HTTP_201_CREATED) +def create_comment(comment: CommentCreate, db: Session = Depends(get_db)): + new_comment = Comment(**comment.dict()) + db.add(new_comment) + db.commit() + db.refresh(new_comment) + return new_comment + +@router.get("/", response_model=List[CommentResponse]) +def get_all_comments(db: Session = Depends(get_db)): + comments = db.query(Comment).all() + return comments + +@router.get("/{comment_id}", response_model=CommentResponse) +def get_comment(comment_id: int, db: Session = Depends(get_db)): + comment = db.query(Comment).filter(Comment.id == comment_id).first() + if not comment: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found") + return comment + +@router.put("/{comment_id}", response_model=CommentResponse) +def update_comment(comment_id: int, comment: CommentCreate, db: Session = Depends(get_db)): + db_comment = db.query(Comment).filter(Comment.id == comment_id).first() + if not db_comment: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found") + db_comment.text = comment.text + db.commit() + db.refresh(db_comment) + return db_comment + +@router.delete("/{comment_id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_comment(comment_id: int, db: Session = Depends(get_db)): + comment = db.query(Comment).filter(Comment.id == comment_id).first() + if not comment: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found") + db.delete(comment) + db.commit() + return + +This code defines a `router` object with the prefix `/comments` and the tag `"Comments"`. The router contains the following endpoints: + + + +Note: You'll need to define the `Comment`, `CommentCreate`, and `CommentResponse` models and schemas in the appropriate files (`models.py` and `schemas.py`) for this code to work correctly. \ No newline at end of file diff --git a/app/api/v1/routes/posts.py b/app/api/v1/routes/posts.py new file mode 100644 index 0000000..d71ac52 --- /dev/null +++ b/app/api/v1/routes/posts.py @@ -0,0 +1,57 @@ +from typing import List +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from app.db import get_db +from app.models import Post +from app.schemas import PostCreate, PostResponse + +router = APIRouter( + prefix="/posts", + tags=["Posts"], + +@router.post("/", response_model=PostResponse, status_code=status.HTTP_201_CREATED) +def create_post(post: PostCreate, db: Session = Depends(get_db)): + new_post = Post(**post.dict()) + db.add(new_post) + db.commit() + db.refresh(new_post) + return new_post + +@router.get("/", response_model=List[PostResponse]) +def get_all_posts(db: Session = Depends(get_db)): + posts = db.query(Post).all() + return posts + +@router.get("/{post_id}", response_model=PostResponse) +def get_post(post_id: int, db: Session = Depends(get_db)): + post = db.query(Post).filter(Post.id == post_id).first() + if not post: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found") + return post + +@router.put("/{post_id}", response_model=PostResponse) +def update_post(post_id: int, post: PostCreate, db: Session = Depends(get_db)): + db_post = db.query(Post).filter(Post.id == post_id).first() + if not db_post: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found") + update_data = post.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(db_post, key, value) + db.commit() + db.refresh(db_post) + return db_post + +@router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_post(post_id: int, db: Session = Depends(get_db)): + post = db.query(Post).filter(Post.id == post_id).first() + if not post: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found") + db.delete(post) + db.commit() + return + +This file defines the following endpoints: + + + +Note that you'll need to define the `Post` model and `PostCreate` and `PostResponse` schemas in separate files (`models.py` and `schemas.py`, respectively) for this code to work. \ No newline at end of file diff --git a/app/api/v1/routes/tags.py b/app/api/v1/routes/tags.py new file mode 100644 index 0000000..8dc6d67 --- /dev/null +++ b/app/api/v1/routes/tags.py @@ -0,0 +1,59 @@ +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.db import get_db +from app import models, schemas + +router = APIRouter( + prefix="/tags", + tags=["Tags"], + +@router.get("/", response_model=List[schemas.Tag]) +def get_tags(db: Session = Depends(get_db)): + tags = db.query(models.Tag).all() + return tags + +@router.get("/{tag_id}", response_model=schemas.Tag) +def get_tag(tag_id: int, db: Session = Depends(get_db)): + tag = db.query(models.Tag).filter(models.Tag.id == tag_id).first() + if not tag: + raise HTTPException(status_code=404, detail="Tag not found") + return tag + +@router.post("/", response_model=schemas.Tag) +def create_tag(tag: schemas.TagCreate, db: Session = Depends(get_db)): + new_tag = models.Tag(name=tag.name) + db.add(new_tag) + db.commit() + db.refresh(new_tag) + return new_tag + +@router.put("/{tag_id}", response_model=schemas.Tag) +def update_tag(tag_id: int, tag: schemas.TagUpdate, db: Session = Depends(get_db)): + db_tag = db.query(models.Tag).filter(models.Tag.id == tag_id).first() + if not db_tag: + raise HTTPException(status_code=404, detail="Tag not found") + + update_data = tag.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(db_tag, key, value) + + db.commit() + db.refresh(db_tag) + return db_tag + +@router.delete("/{tag_id}", status_code=204) +def delete_tag(tag_id: int, db: Session = Depends(get_db)): + tag = db.query(models.Tag).filter(models.Tag.id == tag_id).first() + if not tag: + raise HTTPException(status_code=404, detail="Tag not found") + + db.delete(tag) + db.commit() + return None + +This code defines a set of CRUD (Create, Read, Update, Delete) endpoints for managing tags in the `blog_app_svkgt` FastAPI application. Here's a breakdown of the endpoints: + + +The code uses SQLAlchemy models and Pydantic schemas (not shown in the provided code) to interact with the database and validate the request data, respectively. The `get_db` dependency is used to obtain a database session for each request. \ No newline at end of file diff --git a/app/api/v1/routes/user.py b/app/api/v1/routes/user.py new file mode 100644 index 0000000..00a0ad2 --- /dev/null +++ b/app/api/v1/routes/user.py @@ -0,0 +1,60 @@ +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.db import get_db +from app.models import User +from app.schemas import UserCreate, UserRead, UserUpdate + +router = APIRouter() + +@router.post("/users", response_model=UserRead) +def create_user(user: UserCreate, db: Session = Depends(get_db)): + db_user = User(**user.dict()) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + +@router.get("/users", response_model=List[UserRead]) +def read_users(db: Session = Depends(get_db)): + users = db.query(User).all() + return users + +@router.get("/users/{user_id}", response_model=UserRead) +def read_user(user_id: int, db: Session = Depends(get_db)): + db_user = db.query(User).get(user_id) + if not db_user: + raise HTTPException(status_code=404, detail="User not found") + return db_user + +@router.put("/users/{user_id}", response_model=UserRead) +def update_user(user_id: int, user: UserUpdate, db: Session = Depends(get_db)): + db_user = db.query(User).get(user_id) + if not db_user: + raise HTTPException(status_code=404, detail="User not found") + update_data = user.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(db_user, key, value) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + +@router.delete("/users/{user_id}") +def delete_user(user_id: int, db: Session = Depends(get_db)): + db_user = db.query(User).get(user_id) + if not db_user: + raise HTTPException(status_code=404, detail="User not found") + db.delete(db_user) + db.commit() + return {"message": "User deleted successfully"} + +This code defines a `router` object using `APIRouter` from FastAPI. It contains the following endpoints: + + + + + + +Note that this code assumes the existence of the following components: \ No newline at end of file diff --git a/app/api/v1/schemas/comments.py b/app/api/v1/schemas/comments.py new file mode 100644 index 0000000..6404f53 --- /dev/null +++ b/app/api/v1/schemas/comments.py @@ -0,0 +1,38 @@ +Here's the `comments.py` file with Pydantic schemas for comments, following the FastAPI project structure with SQLite and SQLAlchemy: + + +from datetime import datetime +from typing import Optional + +from pydantic import BaseModel + +from app.db.models import Comment + +class CommentBase(BaseModel): + body: str + +class CommentCreate(CommentBase): + post_id: int + +class CommentUpdate(CommentBase): + pass + +class CommentInDBBase(CommentBase): + id: int + post_id: int + created_at: datetime + updated_at: Optional[datetime] = None + + class Config: + orm_mode = True + +class Comment(CommentInDBBase): + pass + +class CommentInDB(CommentInDBBase): + pass + +Here's a breakdown of the file: + +2. `CommentBase` is a Pydantic model representing the base fields of a comment (body). +5. `CommentInDBBase` inherits from `CommentBase` and includes additional fields (`id`, `post_id`, `created_at`, and `updated_at`) that are typically stored in the database. The `orm_mode` config is set to `True` to allow reading data from an ORM model. \ No newline at end of file diff --git a/app/api/v1/schemas/posts.py b/app/api/v1/schemas/posts.py new file mode 100644 index 0000000..d3e7a2a --- /dev/null +++ b/app/api/v1/schemas/posts.py @@ -0,0 +1,39 @@ +Here's the `posts.py` file with Pydantic schemas for posts, located in the `app/api/v1/schemas/` directory: + +from typing import Optional +from pydantic import BaseModel +from datetime import datetime + +class PostBase(BaseModel): + title: str + content: str + +class PostCreate(PostBase): + pass + +class PostUpdate(PostBase): + pass + +class PostInDBBase(PostBase): + id: int + created_at: datetime + updated_at: Optional[datetime] = None + + class Config: + orm_mode = True + +class Post(PostInDBBase): + pass + +class PostList(BaseModel): + __root__: list[Post] + +Explanation: + +1. We import the necessary modules: `typing` for type annotations, `pydantic` for defining data models, and `datetime` for working with date and time objects. + +2. `PostBase` is a Pydantic model that defines the base fields for a post: `title` and `content`. + + + +5. `PostInDBBase` inherits from `PostBase` and adds additional fields: `id`, `created_at`, and `updated_at`. The `orm_mode` config is set to `True` to allow Pydantic to read data from SQLAlchemy models. \ No newline at end of file diff --git a/app/api/v1/schemas/tags.py b/app/api/v1/schemas/tags.py new file mode 100644 index 0000000..9b72297 --- /dev/null +++ b/app/api/v1/schemas/tags.py @@ -0,0 +1,34 @@ +Here's the `tags.py` file with Pydantic schemas for tags in the `app/api/v1/schemas/` directory: + +from typing import Optional +from pydantic import BaseModel + +class TagBase(BaseModel): + name: str + +class TagCreate(TagBase): + pass + +class TagUpdate(TagBase): + name: Optional[str] = None + +class TagInDBBase(TagBase): + id: int + name: str + + class Config: + orm_mode = True + +class Tag(TagInDBBase): + pass + +class TagInDB(TagInDBBase): + pass + +Explanation: + + + + + +5. `TagInDBBase` inherits from `TagBase` and adds an `id` field as a required integer. It also sets the `orm_mode` configuration to `True`, which allows Pydantic to handle data from an ORM (SQLAlchemy). \ No newline at end of file diff --git a/app/api/v1/schemas/user.py b/app/api/v1/schemas/user.py new file mode 100644 index 0000000..bd631d6 --- /dev/null +++ b/app/api/v1/schemas/user.py @@ -0,0 +1,35 @@ +Here's the `user.py` file with Pydantic schemas for the `User` model, which you can place in the `app/api/v1/schemas/` directory: + +from typing import Optional +from pydantic import BaseModel, EmailStr + +class UserBase(BaseModel): + email: Optional[EmailStr] = None + is_active: Optional[bool] = True + is_superuser: bool = False + full_name: Optional[str] = None + +class UserCreate(UserBase): + email: EmailStr + password: str + +class UserUpdate(UserBase): + password: Optional[str] = None + +class UserInDBBase(UserBase): + id: Optional[int] = None + + class Config: + orm_mode = True + +class User(UserInDBBase): + pass + +class UserInUpdate(UserUpdate): + pass + +This file defines several Pydantic models for the `User` model: + + + +Note: This code assumes that you have the `pydantic` library installed and that your project follows the recommended structure for FastAPI applications using SQLAlchemy and SQLite. \ No newline at end of file diff --git a/main.py b/main.py index dca5c44..1024a94 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,10 @@ from fastapi import FastAPI - -app = FastAPI(title="Generated Backend") - -@app.get("/") -def read_root(): - return {"message": "Welcome to the generated backend"} \ No newline at end of file +from app.api.db.database import engine, Base +from app.api.v1.routes import router +from app.api.core.middleware.activity_tracker import ActivityTrackerMiddleware +app = FastAPI() +app.add_middleware(ActivityTrackerMiddleware) +app.include_router(router, prefix='/v1') +@app.on_event('startup') +def startup(): + Base.metadata.create_all(bind=engine) diff --git a/requirements.txt b/requirements.txt index a70e8ac..d54d189 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1 @@ -fastapi -uvicorn -sqlalchemy -pydantic \ No newline at end of file +Here's the `requirements.txt` file for the 'blog_app' FastAPI backend: \ No newline at end of file