first commit
This commit is contained in:
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
57
backend/app/api/deps.py
Normal file
57
backend/app/api/deps.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from collections.abc import Generator
|
||||
from typing import Annotated
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pydantic import ValidationError
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.core import security
|
||||
from app.core.config import settings
|
||||
from app.core.db import engine
|
||||
from app.models import TokenPayload, User
|
||||
|
||||
reusable_oauth2 = OAuth2PasswordBearer(
|
||||
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
|
||||
)
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
|
||||
SessionDep = Annotated[Session, Depends(get_db)]
|
||||
TokenDep = Annotated[str, Depends(reusable_oauth2)]
|
||||
|
||||
|
||||
def get_current_user(session: SessionDep, token: TokenDep) -> User:
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||
)
|
||||
token_data = TokenPayload(**payload)
|
||||
except (InvalidTokenError, ValidationError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Could not validate credentials",
|
||||
)
|
||||
user = session.get(User, token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return user
|
||||
|
||||
|
||||
CurrentUser = Annotated[User, Depends(get_current_user)]
|
||||
|
||||
|
||||
def get_current_active_superuser(current_user: CurrentUser) -> User:
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=403, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
return current_user
|
16
backend/app/api/main.py
Normal file
16
backend/app/api/main.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.routes import items, login, users, utils, messages, setting, aboutUs, course, image, info_image, sechedule
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(login.router, tags=["login"])
|
||||
api_router.include_router(users.router, prefix="/users", tags=["users"])
|
||||
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
|
||||
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(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"])
|
||||
api_router.include_router(sechedule.router, prefix="/sechedule", tags=["sechedule"])
|
0
backend/app/api/routes/__init__.py
Normal file
0
backend/app/api/routes/__init__.py
Normal file
97
backend/app/api/routes/aboutUs.py
Normal file
97
backend/app/api/routes/aboutUs.py
Normal file
@@ -0,0 +1,97 @@
|
||||
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 (
|
||||
AboutUsBase,
|
||||
AboutUs,
|
||||
AboutUsPublic,
|
||||
AboutUsCreate,
|
||||
AboutsUpdate,
|
||||
AboutsListPublic,
|
||||
Message,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/", response_model=AboutUsPublic)
|
||||
async def create_aboutUs(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
description: 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, folderName="tmp")
|
||||
# aboutus_in.image = imageUrl
|
||||
aboutUs_in = AboutUsCreate(description=description, image=imageUrl, index=index)
|
||||
aboutUs = AboutUs.from_orm(aboutUs_in)
|
||||
session.add(aboutUs)
|
||||
session.commit()
|
||||
session.refresh(aboutUs)
|
||||
return aboutUs
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=AboutUsPublic)
|
||||
async def edit_aboutUs(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
description: str = Form(),
|
||||
image: Annotated[UploadFile, File()] = None,
|
||||
index: int = Form()
|
||||
) -> Any:
|
||||
aboutUs = session.get(AboutUs, id)
|
||||
|
||||
if image is not None:
|
||||
validate_file_size_type(image)
|
||||
imageUrl = await save_picture(file=image, folderName="tmp")
|
||||
await del_picture(aboutUs.image)
|
||||
aboutUs_in = AboutsUpdate(description=description, image=imageUrl, index=index)
|
||||
else :
|
||||
aboutUs_in = AboutsUpdate(description=description, image=aboutUs.image, index=index)
|
||||
|
||||
update_dict = aboutUs_in.model_dump(exclude_unset=True)
|
||||
aboutUs.sqlmodel_update(update_dict)
|
||||
session.add(aboutUs)
|
||||
session.commit()
|
||||
session.refresh(aboutUs)
|
||||
|
||||
return aboutUs
|
||||
|
||||
|
||||
@router.get("/", response_model=AboutsListPublic)
|
||||
def read_aboutus_list(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
|
||||
|
||||
count_statement = select(func.count()).select_from(AboutUs)
|
||||
count = session.exec(count_statement).one()
|
||||
statement = select(AboutUs).offset(skip).limit(limit)
|
||||
aboutus = session.exec(statement).all()
|
||||
|
||||
return AboutsListPublic(data=aboutus, count=count)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_aboutus(
|
||||
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
|
||||
) -> Message:
|
||||
"""
|
||||
Delete an course.
|
||||
"""
|
||||
aboutUs = session.get(AboutUs, id)
|
||||
if not aboutUs:
|
||||
raise HTTPException(status_code=404, detail="aboutUs not found")
|
||||
await del_picture(aboutUs.image)
|
||||
session.delete(aboutUs)
|
||||
session.commit()
|
||||
return Message(message="aboutUs deleted successfully")
|
95
backend/app/api/routes/course.py
Normal file
95
backend/app/api/routes/course.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from sqlmodel import func, select
|
||||
|
||||
from app.api.deps import CurrentUser, SessionDep
|
||||
from app.models import (
|
||||
Course,
|
||||
CourseCreate,
|
||||
CoursePublic,
|
||||
CoursesPublic,
|
||||
CourseUpdate,
|
||||
CoursePublicWithImages,
|
||||
Message,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=CoursesPublic)
|
||||
def read_courses(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
|
||||
"""
|
||||
Retrieve courses.
|
||||
"""
|
||||
count_statement = select(func.count()).select_from(Course)
|
||||
count = session.exec(count_statement).one()
|
||||
statement = select(Course).offset(skip).limit(limit)
|
||||
courses = session.exec(statement).all()
|
||||
|
||||
return CoursesPublic(data=courses, count=count)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=CoursePublicWithImages)
|
||||
def read_course(session: SessionDep, id: uuid.UUID) -> Any:
|
||||
"""
|
||||
Get item by ID.
|
||||
"""
|
||||
course = session.get(Course, id)
|
||||
if not course:
|
||||
raise HTTPException(status_code=404, detail="course not found")
|
||||
|
||||
return course
|
||||
|
||||
|
||||
@router.post("/", response_model=CoursePublic)
|
||||
def create_item(
|
||||
*, session: SessionDep, current_user: CurrentUser, course_in: CourseCreate
|
||||
) -> Any:
|
||||
"""
|
||||
Create new course.
|
||||
"""
|
||||
course = Course.model_validate(course_in)
|
||||
session.add(course)
|
||||
session.commit()
|
||||
session.refresh(course)
|
||||
return course
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=CoursePublic)
|
||||
def update_course(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
course_in: CourseUpdate,
|
||||
) -> Any:
|
||||
"""
|
||||
Update an course.
|
||||
"""
|
||||
course = session.get(Course, id)
|
||||
if not course:
|
||||
raise HTTPException(status_code=404, detail="course not found")
|
||||
|
||||
update_dict = course_in.model_dump(exclude_unset=True)
|
||||
course.sqlmodel_update(update_dict)
|
||||
session.add(course)
|
||||
session.commit()
|
||||
session.refresh(course)
|
||||
return course
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
def delete_course(
|
||||
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
|
||||
) -> Message:
|
||||
"""
|
||||
Delete an course.
|
||||
"""
|
||||
course = session.get(Course, id)
|
||||
if not course:
|
||||
raise HTTPException(status_code=404, detail="course not found")
|
||||
session.delete(course)
|
||||
session.commit()
|
||||
return Message(message="course deleted successfully")
|
68
backend/app/api/routes/image.py
Normal file
68
backend/app/api/routes/image.py
Normal file
@@ -0,0 +1,68 @@
|
||||
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 (
|
||||
Image,
|
||||
ImageCreate,
|
||||
Message,
|
||||
Course,
|
||||
CoursePublicWithImages
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/",response_model=CoursePublicWithImages)
|
||||
async def create_image(
|
||||
*, session: SessionDep, current_user: CurrentUser,
|
||||
image: Annotated[UploadFile, File()],
|
||||
index: int = Form(),
|
||||
course_id: uuid.UUID = Form(),
|
||||
) -> Any:
|
||||
"""
|
||||
Create new image.
|
||||
"""
|
||||
validate_file_size_type(image)
|
||||
imageUrl = await save_picture(file=image, folderName="tmp")
|
||||
image_in = ImageCreate(image=imageUrl, index=index, course_id=course_id)
|
||||
image = Image.from_orm(image_in)
|
||||
session.add(image)
|
||||
session.commit()
|
||||
session.refresh(image)
|
||||
course = session.get(Course, course_id)
|
||||
return course
|
||||
|
||||
@router.put("/{id}",response_model=CoursePublicWithImages)
|
||||
async def edit_image(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
index: int,
|
||||
)-> Any:
|
||||
image = session.get(Image, id)
|
||||
image_in = ImageCreate(image=image.image, index=index, course_id=image.course_id)
|
||||
update_dict = image_in.model_dump(exclude_unset=True)
|
||||
image.sqlmodel_update(update_dict)
|
||||
session.add(image)
|
||||
session.commit()
|
||||
session.refresh(image)
|
||||
course = session.get(Course, image.course_id)
|
||||
return course
|
||||
|
||||
@router.delete("/{id}",response_model=CoursePublicWithImages)
|
||||
async def delete_image(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
)-> Any:
|
||||
image = session.get(Image, id)
|
||||
await del_picture(image.image)
|
||||
session.delete(image)
|
||||
session.commit()
|
||||
course = session.get(Course, image.course_id)
|
||||
return course
|
68
backend/app/api/routes/info_image.py
Normal file
68
backend/app/api/routes/info_image.py
Normal file
@@ -0,0 +1,68 @@
|
||||
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 (
|
||||
Info_Image,
|
||||
Info_ImageCreate,
|
||||
Message,
|
||||
Course,
|
||||
CoursePublicWithImages
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/",response_model=CoursePublicWithImages)
|
||||
async def create_info_image(
|
||||
*, session: SessionDep, current_user: CurrentUser,
|
||||
image: Annotated[UploadFile, File()],
|
||||
index: int = Form(),
|
||||
course_id: uuid.UUID = Form(),
|
||||
) -> Any:
|
||||
"""
|
||||
Create new image.
|
||||
"""
|
||||
validate_file_size_type(image)
|
||||
imageUrl = await save_picture(file=image, folderName="tmp")
|
||||
info_image_in = Info_ImageCreate(image=imageUrl, index=index, course_id=course_id)
|
||||
info_image = Info_Image.from_orm(info_image_in)
|
||||
session.add(info_image)
|
||||
session.commit()
|
||||
session.refresh(info_image)
|
||||
course = session.get(Course, course_id)
|
||||
return course
|
||||
|
||||
@router.put("/{id}",response_model=CoursePublicWithImages)
|
||||
async def edit_info_image(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
index: int,
|
||||
)-> Any:
|
||||
info_image = session.get(Info_Image, id)
|
||||
info_image_in = Info_ImageCreate(image=info_image.image, index=index, course_id=info_image.course_id)
|
||||
update_dict = info_image_in.model_dump(exclude_unset=True)
|
||||
info_image.sqlmodel_update(update_dict)
|
||||
session.add(info_image)
|
||||
session.commit()
|
||||
session.refresh(info_image)
|
||||
course = session.get(Course, info_image.course_id)
|
||||
return course
|
||||
|
||||
@router.delete("/{id}",response_model=CoursePublicWithImages)
|
||||
async def delete_info_image(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
)-> Any:
|
||||
info_Image = session.get(Info_Image, id)
|
||||
await del_picture(info_Image.image)
|
||||
session.delete(info_Image)
|
||||
session.commit()
|
||||
course = session.get(Course, info_Image.course_id)
|
||||
return course
|
114
backend/app/api/routes/items.py
Normal file
114
backend/app/api/routes/items.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from sqlmodel import func, select
|
||||
|
||||
from app.api.deps import CurrentUser, SessionDep
|
||||
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=ItemsPublic)
|
||||
def read_items(
|
||||
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
|
||||
) -> Any:
|
||||
"""
|
||||
Retrieve items.
|
||||
"""
|
||||
|
||||
if current_user.is_superuser:
|
||||
count_statement = select(func.count()).select_from(Item)
|
||||
count = session.exec(count_statement).one()
|
||||
statement = select(Item).offset(skip).limit(limit)
|
||||
items = session.exec(statement).all()
|
||||
else:
|
||||
count_statement = (
|
||||
select(func.count())
|
||||
.select_from(Item)
|
||||
.where(Item.owner_id == current_user.id)
|
||||
)
|
||||
count = session.exec(count_statement).one()
|
||||
statement = (
|
||||
select(Item)
|
||||
.where(Item.owner_id == current_user.id)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
)
|
||||
items = session.exec(statement).all()
|
||||
|
||||
return ItemsPublic(data=items, count=count)
|
||||
|
||||
|
||||
@router.get("/items/{item_id}")
|
||||
async def agcd(item_id: int):
|
||||
return {"item_id": item_id}
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=ItemPublic)
|
||||
def read_item(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) -> Any:
|
||||
"""
|
||||
Get item by ID.
|
||||
"""
|
||||
item = session.get(Item, id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
if not current_user.is_superuser and (item.owner_id != current_user.id):
|
||||
raise HTTPException(status_code=400, detail="Not enough permissions")
|
||||
return item
|
||||
|
||||
|
||||
@router.post("/", response_model=ItemPublic)
|
||||
def create_item(
|
||||
*, session: SessionDep, current_user: CurrentUser, item_in: ItemCreate
|
||||
) -> Any:
|
||||
"""
|
||||
Create new item.
|
||||
"""
|
||||
item = Item.model_validate(item_in, update={"owner_id": current_user.id})
|
||||
session.add(item)
|
||||
session.commit()
|
||||
session.refresh(item)
|
||||
return item
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=ItemPublic)
|
||||
def update_item(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
id: uuid.UUID,
|
||||
item_in: ItemUpdate,
|
||||
) -> Any:
|
||||
"""
|
||||
Update an item.
|
||||
"""
|
||||
item = session.get(Item, id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
if not current_user.is_superuser and (item.owner_id != current_user.id):
|
||||
raise HTTPException(status_code=400, detail="Not enough permissions")
|
||||
update_dict = item_in.model_dump(exclude_unset=True)
|
||||
item.sqlmodel_update(update_dict)
|
||||
session.add(item)
|
||||
session.commit()
|
||||
session.refresh(item)
|
||||
return item
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
def delete_item(
|
||||
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
|
||||
) -> Message:
|
||||
"""
|
||||
Delete an item.
|
||||
"""
|
||||
item = session.get(Item, id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
if not current_user.is_superuser and (item.owner_id != current_user.id):
|
||||
raise HTTPException(status_code=400, detail="Not enough permissions")
|
||||
session.delete(item)
|
||||
session.commit()
|
||||
return Message(message="Item deleted successfully")
|
124
backend/app/api/routes/login.py
Normal file
124
backend/app/api/routes/login.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from datetime import timedelta
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from app import crud
|
||||
from app.api.deps import CurrentUser, SessionDep, get_current_active_superuser
|
||||
from app.core import security
|
||||
from app.core.config import settings
|
||||
from app.core.security import get_password_hash
|
||||
from app.models import Message, NewPassword, Token, UserPublic
|
||||
from app.utils import (
|
||||
generate_password_reset_token,
|
||||
generate_reset_password_email,
|
||||
send_email,
|
||||
verify_password_reset_token,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/login/access-token")
|
||||
def login_access_token(
|
||||
session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
|
||||
) -> Token:
|
||||
"""
|
||||
OAuth2 compatible token login, get an access token for future requests
|
||||
"""
|
||||
user = crud.authenticate(
|
||||
session=session, email=form_data.username, password=form_data.password
|
||||
)
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="Incorrect email or password")
|
||||
elif not user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
return Token(
|
||||
access_token=security.create_access_token(
|
||||
user.id, expires_delta=access_token_expires
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login/test-token", response_model=UserPublic)
|
||||
def test_token(current_user: CurrentUser) -> Any:
|
||||
"""
|
||||
Test access token
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.post("/password-recovery/{email}")
|
||||
def recover_password(email: str, session: SessionDep) -> Message:
|
||||
"""
|
||||
Password Recovery
|
||||
"""
|
||||
user = crud.get_user_by_email(session=session, email=email)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this email does not exist in the system.",
|
||||
)
|
||||
password_reset_token = generate_password_reset_token(email=email)
|
||||
email_data = generate_reset_password_email(
|
||||
email_to=user.email, email=email, token=password_reset_token
|
||||
)
|
||||
send_email(
|
||||
email_to=user.email,
|
||||
subject=email_data.subject,
|
||||
html_content=email_data.html_content,
|
||||
)
|
||||
return Message(message="Password recovery email sent")
|
||||
|
||||
|
||||
@router.post("/reset-password/")
|
||||
def reset_password(session: SessionDep, body: NewPassword) -> Message:
|
||||
"""
|
||||
Reset password
|
||||
"""
|
||||
email = verify_password_reset_token(token=body.token)
|
||||
if not email:
|
||||
raise HTTPException(status_code=400, detail="Invalid token")
|
||||
user = crud.get_user_by_email(session=session, email=email)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this email does not exist in the system.",
|
||||
)
|
||||
elif not user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
hashed_password = get_password_hash(password=body.new_password)
|
||||
user.hashed_password = hashed_password
|
||||
session.add(user)
|
||||
session.commit()
|
||||
return Message(message="Password updated successfully")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/password-recovery-html-content/{email}",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
response_class=HTMLResponse,
|
||||
)
|
||||
def recover_password_html_content(email: str, session: SessionDep) -> Any:
|
||||
"""
|
||||
HTML Content for Password Recovery
|
||||
"""
|
||||
user = crud.get_user_by_email(session=session, email=email)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system.",
|
||||
)
|
||||
password_reset_token = generate_password_reset_token(email=email)
|
||||
email_data = generate_reset_password_email(
|
||||
email_to=user.email, email=email, token=password_reset_token
|
||||
)
|
||||
|
||||
return HTMLResponse(
|
||||
content=email_data.html_content, headers={"subject:": email_data.subject}
|
||||
)
|
51
backend/app/api/routes/messages.py
Normal file
51
backend/app/api/routes/messages.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from sqlmodel import func, select
|
||||
|
||||
from app.api.deps import CurrentUser, SessionDep
|
||||
from app.models import MessageCreate, MessagePublic, MessagesPublic, Message
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/", response_model=Message)
|
||||
def create_message(*, session: SessionDep, Message_in: MessageCreate) -> Any:
|
||||
"""
|
||||
Create new message.
|
||||
"""
|
||||
message = Message.model_validate(Message_in)
|
||||
session.add(message)
|
||||
session.commit()
|
||||
session.refresh(message)
|
||||
return message
|
||||
|
||||
|
||||
@router.get("/", response_model=MessagesPublic)
|
||||
def read_messages(
|
||||
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
|
||||
) -> Any:
|
||||
|
||||
count_statement = select(func.count()).select_from(Message)
|
||||
count = session.exec(count_statement).one()
|
||||
statement = select(Message).offset(skip).limit(limit)
|
||||
messages = session.exec(statement).all()
|
||||
|
||||
|
||||
return MessagesPublic(data=messages, count=count)
|
||||
|
||||
@router.delete("/{id}")
|
||||
def delete_item(
|
||||
session: SessionDep, current_user: CurrentUser, id: uuid.UUID
|
||||
) -> Message:
|
||||
"""
|
||||
Delete an item.
|
||||
"""
|
||||
message = session.get(Message, id)
|
||||
if not message:
|
||||
raise HTTPException(status_code=404, detail="Message not found")
|
||||
|
||||
session.delete(message)
|
||||
session.commit()
|
||||
return Message(message="Message deleted successfully")
|
0
backend/app/api/routes/schedule
Normal file
0
backend/app/api/routes/schedule
Normal file
60
backend/app/api/routes/sechedule.py
Normal file
60
backend/app/api/routes/sechedule.py
Normal file
@@ -0,0 +1,60 @@
|
||||
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 (
|
||||
ScheduleCreate,
|
||||
Schedule,
|
||||
ScheduleUpdate,
|
||||
Course,
|
||||
CoursePublicWithImages,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/", response_model=CoursePublicWithImages)
|
||||
async def create_schedule(
|
||||
*, session: SessionDep, current_user: CurrentUser, schedule_in: ScheduleCreate
|
||||
) -> Any:
|
||||
|
||||
schedule = Schedule.model_validate(schedule_in)
|
||||
session.add(schedule)
|
||||
session.commit()
|
||||
session.refresh(schedule)
|
||||
course = session.get(Course, schedule.course_id)
|
||||
return course
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=CoursePublicWithImages)
|
||||
async def edit_schedule(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
schedule_in: ScheduleUpdate,
|
||||
id: uuid.UUID,
|
||||
) -> Any:
|
||||
schedule = session.get(Schedule, id)
|
||||
update_dict = schedule_in.model_dump(exclude_unset=True)
|
||||
schedule.sqlmodel_update(update_dict)
|
||||
session.add(schedule)
|
||||
session.commit()
|
||||
session.refresh(schedule)
|
||||
course = session.get(Course, schedule.course_id)
|
||||
return course
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=CoursePublicWithImages)
|
||||
async def delete_schedule(
|
||||
*, session: SessionDep, current_user: CurrentUser, id: uuid.UUID
|
||||
) -> Any:
|
||||
schedule = session.get(Schedule, id)
|
||||
if not schedule:
|
||||
raise HTTPException(status_code=404, detail="schedule not found")
|
||||
session.delete(schedule)
|
||||
session.commit()
|
||||
course = session.get(Course, schedule.course_id)
|
||||
return course
|
44
backend/app/api/routes/setting.py
Normal file
44
backend/app/api/routes/setting.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, UploadFile, FastAPI, File
|
||||
from sqlmodel import func, select
|
||||
from typing import List
|
||||
from app.api.deps import CurrentUser, SessionDep
|
||||
from app.models import SettingBase, Setting
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=Setting,
|
||||
)
|
||||
def read_setting(session: SessionDep, current_user: CurrentUser) -> Any:
|
||||
"""
|
||||
Retrieve users.
|
||||
"""
|
||||
setting = session.exec(select(Setting)).first()
|
||||
if not setting:
|
||||
raise HTTPException(status_code=404, detail="Setting not found")
|
||||
return setting
|
||||
|
||||
|
||||
@router.put("/", response_model=SettingBase)
|
||||
def update_setting(
|
||||
*,
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
setting_in: SettingBase,
|
||||
) -> Any:
|
||||
"""
|
||||
Update an item.
|
||||
"""
|
||||
setting = session.exec(select(Setting)).first()
|
||||
update_dict = setting_in.model_dump(exclude_unset=True)
|
||||
setting.sqlmodel_update(update_dict)
|
||||
session.add(setting)
|
||||
session.commit()
|
||||
session.refresh(setting)
|
||||
return setting
|
||||
|
228
backend/app/api/routes/users.py
Normal file
228
backend/app/api/routes/users.py
Normal file
@@ -0,0 +1,228 @@
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlmodel import col, delete, func, select
|
||||
|
||||
from app import crud
|
||||
from app.api.deps import (
|
||||
CurrentUser,
|
||||
SessionDep,
|
||||
get_current_active_superuser,
|
||||
)
|
||||
from app.core.config import settings
|
||||
from app.core.security import get_password_hash, verify_password
|
||||
from app.models import (
|
||||
Item,
|
||||
Message,
|
||||
UpdatePassword,
|
||||
User,
|
||||
UserCreate,
|
||||
UserPublic,
|
||||
UserRegister,
|
||||
UsersPublic,
|
||||
UserUpdate,
|
||||
UserUpdateMe,
|
||||
)
|
||||
from app.utils import generate_new_account_email, send_email
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
response_model=UsersPublic,
|
||||
)
|
||||
def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
|
||||
"""
|
||||
Retrieve users.
|
||||
"""
|
||||
|
||||
count_statement = select(func.count()).select_from(User)
|
||||
count = session.exec(count_statement).one()
|
||||
|
||||
statement = select(User).offset(skip).limit(limit)
|
||||
users = session.exec(statement).all()
|
||||
|
||||
return UsersPublic(data=users, count=count)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/", dependencies=[Depends(get_current_active_superuser)], response_model=UserPublic
|
||||
)
|
||||
def create_user(*, session: SessionDep, user_in: UserCreate) -> Any:
|
||||
"""
|
||||
Create new user.
|
||||
"""
|
||||
user = crud.get_user_by_email(session=session, email=user_in.email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="The user with this email already exists in the system.",
|
||||
)
|
||||
|
||||
user = crud.create_user(session=session, user_create=user_in)
|
||||
if settings.emails_enabled and user_in.email:
|
||||
email_data = generate_new_account_email(
|
||||
email_to=user_in.email, username=user_in.email, password=user_in.password
|
||||
)
|
||||
send_email(
|
||||
email_to=user_in.email,
|
||||
subject=email_data.subject,
|
||||
html_content=email_data.html_content,
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@router.patch("/me", response_model=UserPublic)
|
||||
def update_user_me(
|
||||
*, session: SessionDep, user_in: UserUpdateMe, current_user: CurrentUser
|
||||
) -> Any:
|
||||
"""
|
||||
Update own user.
|
||||
"""
|
||||
|
||||
if user_in.email:
|
||||
existing_user = crud.get_user_by_email(session=session, email=user_in.email)
|
||||
if existing_user and existing_user.id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=409, detail="User with this email already exists"
|
||||
)
|
||||
user_data = user_in.model_dump(exclude_unset=True)
|
||||
current_user.sqlmodel_update(user_data)
|
||||
session.add(current_user)
|
||||
session.commit()
|
||||
session.refresh(current_user)
|
||||
return current_user
|
||||
|
||||
|
||||
@router.patch("/me/password", response_model=Message)
|
||||
def update_password_me(
|
||||
*, session: SessionDep, body: UpdatePassword, current_user: CurrentUser
|
||||
) -> Any:
|
||||
"""
|
||||
Update own password.
|
||||
"""
|
||||
if not verify_password(body.current_password, current_user.hashed_password):
|
||||
raise HTTPException(status_code=400, detail="Incorrect password")
|
||||
if body.current_password == body.new_password:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="New password cannot be the same as the current one"
|
||||
)
|
||||
hashed_password = get_password_hash(body.new_password)
|
||||
current_user.hashed_password = hashed_password
|
||||
session.add(current_user)
|
||||
session.commit()
|
||||
return Message(message="Password updated successfully")
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserPublic)
|
||||
def read_user_me(current_user: CurrentUser) -> Any:
|
||||
"""
|
||||
Get current user.
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.delete("/me", response_model=Message)
|
||||
def delete_user_me(session: SessionDep, current_user: CurrentUser) -> Any:
|
||||
"""
|
||||
Delete own user.
|
||||
"""
|
||||
if current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=403, detail="Super users are not allowed to delete themselves"
|
||||
)
|
||||
statement = delete(Item).where(col(Item.owner_id) == current_user.id)
|
||||
session.exec(statement) # type: ignore
|
||||
session.delete(current_user)
|
||||
session.commit()
|
||||
return Message(message="User deleted successfully")
|
||||
|
||||
|
||||
@router.post("/signup", response_model=UserPublic)
|
||||
def register_user(session: SessionDep, user_in: UserRegister) -> Any:
|
||||
"""
|
||||
Create new user without the need to be logged in.
|
||||
"""
|
||||
user = crud.get_user_by_email(session=session, email=user_in.email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="The user with this email already exists in the system",
|
||||
)
|
||||
user_create = UserCreate.model_validate(user_in)
|
||||
user = crud.create_user(session=session, user_create=user_create)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserPublic)
|
||||
def read_user_by_id(
|
||||
user_id: uuid.UUID, session: SessionDep, current_user: CurrentUser
|
||||
) -> Any:
|
||||
"""
|
||||
Get a specific user by id.
|
||||
"""
|
||||
user = session.get(User, user_id)
|
||||
if user == current_user:
|
||||
return user
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="The user doesn't have enough privileges",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{user_id}",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
response_model=UserPublic,
|
||||
)
|
||||
def update_user(
|
||||
*,
|
||||
session: SessionDep,
|
||||
user_id: uuid.UUID,
|
||||
user_in: UserUpdate,
|
||||
) -> Any:
|
||||
"""
|
||||
Update a user.
|
||||
"""
|
||||
|
||||
db_user = session.get(User, user_id)
|
||||
if not db_user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this id does not exist in the system",
|
||||
)
|
||||
if user_in.email:
|
||||
existing_user = crud.get_user_by_email(session=session, email=user_in.email)
|
||||
if existing_user and existing_user.id != user_id:
|
||||
raise HTTPException(
|
||||
status_code=409, detail="User with this email already exists"
|
||||
)
|
||||
|
||||
db_user = crud.update_user(session=session, db_user=db_user, user_in=user_in)
|
||||
return db_user
|
||||
|
||||
|
||||
@router.delete("/{user_id}", dependencies=[Depends(get_current_active_superuser)])
|
||||
def delete_user(
|
||||
session: SessionDep, current_user: CurrentUser, user_id: uuid.UUID
|
||||
) -> Message:
|
||||
"""
|
||||
Delete a user.
|
||||
"""
|
||||
user = session.get(User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if user == current_user:
|
||||
raise HTTPException(
|
||||
status_code=403, detail="Super users are not allowed to delete themselves"
|
||||
)
|
||||
statement = delete(Item).where(col(Item.owner_id) == user_id)
|
||||
session.exec(statement) # type: ignore
|
||||
session.delete(user)
|
||||
session.commit()
|
||||
return Message(message="User deleted successfully")
|
26
backend/app/api/routes/utils.py
Normal file
26
backend/app/api/routes/utils.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic.networks import EmailStr
|
||||
|
||||
from app.api.deps import get_current_active_superuser
|
||||
from app.models import Message
|
||||
from app.utils import generate_test_email, send_email
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/test-email/",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
status_code=201,
|
||||
)
|
||||
def test_email(email_to: EmailStr) -> Message:
|
||||
"""
|
||||
Test emails.
|
||||
"""
|
||||
email_data = generate_test_email(email_to=email_to)
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject=email_data.subject,
|
||||
html_content=email_data.html_content,
|
||||
)
|
||||
return Message(message="Test email sent")
|
Reference in New Issue
Block a user