This commit is contained in:
암냥 2025-09-13 16:18:28 +09:00
commit 40266cc6e5
191 changed files with 5022 additions and 0 deletions

View file

View file

@ -0,0 +1,74 @@
from typing import Optional
from ..schemas.avatar import (
AvatarUpdate,
Avatar,
AvatarOptions,
AvatarType,
TopClothesType,
BottomClothesType,
)
from ..utils.db import execute, fetch_one
from ..utils.queries.avatar import AvatarQueries
class AvatarService:
@staticmethod
async def init_db():
await execute(AvatarQueries.CREATE_TABLE)
async def get_or_create_avatar(self, user_id: int) -> Avatar:
avatar = await self.get_user_avatar(user_id)
if not avatar:
avatar = await self.create_default_avatar(user_id)
return avatar
async def get_user_avatar(self, user_id: int) -> Optional[Avatar]:
row = await fetch_one(AvatarQueries.SELECT_USER_AVATAR, (user_id,))
if not row:
return None
return Avatar(**row)
async def create_default_avatar(self, user_id: int) -> Avatar:
await execute(
AvatarQueries.INSERT_AVATAR,
(
user_id,
AvatarType.MALE.value,
TopClothesType.ANA_CLOTHES.value,
BottomClothesType.JEANS.value,
),
)
return await self.get_user_avatar(user_id)
async def update_avatar(self, user_id: int, avatar_data: AvatarUpdate) -> Avatar:
update_fields = []
params = []
if avatar_data.avatar_type is not None:
update_fields.append("avatar_type = ?")
params.append(avatar_data.avatar_type.value)
if avatar_data.top_clothe_type is not None:
update_fields.append("top_clothe_type = ?")
params.append(avatar_data.top_clothe_type.value)
if avatar_data.bottom_clothe_type is not None:
update_fields.append("bottom_clothe_type = ?")
params.append(avatar_data.bottom_clothe_type.value)
if update_fields:
query = AvatarQueries.UPDATE_AVATAR.format(fields=", ".join(update_fields))
params.append(user_id)
await execute(query, tuple(params))
return await self.get_user_avatar(user_id)
async def get_avatar_options(self) -> AvatarOptions:
return AvatarOptions(
avatar_types=list(AvatarType),
top_clothe_types=list(TopClothesType),
bottom_clothe_types=list(BottomClothesType),
)
async def get_avatar_by_userId(self, user_id: int) -> Optional[Avatar]:
return await self.get_user_avatar(user_id)

View file

