diff --git a/backend/app/alembic/versions/e77992962f85_add_organ.py b/backend/app/alembic/versions/e77992962f85_add_organ.py new file mode 100644 index 0000000..b9e27bb --- /dev/null +++ b/backend/app/alembic/versions/e77992962f85_add_organ.py @@ -0,0 +1,36 @@ +"""add organ + +Revision ID: e77992962f85 +Revises: 7acc8c28d84c +Create Date: 2024-10-26 19:33:40.557599 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = 'e77992962f85' +down_revision = '7acc8c28d84c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('organ', + sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=1024), nullable=False), + sa.Column('image', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), + sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False), + sa.Column('index', sa.Integer(), nullable=False), + sa.Column('id', sa.Uuid(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('organ') + # ### end Alembic commands ### diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 273a596..4cbedaf 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.routes import items, login, users, utils, messages, setting, aboutUs, course, image, info_image, sechedule +from app.api.routes import items, login, users, utils, messages, setting, aboutUs, course, image, info_image, sechedule, organ api_router = APIRouter() api_router.include_router(login.router, tags=["login"]) @@ -10,6 +10,7 @@ api_router.include_router(items.router, prefix="/items", tags=["items"]) api_router.include_router(messages.router, prefix="/messages", tags=["messages"]) api_router.include_router(setting.router, prefix="/setting", tags=["setting"]) api_router.include_router(aboutUs.router, prefix="/aboutUs", tags=["aboutUs"]) +api_router.include_router(organ.router, prefix="/organ", tags=["organ"]) api_router.include_router(course.router, prefix="/course", tags=["course"]) api_router.include_router(image.router, prefix="/image", tags=["image"]) api_router.include_router(info_image.router, prefix="/info_image", tags=["info_image"]) diff --git a/backend/app/api/routes/organ.py b/backend/app/api/routes/organ.py new file mode 100644 index 0000000..66de563 --- /dev/null +++ b/backend/app/api/routes/organ.py @@ -0,0 +1,98 @@ +import uuid +from typing import Any, Annotated, Optional +from app.utils import validate_file_size_type, save_picture, del_picture +from fastapi import APIRouter, HTTPException, UploadFile, File, Form +from sqlmodel import func, select + +from app.api.deps import CurrentUser, SessionDep +from app.models import ( + Organ, + OrganPublic, + OrganCreate, + OrganUpdate, + OrganListPublic, + Message +) + +router = APIRouter() + + +@router.post("/", response_model=OrganPublic) +async def create_organ( + *, + session: SessionDep, + current_user: CurrentUser, + description: str = Form(), + title: str = Form(), + image: Annotated[UploadFile, File()], + index: int = Form() +) -> Any: + """ + Create new about us. + """ + validate_file_size_type(image) + imageUrl = await save_picture(file=image, folder_name="tmp") + # aboutus_in.image = imageUrl + organ_in = OrganCreate(description=description, image=imageUrl, index=index ,title=title) + organ = Organ.from_orm(organ_in) + session.add(organ) + session.commit() + session.refresh(organ) + return organ + + +@router.put("/{id}", response_model= OrganPublic) +async def edit_organ( + *, + session: SessionDep, + current_user: CurrentUser, + id: uuid.UUID, + description: str = Form(), + image: Annotated[UploadFile, File()] = None, + title: str = Form(), + index: int = Form() +) -> Any: + organ = session.get(Organ, id) + + if image is not None: + validate_file_size_type(image) + imageUrl = await save_picture(file=image, folder_name="tmp") + await del_picture(organ.image) + organ_in = OrganUpdate(description=description, image=imageUrl, index=index, title=title) + else : + organ_in = OrganUpdate(description=description, image=organ.image, index=index, title=title) + + update_dict = organ_in.model_dump(exclude_unset=True) + organ.sqlmodel_update(update_dict) + session.add(organ) + session.commit() + session.refresh(organ) + + return organ + + +@router.get("/", response_model=OrganListPublic) +def read_organ_list(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: + + count_statement = select(func.count()).select_from(Organ) + count = session.exec(count_statement).one() + statement = select(Organ).offset(skip).limit(limit) + organ = session.exec(statement).all() + + return OrganListPublic(data=organ, count=count) + + +@router.delete("/{id}") +async def delete_organ( + session: SessionDep, current_user: CurrentUser, id: uuid.UUID +) -> Message: + """ + Delete an course. + """ + organ = session.get(Organ, id) + if not organ: + raise HTTPException(status_code=404, detail="aboutUs not found") + await del_picture(organ.image) + session.delete(organ) + session.commit() + return Message(message="aboutUs deleted successfully") diff --git a/backend/app/models.py b/backend/app/models.py index f97b97f..720a429 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -4,6 +4,8 @@ from pydantic import EmailStr, BaseModel from sqlmodel import Field, Relationship, SQLModel from datetime import datetime from sqlalchemy import JSON + + # Shared properties class UserBase(SQLModel): email: EmailStr = Field(unique=True, index=True, max_length=255) @@ -112,34 +114,38 @@ class TokenPayload(SQLModel): class NewPassword(SQLModel): token: str new_password: str = Field(min_length=8, max_length=40) - -# Client Messages + +# Client Messages + class MessageBase(SQLModel): name: str = Field(max_length=255) phone: str = Field(max_length=255) email: str = Field(max_length=255) message: str = Field(max_length=1024) - + + class Message(MessageBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) created_at: datetime = Field(default_factory=datetime.utcnow) - + + class MessageCreate(MessageBase): pass - + + class MessagePublic(MessageBase): id: uuid.UUID - + class MessagesPublic(SQLModel): data: list[Message] count: int - -# setting +# setting + class SettingBase(SQLModel): address: str = Field(max_length=255) @@ -153,38 +159,79 @@ class SettingBase(SQLModel): youtube: str = Field(max_length=255) youtube_link: str = Field(max_length=255) whatsapp: str = Field(max_length=255) - + + class SettingCreate(SettingBase): - pass + pass + class Setting(SettingBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) - - - # About us - -class AboutUsBase(SQLModel): + + +# organ +class OrganBase(SQLModel): description: str = Field(max_length=1024) - image:str | None = Field(default=None, max_length=255) + image: str | None = Field(default=None, max_length=255) title: str = Field(max_length=255) index: int + +class OrganCreate(OrganBase): + pass + + +class Organ(OrganBase, table=True): + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + + +class OrganPublic(OrganBase): + id: uuid.UUID + + +class OrganUpdate(OrganBase): + pass + + +class OrganListPublic(SQLModel): + data: list[OrganPublic] + count: int + + +# About us + + +class AboutUsBase(SQLModel): + description: str = Field(max_length=1024) + image: str | None = Field(default=None, max_length=255) + title: str = Field(max_length=255) + index: int + + class AboutUsCreate(AboutUsBase): pass + class AboutUs(AboutUsBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + + class AboutUsPublic(AboutUsBase): id: uuid.UUID + + class AboutsUpdate(AboutUsBase): pass + class AboutsListPublic(SQLModel): data: list[AboutUsPublic] count: int - - #courses - + + +# courses + + class CourseBase(SQLModel): title: str = Field(max_length=255) sort_description: str = Field(max_length=32768) @@ -192,28 +239,37 @@ class CourseBase(SQLModel): information: str = Field(max_length=32768) contant: str = Field(max_length=32768) remark: str = Field(max_length=32768) - + + class CourseCreate(CourseBase): pass + class CourseUpdate(CourseBase): pass + class Course(CourseBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) created_at: datetime = Field(default_factory=datetime.utcnow) - images: list["Image"] = Relationship(back_populates="course", cascade_delete=True) - info_images: list["Info_Image"] = Relationship(back_populates="course", cascade_delete=True) - schedule: list["Schedule"] = Relationship(back_populates="course", cascade_delete=True) + images: list["Image"] = Relationship(back_populates="course", cascade_delete=True) + info_images: list["Info_Image"] = Relationship( + back_populates="course", cascade_delete=True + ) + schedule: list["Schedule"] = Relationship( + back_populates="course", cascade_delete=True + ) + class CoursePublic(CourseBase): id: uuid.UUID title: str - images: list["Image"] + images: list["Image"] info_images: list["Info_Image"] schedule: list["Schedule"] - created_at: datetime - + created_at: datetime + + class CoursesPublic(SQLModel): data: list[CoursePublic] count: int @@ -223,55 +279,66 @@ class CoursesPublic(SQLModel): class ImageBase(SQLModel): image: str = Field(max_length=255) index: int - - + + class ImagePublic(ImageBase): id: uuid.UUID + class ImageCreate(ImageBase): - course_id: uuid.UUID + course_id: uuid.UUID + class Image(ImageBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) - course_id: uuid.UUID = Field(foreign_key="course.id", nullable=False, ondelete="CASCADE") + course_id: uuid.UUID = Field( + foreign_key="course.id", nullable=False, ondelete="CASCADE" + ) course: Course | None = Relationship(back_populates="images") - class Info_ImageBase(SQLModel): image: str = Field(max_length=255) index: int + class Info_ImageCreate(Info_ImageBase): - course_id: uuid.UUID + course_id: uuid.UUID + + class Info_Image(Info_ImageBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) - course_id: uuid.UUID = Field(foreign_key="course.id", nullable=False, ondelete="CASCADE") + course_id: uuid.UUID = Field( + foreign_key="course.id", nullable=False, ondelete="CASCADE" + ) course: Course | None = Relationship(back_populates="info_images") + # schedules class ScheduleBase(SQLModel): title: str = Field(max_length=255) info1: str = Field(max_length=255) info2: str = Field(max_length=255) date: str - + + class ScheduleUpdate(ScheduleBase): pass - + + class ScheduleCreate(ScheduleBase): - course_id: uuid.UUID + course_id: uuid.UUID + class Schedule(ScheduleBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) - course_id: uuid.UUID = Field(foreign_key="course.id", nullable=False, ondelete="CASCADE") + course_id: uuid.UUID = Field( + foreign_key="course.id", nullable=False, ondelete="CASCADE" + ) course: Course | None = Relationship(back_populates="schedule") - - + + class CoursePublicWithImages(CoursePublic): images: list[Image] = [] info_images: list[Info_Image] = [] schedule: list[Schedule] = [] - - - \ No newline at end of file