@ -0,0 +1,144 @@
import os
from datetime import datetime
from typing import List, Optional
from fastapi import UploadFile
from ..schemas.diary import DiaryCreate, DiaryUpdate, Diary
from ..utils.db import execute, fetch_one, fetch_all
from ..utils.image_processor import ImageProcessor
from ..utils.queries.diary import DiaryQueries
class DiaryService:
def __init__(self):
self.image_processor = ImageProcessor()
self.upload_dir = "uploads/diary"
os.makedirs(self.upload_dir, exist_ok=True)
@staticmethod
async def init_db():
await execute(DiaryQueries.CREATE_TABLE)
async def create_diary(
self, user_id: int, diary_data: DiaryCreate, files: List[UploadFile]
) -> Diary:
# image_path 는 ,로 구분 되어 있음
image_path = ""
if files is not None:
for file in files:
image_path += (
","
+ await self.image_processor.write_file_and_get_image_path(
file, self.upload_dir
)
)
image_path = image_path[1:]
query = DiaryQueries.INSERT_DIARY
created_at = datetime.now()
await execute(
query,
(
user_id,
diary_data.title,
diary_data.content,
image_path,
diary_data.category,
created_at,
False,
False,
),
)
row = await fetch_one(
DiaryQueries.SELECT_LATEST_USER_DIARY,
(user_id,),
)
return Diary(**row)
async def get_user_diaries(
self, user_id: int, skip: int = 0, limit: int = 20, category: str = None
) -> List[Diary]:
if category:
query = DiaryQueries.SELECT_USER_DIARIES_BY_CATEGORY
rows = await fetch_all(query, (user_id, category, limit, skip))
else:
query = DiaryQueries.SELECT_USER_DIARIES
rows = await fetch_all(query, (user_id, limit, skip))
return [Diary(**row) for row in rows]
async def get_diary_by_id(self, diary_id: int) -> Optional[Diary]:
query = DiaryQueries.SELECT_BY_ID
row = await fetch_one(query, (diary_id,))
if not row:
return None
return Diary(**row)
async def get_diary_with_user_id(
self, diary_id: int, user_id: int
) -> Optional[Diary]:
query = DiaryQueries.SELECT_BY_ID_WITH_USER_ID
row = await fetch_one(query, (diary_id, user_id))
if not row:
return None
return Diary(**row)
async def update_diary(
self,
diary_id: int,
user_id: int,
diary_data: DiaryUpdate,
files: List[UploadFile],
) -> Optional[Diary]:
diary = await self.get_diary_with_user_id(diary_id, user_id)
if not diary:
return None
update_fields = []
params = []
if diary_data.title is not None:
update_fields.append("title = ?")
params.append(diary_data.title)
if diary_data.content is not None:
update_fields.append("content = ?")
params.append(diary_data.content)
if diary_data.category is not None:
update_fields.append("category = ?")
params.append(diary_data.category)
if files is not None:
update_fields.append("images = ?")
image_paths = ""
for file in files:
image_paths += (
","
+ await self.image_processor.write_file_and_get_image_path(
file, self.upload_dir
)
)
image_paths = image_paths[1:]
params.append(image_paths)
if update_fields:
query = DiaryQueries.UPDATE_DIARY.format(fields=", ".join(update_fields))
params.extend([diary_id, user_id])
await execute(query, tuple(params))
return await self.get_diary_with_user_id(diary_id, user_id)
async def delete_diary(self, diary_id: int, user_id: int) -> bool:
try:
query = DiaryQueries.DELETE_DIARY
await execute(query, (diary_id, user_id))
return True
except Exception:
return False

View file

@ -0,0 +1,142 @@
from typing import List, Optional
from datetime import datetime
from ..schemas.friendship import Friendship, FriendshipResponse, FriendshipStatus
from ..utils.db import execute, fetch_one, fetch_all
from ..utils.queries.friendship import FriendshipQueries
class FriendshipService:
@staticmethod
async def init_db():
await execute(FriendshipQueries.CREATE_TABLE)
async def send_friendship_request(
self, user_id: int, friend_username: str
) -> FriendshipResponse:
friend_query = FriendshipQueries.SELECT_USER_BY_USERNAME
friend_row = await fetch_one(friend_query, (friend_username,))
if not friend_row:
raise ValueError("User not found")
friend_id = friend_row["id"]
if user_id == friend_id:
raise ValueError("Cannot send friendship request to yourself")
existing_query = FriendshipQueries.SELECT_EXISTING_FRIENDSHIP
existing_row = await fetch_one(
existing_query, (user_id, friend_id, friend_id, user_id)
)
if existing_row:
raise ValueError("Friendship request already exists")
created_at = datetime.now()
query = FriendshipQueries.INSERT_FRIENDSHIP
await execute(
query, (user_id, friend_id, FriendshipStatus.PENDING.value, created_at)
)
friendship_row = await fetch_one(
FriendshipQueries.SELECT_FRIENDSHIP_BY_IDS,
(user_id, friend_id),
)
if not friendship_row:
raise ValueError("Friendship not found after creation")
friendship = Friendship(
id=friendship_row["id"],
user_id=friendship_row["user_id"],
friend_id=friendship_row["friend_id"],
status=friendship_row["status"],
created_at=friendship_row["created_at"],
)
return friendship.to_response(friend_username)
async def accept_friendship_request(
self, friendship_id: int, user_id: int
) -> Optional[FriendshipResponse]:
friendship_query = FriendshipQueries.SELECT_FRIENDSHIP_FOR_ACCEPT
friendship_row = await fetch_one(
friendship_query,
(friendship_id, user_id, FriendshipStatus.PENDING.value),
)
if not friendship_row:
return None
update_query = FriendshipQueries.UPDATE_FRIENDSHIP_STATUS
await execute(update_query, (FriendshipStatus.ACCEPTED.value, friendship_id))
friendship = Friendship(
id=friendship_row["id"],
user_id=friendship_row["user_id"],
friend_id=friendship_row["friend_id"],
status=FriendshipStatus.ACCEPTED.value,
created_at=friendship_row["created_at"],
)
return friendship.to_response(friendship_row["username"])
async def get_user_friendships(
self, user_id: int, status: Optional[str] = None
) -> List[FriendshipResponse]:
if status:
query = FriendshipQueries.SELECT_USER_FRIENDSHIPS_BY_STATUS
rows = await fetch_all(query, (user_id, user_id, user_id, status))
else:
query = FriendshipQueries.SELECT_USER_FRIENDSHIPS
rows = await fetch_all(
query, (user_id, user_id, user_id, FriendshipStatus.ACCEPTED.value)
)
friendships = []
for row in rows:
friendship = Friendship(
id=row["id"],
user_id=row["user_id"],
friend_id=row["friend_id"],
status=row["status"],
created_at=row["created_at"],
)
friendships.append(friendship.to_response(row["username"]))
return friendships
async def delete_friendship(self, friendship_id: int, user_id: int) -> bool:
query = FriendshipQueries.DELETE_FRIENDSHIP
await execute(query, (friendship_id, user_id, user_id))
return True
async def get_pending_requests(self, user_id: int) -> List[FriendshipResponse]:
query = FriendshipQueries.SELECT_PENDING_REQUESTS
rows = await fetch_all(query, (user_id, FriendshipStatus.PENDING.value))
friendships = []
for row in rows:
friendship = Friendship(
id=row["id"],
user_id=row["user_id"],
friend_id=row["friend_id"],
status=row["status"],
created_at=row["created_at"],
)
friendships.append(friendship.to_response(row["username"]))
return friendships
async def check_friendship(self, user_id1: int, user_id2: int) -> bool:
friendship_query = FriendshipQueries.CHECK_FRIENDSHIP_STATUS
friendship_row = await fetch_one(
friendship_query, (user_id1, user_id2, user_id2, user_id1)
)
return friendship_row is not None

View file

@ -0,0 +1,110 @@
from datetime import datetime
from typing import List
from fastapi import HTTPException
from Backend.utils.db import execute, fetch_one, fetch_all
from Backend.utils.queries.guestbook import GuestBookQueries
from Backend.schemas.guestbook import GuestBook, GuestBookCreate, GuestbookResponse
from Backend.schemas.user import User
from Backend.utils.queries.user import UserQueries
class GuestbookService:
def __init__(self):
pass
@staticmethod
async def init_db():
await execute(GuestBookQueries.CREATE_TABLE)
async def create_guestbook(
self, data: GuestBookCreate, user: User
) -> GuestbookResponse:
user_exist = await fetch_one(UserQueries.SELECT_BY_ID, (data.target_user_id,))
if user_exist is None:
raise HTTPException(status_code=404, detail="User not found")
ex_row = await fetch_one(
GuestBookQueries.SELECT_GUEST_BOOK_BY_USER_ID, (user.id,)
)
created_at = datetime.now()
query = GuestBookQueries.INSERT_GUEST_BOOK
await execute(query, (data.target_user_id, user.id, data.content, created_at))
query = GuestBookQueries.SELECT_GUEST_BOOK_BY_USER_ID
row = await fetch_one(query, (user.id,))
if not (ex_row is None):
if row is None or ex_row["id"] == row["id"]:
raise HTTPException(
status_code=400, detail="Failed to create guest book"
)
return GuestbookResponse(
id=row["id"],
content=row["content"],
target_user_id=row["target_user_id"],
user_id=row["user_id"],
user_profile_path=user.profile_image_path,
username=user.username,
created_at=row["created_at"],
)
async def get_target_user_guestbooks(
self, target_user_id: int, limit: int = 20, offset: int = 0
) -> List[GuestbookResponse]:
query = GuestBookQueries.SELECT_TARGET_USER_GUEST_BOOKS
rows = await fetch_all(query, (target_user_id, limit, offset))
response_list = []
for row in rows:
user = await fetch_one(UserQueries.SELECT_BY_ID, (row["user_id"],))
response_list.append(
GuestbookResponse(
id=row["id"],
content=row["content"],
target_user_id=row["target_user_id"],
user_id=row["user_id"],
user_profile_path=user["profile_image_path"],
username=user["username"],
created_at=row["created_at"],
)
)
return response_list
async def update_guestbook_by_id(self, id: int, content: str) -> GuestbookResponse:
query = GuestBookQueries.SELECT_GUEST_BOOK_BY_ID
row = await fetch_one(query, (id,))
if row is None:
raise HTTPException(status_code=404, detail="Guest book not found")
query = GuestBookQueries.UPDATE_GUEST_BOOK_BY_ID
await execute(query, (content, id))
query = GuestBookQueries.SELECT_GUEST_BOOK_BY_ID
row = await fetch_one(query, (id,))
user = await fetch_one(UserQueries.SELECT_BY_ID, (row["user_id"],))
return GuestbookResponse(
id=row["id"],
content=row["content"],
target_user_id=row["target_user_id"],
user_id=row["user_id"],
user_profile_path=user["profile_image_path"],
username=user["username"],
created_at=row["created_at"],
)
async def delete_guestbook_by_id(self, id: int, user_id: int) -> bool:
try:
query = GuestBookQueries.DELETE_GUEST_BOOK
await execute(query, (id, user_id))
return True
except Exception:
return False

View file

@ -0,0 +1,81 @@
# from typing import List, Optional
# from ..schemas.letter import LetterCreate, Letter, EmailRequest
# from ..utils.db import execute, fetch_one, fetch_all
# from ..utils.default_queries import LetterQueries
# from ..utils.email_processor import EmailProcessor
#
#
# class LetterService:
# def __init__(self):
# self.email_processor = EmailProcessor()
#
# @staticmethod
# async def init_db():
# await execute(LetterQueries.CREATE_TABLE)
#
# async def create_letter(self, sender_id: int, letter_data: LetterCreate) -> Letter:
# query = LetterQueries.INSERT_LETTER
#
# await execute(
# query,
# (sender_id, letter_data.content),
# )
#
# row = await fetch_one(
# LetterQueries.SELECT_LATEST_USER_LETTER,
# (sender_id,),
# )
#
# return Letter(**row)
#
# async def get_user_letters(
# self, sender_id: int, skip: int = 0, limit: int = 20
# ) -> List[Letter]:
# query = LetterQueries.SELECT_USER_LETTERS
# rows = await fetch_all(query, (sender_id, limit, skip))
# return [Letter(**row) for row in rows]
#
# async def get_letter_by_id(
# self, letter_id: int, sender_id: int
# ) -> Optional[Letter]:
# query = LetterQueries.SELECT_LETTER_BY_ID
# row = await fetch_one(query, (letter_id, sender_id))
# if not row:
# return None
# return Letter(**row)
#
# async def delete_letter(self, letter_id: int, sender_id: int) -> bool:
# try:
# query = LetterQueries.DELETE_LETTER
# await execute(
# query,
# (letter_id, sender_id),
# )
# return True
# except Exception:
# return False
#
# async def update_letter(
# self, letter_id: int, sender_id: int, content: str
# ) -> Optional[Letter]:
# query = LetterQueries.UPDATE_LETTER
# await execute(
# query,
# (content, letter_id, sender_id),
# )
#
# row = await fetch_one(
# LetterQueries.SELECT_LETTER_BY_ID,
# (letter_id, sender_id),
# )
# if row is None:
# return None
#
# return Letter(**row)
#
# async def send_letter(self, letter: Letter, data: EmailRequest):
# subject = f"2025_SSF_LETTER_{data.sender_name}"
# content = letter.content
# await self.email_processor.send_email(
# subject, content, data.sender_email, data.sender_password
# )

View file

@ -0,0 +1,150 @@
# import os
# from datetime import datetime
# from typing import List
# from fastapi import UploadFile
# from ..schemas.photo import (
# PhotoUpload,
# Photo,
# PhotoComment,
# CommentCreate,
# CommentResponse,
# )
# from ..utils.db import execute, fetch_one, fetch_all
# from ..utils.default_queries import PhotoQueries
# from ..utils.image_processor import ImageProcessor
#
#
# class PhotoService:
# def __init__(self):
# self.image_processor = ImageProcessor()
# self.upload_dir = "uploads/photos"
# os.makedirs(self.upload_dir, exist_ok=True)
#
# @staticmethod
# async def init_db():
# await execute(PhotoQueries.CREATE_TABLE)
# await execute(PhotoQueries.CREATE_COMMENTS_TABLE)
#
# async def upload_photo(
# self, user_id: int, photo_data: PhotoUpload, file: UploadFile
# ) -> Photo:
# if not file.content_type.startswith("image/"):
# raise ValueError("File must be an image")
#
# self.image_processor.validate_image_file(file.filename, file.size)
#
# created_at = datetime.now()
#
# image_path = await self.image_processor.write_file_and_get_image_path(
# file, self.upload_dir
# )
#
# query = PhotoQueries.INSERT_PHOTO
#
# await execute(
# query,
# (user_id, photo_data.album_name, image_path, photo_data.title, created_at),
# )
#
# row = await fetch_one(
# PhotoQueries.SELECT_LATEST_USER_PHOTO,
# (user_id,),
# )
#
# return Photo(**row)
#
# async def get_user_photos(
# self, user_id: int, skip: int = 0, limit: int = 20, album_name: str = None
# ) -> List[Photo]:
# if album_name:
# query = PhotoQueries.SELECT_USER_PHOTOS_BY_ALBUM
# rows = await fetch_all(query, (user_id, album_name, limit, skip))
# else:
# query = PhotoQueries.SELECT_USER_PHOTOS
# rows = await fetch_all(query, (user_id, limit, skip))
#
# return [Photo(**row) for row in rows]
#
# async def check_friendship(self, user_id: int, photo_id: int) -> bool:
# photo_query = PhotoQueries.SELECT_PHOTO_OWNER
# photo_row = await fetch_one(photo_query, (photo_id,))
#
# if not photo_row:
# return False
#
# photo_owner_id = photo_row["user_id"]
#
# if user_id == photo_owner_id:
# return True
#
# from ..services.friendship_service import FriendshipService
#
# friendship_service = FriendshipService()
# return await friendship_service.check_friendship(user_id, photo_owner_id)
#
# async def add_comment(
# self, photo_id: int, user_id: int, comment_data: CommentCreate
# ) -> PhotoComment:
# if not await self.check_friendship(user_id, photo_id):
# raise ValueError("Cannot add comment before being friends")
# created_at = datetime.now()
#
# query = PhotoQueries.INSERT_COMMENT
#
# await execute(query, (photo_id, user_id, comment_data.content, created_at))
#
# row = await fetch_one(
# PhotoQueries.SELECT_LATEST_COMMENT,
# (photo_id, user_id),
# )
#
# return PhotoComment(**row)
#
# async def get_photo_comments(self, photo_id: int) -> List[CommentResponse]:
# query = PhotoQueries.SELECT_PHOTO_COMMENTS
#
# rows = await fetch_all(query, (photo_id,))
#
# return [CommentResponse(**row) for row in rows]
#
# async def apply_filter(
# self,
# photo_id: int,
# filter_type: str,
# user_id: int,
# cover: bool = False,
# title: str = None,
# ) -> str:
# photo_query = PhotoQueries.SELECT_PHOTO_BY_ID
# row = await fetch_one(photo_query, (photo_id, user_id))
#
# if not row:
# raise ValueError("Photo not found")
#
# original_path = row["image_path"]
# filtered_path = await self.image_processor.apply_filter(
# original_path, filter_type
# )
#
# if cover:
# photo_update_query = PhotoQueries.UPDATE_PHOTO_PATH
# await execute(photo_update_query, (filtered_path, photo_id, user_id))
# else:
# row = await fetch_one(
# PhotoQueries.SELECT_PHOTO_ALBUM_NAME, (photo_id, user_id)
# )
# photo_create_query = PhotoQueries.INSERT_PHOTO
# await execute(
# photo_create_query,
# (user_id, row["album_name"], filtered_path, title, datetime.now()),
# )
#
# return filtered_path
#
# async def delete_photo(self, photo_id: int, user_id: int) -> bool:
# try:
# query = PhotoQueries.DELETE_PHOTO
# await execute(query, (photo_id, user_id))
# return True
# except Exception:
# return False

View file

@ -0,0 +1,149 @@
from fastapi import HTTPException
from typing import List
from Backend.utils.queries.room import RoomQueries
from Backend.schemas.room import (
FurnitureItem,
RoomFurniturePlacement,
FurniturePlacementRequest,
Room,
RoomTypes,
RoomTypeResponse,
room_path,
Furniture,
furniture_path,
RoomFurnitureResponse,
FurniturePlacementResponse,
RoomResponse,
)
from Backend.utils.db import fetch_all, fetch_one, execute
class RoomService:
@staticmethod
async def init_db():
await execute(RoomQueries.CREATE_TABLE)
await execute(RoomQueries.CREATE_TABLE_ROOM_FURNITURE)
await execute(RoomQueries.CREATE_TABLE_USER_FURNITURE)
async def get_or_create_room(self, user_id: int) -> int:
row = await fetch_one(RoomQueries.SELECT_ROOM_ID_BY_USER_ID, (user_id,))
if row:
return row["id"]
await execute(RoomQueries.INSERT_ROOM, (user_id, "My Room", "room_1"))
new_row = await fetch_one(RoomQueries.SELECT_ROOM_ID_BY_USER_ID, (user_id,))
return new_row["id"]
async def get_room_by_id(self, id: int) -> Room:
row = await fetch_one(RoomQueries.SELECT_ROOM_BY_ID, (id,))
if row is None:
raise HTTPException(status_code=404, detail="Room not found")
return Room(**row)
async def get_room_by_userId(self, id: int) -> Room:
row = await fetch_one(RoomQueries.SELECT_ROOM_BY_USER_ID, (id,))
if row is None:
raise HTTPException(status_code=404, detail="Room not found")
return Room(**row)
async def get_room_types(self) -> List[RoomTypeResponse]:
return [
RoomTypeResponse(type=rt.value, image_path=room_path[rt.value])
for rt in RoomTypes
]
async def update_room_name(self, room_id: int, new_name: str):
await execute(RoomQueries.UPDATE_ROOM_NAME, (new_name, room_id))
async def update_room_type(self, room_id: int, new_type: RoomTypes):
query = RoomQueries.UPDATE_ROOM_TYPE
await execute(query, (new_type.value, room_id))
row = await fetch_one(RoomQueries.SELECT_ROOM_BY_ID, (room_id,))
return Room(**row)
# furniture
async def get_furniture_catalog(self) -> List[FurnitureItem]:
furniture_list = []
for f in list(Furniture):
furniture_list.append(
FurnitureItem(
name=f,
image_path=furniture_path[f],
width=int(f[-1]),
)
)
return furniture_list
async def get_room_furnitures(self, room_id: int) -> RoomFurnitureResponse:
rows = await fetch_all(RoomQueries.SELECT_ROOM_FURNITURE, (room_id,))
furniture_placement_response = []
for row in rows:
furniture_placement_response.append(
FurniturePlacementResponse(
furniture_name=row["furniture_name"],
x=row["x"],
y=row["y"],
image_path=furniture_path[row["furniture_name"]],
)
)
row = await fetch_one(RoomQueries.SELECT_ROOM_BY_ID, (room_id,))
return RoomFurnitureResponse(
furniture=furniture_placement_response,
room=RoomResponse(
id=row["id"],
user_id=row["user_id"],
room_name=row["room_name"],
room_type=row["room_type"],
room_image_path=room_path[row["room_type"]],
),
)
async def place_furniture(self, room_id: int, request: FurniturePlacementRequest):
is_oneone = furniture_path.get(request.furniture_name + "1") is not None
placed_furnitures = await fetch_all(
RoomQueries.SELECT_ROOM_FURNITURE, (room_id,)
)
for f in placed_furnitures:
if f["x"] == request.x and f["y"] == request.y:
raise HTTPException(status_code=409, detail="Furniture already placed")
await execute(
RoomQueries.INSERT_ROOM_FURNITURE,
(room_id, request.furniture_name, request.x, request.y),
)
if not is_oneone:
await execute(
RoomQueries.INSERT_ROOM_FURNITURE,
(room_id, "1", request.x - 1, request.y),
)
async def remove_furniture(self, room_id: int, x: int, y: int, furniture_name: str):
is_oneone = furniture_path.get(furniture_name + "1") is not None
await execute(RoomQueries.DELETE_FURNITURE, (room_id, x, y))
if not is_oneone:
await execute(RoomQueries.DELETE_FURNITURE, (room_id, x - 1, y))
async def add_furniture(self, user_id: int, furniture_name: str):
await execute(RoomQueries.INSERT_USER_FURNITURE, (user_id, furniture_name))
async def get_user_furniture(self, user_id: int):
rows = await fetch_all(RoomQueries.SELECT_USER_FURNITURE, (user_id,))
furniture_list = []
for row in rows:
furniture_list.append(
FurnitureItem(
name=row["furniture_name"],
image_path=furniture_path[row["furniture_name"]],
width=int(row["furniture_name"][-1]),
)
)
return furniture_list

View file

@ -0,0 +1,33 @@
import httpx
class StoreService:
def __init__(self):
self.SERVER_URL = "https://dotory.ana.st"
async def get_dotory_by_id(self, user_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"{self.SERVER_URL}/")
response_json = response.json()
return
async def register_user(self, user_id: int):
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.SERVER_URL}/", json={"user_id": user_id}
)
return
async def buy_product(self, product_id: int, user_id: int):
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.SERVER_URL}/", json={"user_id": user_id}
)
return
async def update_user_dotory(self, user_id: int, dotoryNum: int):
async with httpx.AsyncClient() as client:
response = await client.put(
f"{self.SERVER_URL}/", json={"num": dotoryNum}
)
return

View file

@ -0,0 +1,178 @@
from datetime import datetime, timezone, timedelta
from typing import Optional, List
import os
from fastapi import UploadFile
from Backend.utils.db import execute, fetch_one, fetch_all
from Backend.utils.image_processor import ImageProcessor
from Backend.utils.queries.user import UserQueries
from Backend.schemas.user import User, UserCreate, UserResponse, UserUpdate
from Backend.services.store_service import StoreService
store_service = StoreService()
class UserService:
def __init__(self):
self.image_processor = ImageProcessor()
self.upload_dir = "uploads/profile"
os.makedirs(self.upload_dir, exist_ok=True)
@staticmethod
async def init_db():
await execute(UserQueries.CREATE_TABLE)
async def create_user(
self, user_data: UserCreate, profile_file: UploadFile = None
) -> User:
password_hash, salt = User.hash_password(user_data.password)
if profile_file is not None:
await self.image_processor.validate_image_file(
)
image_path = await self.image_processor.write_file_and_get_image_path(
profile_file, upload_dir=self.upload_dir
)
query = UserQueries.INSERT_USER_WITH_PROFILE
params = (
user_data.username,
user_data.email,
password_hash,
salt,
image_path,
)
else:
query = UserQueries.INSERT_USER_WITHOUT_PROFILE
params = (user_data.username, user_data.email, password_hash, salt)
await execute(
query,
params,
)
row = await fetch_one(
UserQueries.SELECT_BY_USERNAME,
(user_data.username,),
)
if row is None:
raise Exception("User creation failed")
row = dict(row)
if isinstance(row["created_at"], str):
datetime.fromisoformat(row["created_at"].replace("Z", "+09:00"))
row["is_active"] = bool(row["is_active"])
await store_service.register_user(row["id"])
return User(**row)
async def get_user_by_username(self, username: str) -> Optional[User]:
row = await fetch_one(
UserQueries.SELECT_BY_USERNAME,
(username,),
)
if row is None:
return None
row = dict(row)
if isinstance(row["created_at"], str):
datetime.fromisoformat(row["created_at"].replace("Z", "+09:00"))
row["is_active"] = bool(row["is_active"])
return User(**row)
async def get_user_by_email(self, email: str) -> Optional[User]:
row = await fetch_one(
UserQueries.SELECT_BY_EMAIL,
(email,),
)
if row is None:
return None
row = dict(row)
if isinstance(row["created_at"], str):
datetime.fromisoformat(row["created_at"].replace("Z", "+09:00"))
row["is_active"] = bool(row["is_active"])
return User(**row)
async def authenticate_user(self, username: str, password: str) -> Optional[User]:
user =
if user and :
return user
return None
async def delete_user(self, username: str) -> bool:
try:
query = UserQueries.DELETE_USER_BY_USERNAME
await execute(query, (username,))
return True
except Exception:
return False
async def find_user(self, username: str) -> List[UserResponse]:
query = UserQueries.SELECT_BY_USERNAME_LIKE
rows = await fetch_all(
query,
("%" + username + "%",),
)
return [User(**row).to_response() for row in rows]
async def get_user_by_id(self, user_id: int) -> Optional[User]:
row = await fetch_one(UserQueries.SELECT_BY_ID, (user_id,))
if row is None:
return None
row = dict(row)
if isinstance(row["created_at"], str):
datetime.fromisoformat(row["created_at"].replace("Z", "+09:00"))
row["is_active"] = bool(row["is_active"])
return User(**row)
async def update_user(
self, user: User, user_data: UserUpdate, profile_file: UploadFile = None
) -> User:
update_fields = {}
if user_data.email:
existing_user = await fetch_one(
UserQueries.SELECT_USER_BY_EMAIL_AND_NOT_ID,
(user_data.email, user.id),
)
if existing_user:
raise ValueError("Email already registered")
update_fields["email"] = user_data.email
if user_data.password:
password_hash, salt = User.hash_password(user_data.password)
update_fields["password_hash"] = password_hash
update_fields["salt"] = salt
if profile_file:
await self.image_processor.validate_image_file(
profile_file.filename, profile_file.size
)
image_path = await self.image_processor.write_file_and_get_image_path(
profile_file, upload_dir=self.upload_dir
)
update_fields["profile_image_path"] = image_path
if not update_fields:
return user
set_clause = ", ".join(f"{key} = ?" for key in update_fields.keys())
query = UserQueries.UPDATE_USER_BY_ID.format(set_clause)
params = list(update_fields.values())
params.append(user.id)
await execute(query, tuple(params))
return await self.get_user_by_id(user.id)