a
This commit is contained in:
commit
40266cc6e5
191 changed files with 5022 additions and 0 deletions
0
Backend/utils/__init__.py
Normal file
0
Backend/utils/__init__.py
Normal file
53
Backend/utils/db.py
Normal file
53
Backend/utils/db.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import aiosqlite
|
||||
from typing import Any, List, Tuple, Optional, Dict, Union
|
||||
|
||||
DB_PATH = "database.sqlite3"
|
||||
|
||||
# Generated by Github Copilot
|
||||
# db 사용을 편하게 하기 위한 함수
|
||||
|
||||
|
||||
async def get_db_connection(db_path: str = DB_PATH) -> aiosqlite.Connection:
|
||||
return await aiosqlite.connect(db_path)
|
||||
|
||||
|
||||
async def execute(
|
||||
query: str,
|
||||
params: Union[Tuple[Any, ...], Dict[str, Any]] = (),
|
||||
db_path: str = DB_PATH,
|
||||
) -> None:
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await db.execute(query, params)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def fetch_one(
|
||||
query: str,
|
||||
params: Union[Tuple[Any, ...], Dict[str, Any]] = (),
|
||||
db_path: str = DB_PATH,
|
||||
) -> Optional[aiosqlite.Row]:
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
async with db.execute(query, params) as cursor:
|
||||
return await cursor.fetchone()
|
||||
|
||||
|
||||
async def fetch_all(
|
||||
query: str,
|
||||
params: Union[Tuple[Any, ...], Dict[str, Any]] = (),
|
||||
db_path: str = DB_PATH,
|
||||
) -> List[aiosqlite.Row]:
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
async with db.execute(query, params) as cursor:
|
||||
return await cursor.fetchall()
|
||||
|
||||
|
||||
async def executemany(
|
||||
query: str,
|
||||
seq_of_params: List[Union[Tuple[Any, ...], Dict[str, Any]]],
|
||||
db_path: str = DB_PATH,
|
||||
) -> None:
|
||||
async with aiosqlite.connect(db_path) as db:
|
||||
await db.executemany(query, seq_of_params)
|
||||
await db.commit()
|
||||
168
Backend/utils/default_queries.py
Normal file
168
Backend/utils/default_queries.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
class PhotoQueries:
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS photos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
album_name TEXT NOT NULL,
|
||||
image_path TEXT NOT NULL,
|
||||
title TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
CREATE_COMMENTS_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS photo_comments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
photo_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (photo_id) REFERENCES photos (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_PHOTO = """
|
||||
INSERT INTO photos (user_id, album_name, image_path, title, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_USER_PHOTOS = """
|
||||
SELECT * FROM photos
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"""
|
||||
|
||||
SELECT_USER_PHOTOS_BY_ALBUM = """
|
||||
SELECT * FROM photos
|
||||
WHERE user_id = ? AND album_name = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"""
|
||||
|
||||
SELECT_LATEST_USER_PHOTO = """
|
||||
SELECT * FROM photos WHERE user_id = ? ORDER BY id DESC LIMIT 1
|
||||
"""
|
||||
|
||||
SELECT_PHOTO_OWNER = """
|
||||
SELECT user_id FROM photos WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_PHOTO_ALBUM_NAME = """
|
||||
SELECT album_name FROM photos WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
INSERT_COMMENT = """
|
||||
INSERT INTO photo_comments (photo_id, user_id, content, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_LATEST_COMMENT = """
|
||||
SELECT * FROM photo_comments WHERE photo_id = ? AND user_id = ? ORDER BY id DESC LIMIT 1
|
||||
"""
|
||||
|
||||
SELECT_PHOTO_COMMENTS = """
|
||||
SELECT pc.*, u.username
|
||||
FROM photo_comments pc
|
||||
JOIN users u ON pc.user_id = u.id
|
||||
WHERE pc.photo_id = ?
|
||||
ORDER BY pc.created_at ASC
|
||||
"""
|
||||
|
||||
SELECT_PHOTO_BY_ID = """
|
||||
SELECT * FROM photos WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
DELETE_PHOTO = """
|
||||
DELETE FROM photos WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
UPDATE_PHOTO_PATH = """
|
||||
UPDATE photos SET image_path = ? WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
|
||||
class LetterQueries:
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS letters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender_id INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
FOREIGN KEY (sender_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_LETTER = """
|
||||
INSERT INTO letters (sender_id, content)
|
||||
VALUES (?, ?)
|
||||
"""
|
||||
|
||||
SELECT_USER_LETTERS = """
|
||||
SELECT * FROM letters
|
||||
WHERE sender_id = ?
|
||||
"""
|
||||
|
||||
SELECT_LATEST_USER_LETTER = """
|
||||
SELECT * FROM letters WHERE sender_id = ? LIMIT 1
|
||||
"""
|
||||
|
||||
SELECT_LETTER_BY_ID = """
|
||||
SELECT * FROM letters WHERE id = ? AND sender_id = ?
|
||||
"""
|
||||
|
||||
SELECT_LETTER_FOR_DELIVERY = """
|
||||
SELECT * FROM letters WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_SENDER_USERNAME = """
|
||||
SELECT username FROM users WHERE id = ?
|
||||
"""
|
||||
|
||||
UPDATE_LETTER = """
|
||||
UPDATE letters SET content = ? WHERE id = ? AND sender_id = ?
|
||||
"""
|
||||
|
||||
DELETE_LETTER = """
|
||||
DELETE FROM letters WHERE id = ? AND sender_id = ?
|
||||
"""
|
||||
|
||||
|
||||
class DatabaseIndexes:
|
||||
USER_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)",
|
||||
]
|
||||
|
||||
DIARY_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_diaries_user_id ON diaries(user_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_diaries_category ON diaries(category)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_diaries_created_at ON diaries(created_at)",
|
||||
]
|
||||
|
||||
PHOTO_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_photos_user_id ON photos(user_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_photos_album ON photos(album_name)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_photo_comments_photo_id ON photo_comments(photo_id)",
|
||||
]
|
||||
|
||||
FRIENDSHIP_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_friendships_user_id ON friendships(user_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_friendships_friend_id ON friendships(friend_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_friendships_status ON friendships(status)",
|
||||
]
|
||||
|
||||
LETTER_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_letters_sender_id ON letters(sender_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_letters_sent_date ON letters(sent_date)",
|
||||
]
|
||||
|
||||
AVATAR_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_avatars_user_id ON avatars(user_id)",
|
||||
]
|
||||
|
||||
GUEST_BOOK_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_guest_books_user_id ON guest_books(user_id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_guest_books_created_at ON guest_books(created_at)",
|
||||
]
|
||||
75
Backend/utils/email_processor.py
Normal file
75
Backend/utils/email_processor.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import aiosmtplib
|
||||
from email.message import EmailMessage
|
||||
from typing import Optional
|
||||
import os
|
||||
from pydantic import EmailStr
|
||||
import ssl
|
||||
|
||||
# aiosmtplib을 사용한 비동기 이메일 전송
|
||||
# EmailStr 사용
|
||||
|
||||
|
||||
class EmailProcessor:
|
||||
def __init__(self):
|
||||
self.smtp_server = os.getenv("SMTP_SERVER", "smtp.gmail.com")
|
||||
self.smtp_port = int(os.getenv("SMTP_PORT", "587"))
|
||||
self.sender_email = os.getenv("SENDER_EMAIL")
|
||||
self.sender_password = os.getenv("SENDER_PASSWORD")
|
||||
self.receiver_email = ""
|
||||
|
||||
async def send_email(
|
||||
self,
|
||||
subject: str,
|
||||
content: str,
|
||||
sender_email: EmailStr,
|
||||
sender_password: str,
|
||||
html_content: Optional[str] = None,
|
||||
) -> bool:
|
||||
if not self.sender_email or not self.sender_password:
|
||||
print("Email credentials not configured, cannot send email.")
|
||||
return False
|
||||
|
||||
if not sender_email or not sender_password:
|
||||
print(
|
||||
"Warning: Email credentials (SENDER_EMAIL, SENDER_PASSWORD) "
|
||||
"are not configured in environment variables."
|
||||
)
|
||||
return False
|
||||
|
||||
message = EmailMessage()
|
||||
message["Subject"] = subject
|
||||
message["From"] = sender_email
|
||||
message["To"] = self.receiver_email
|
||||
message.set_content(content)
|
||||
|
||||
# HTML 내용이 있는 경우, 대체 콘텐츠로 추가
|
||||
if html_content:
|
||||
message.add_alternative(html_content, subtype="html")
|
||||
|
||||
try:
|
||||
# 포트 465는 SMTPS (implicit TLS)를 사용하고, 포트 587은 STARTTLS를 사용합니다.
|
||||
use_tls = self.smtp_port == 465
|
||||
context = ssl.create_default_context() if use_tls else None
|
||||
|
||||
await aiosmtplib.send(
|
||||
message,
|
||||
hostname=self.smtp_server,
|
||||
port=self.smtp_port,
|
||||
username=sender_email,
|
||||
password=sender_password,
|
||||
use_tls=use_tls,
|
||||
ssl_context=context,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
print(f"Email sent successfully to {self.receiver_email}")
|
||||
return True
|
||||
|
||||
except aiosmtplib.SMTPException as e:
|
||||
print(f"SMTP error occurred while sending to {self.receiver_email}: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(
|
||||
f"An unexpected error occurred while sending email to {self.receiver_email}: {e}"
|
||||
)
|
||||
return False
|
||||
179
Backend/utils/image_processor.py
Normal file
179
Backend/utils/image_processor.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import os
|
||||
import aiofiles
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
from datetime import datetime
|
||||
from starlette.datastructures import UploadFile
|
||||
|
||||
|
||||
class ImageProcessor:
|
||||
def __init__(self):
|
||||
self.filter_dir = "uploads/filtered"
|
||||
os.makedirs(self.filter_dir, exist_ok=True)
|
||||
|
||||
async def apply_filter(self, image_path: str, filter_type: str) -> str:
|
||||
"""Apply filter to image and return the path to filtered image"""
|
||||
|
||||
# Remove leading slash if exists for local file access
|
||||
local_path = image_path.lstrip("/")
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
raise ValueError(f"Image file not found: {local_path}")
|
||||
|
||||
try:
|
||||
with Image.open(local_path) as img:
|
||||
# Convert to RGB if necessary
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
filtered_img = self._apply_filter_effect(img, filter_type)
|
||||
|
||||
# Generate filtered image filename
|
||||
base_name = os.path.basename(image_path)
|
||||
name, ext = os.path.splitext(base_name)
|
||||
filtered_filename = f"{name}_{filter_type}{ext}"
|
||||
filtered_path = os.path.join(self.filter_dir, filtered_filename)
|
||||
|
||||
# Save filtered image
|
||||
filtered_img.save(filtered_path, quality=85, optimize=True)
|
||||
|
||||
return f"/uploads/filtered/{filtered_filename}"
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to apply filter: {str(e)}")
|
||||
|
||||
def _apply_filter_effect(self, img: Image.Image, filter_type: str) -> Image.Image:
|
||||
"""Apply specific filter effect to image"""
|
||||
if filter_type == "none":
|
||||
return img
|
||||
elif filter_type == "vintage":
|
||||
return self._apply_vintage_filter(img)
|
||||
elif filter_type == "black_white":
|
||||
return self._apply_black_white_filter(img)
|
||||
elif filter_type == "sepia":
|
||||
return self._apply_sepia_filter(img)
|
||||
elif filter_type == "blur":
|
||||
return self._apply_blur_filter(img)
|
||||
elif filter_type == "sharpen":
|
||||
return self._apply_sharpen_filter(img)
|
||||
elif filter_type == "bright":
|
||||
return self._apply_brightness_filter(img)
|
||||
elif filter_type == "contrast":
|
||||
return self._apply_contrast_filter(img)
|
||||
else:
|
||||
raise ValueError(f"Unknown filter type: {filter_type}")
|
||||
|
||||
def _apply_vintage_filter(self, img: Image.Image) -> Image.Image:
|
||||
# Reduce saturation
|
||||
enhancer = ImageEnhance.Color(img)
|
||||
img = enhancer.enhance(0.7)
|
||||
|
||||
# Add slight warm tint
|
||||
r, g, b = img.split()
|
||||
r = ImageEnhance.Brightness(r).enhance(1.1)
|
||||
g = ImageEnhance.Brightness(g).enhance(1.05)
|
||||
b = ImageEnhance.Brightness(b).enhance(0.9)
|
||||
|
||||
return Image.merge("RGB", (r, g, b))
|
||||
|
||||
def _apply_black_white_filter(self, img: Image.Image) -> Image.Image:
|
||||
return img.convert("L").convert("RGB")
|
||||
|
||||
def _apply_sepia_filter(self, img: Image.Image) -> Image.Image:
|
||||
pixels = img.load()
|
||||
width, height = img.size
|
||||
|
||||
for py in range(height):
|
||||
for px in range(width):
|
||||
r, g, b = pixels[px, py]
|
||||
|
||||
tr = int(0.393 * r + 0.769 * g + 0.189 * b)
|
||||
tg = int(0.349 * r + 0.686 * g + 0.168 * b)
|
||||
tb = int(0.272 * r + 0.534 * g + 0.131 * b)
|
||||
|
||||
pixels[px, py] = (min(255, tr), min(255, tg), min(255, tb))
|
||||
|
||||
return img
|
||||
|
||||
def _apply_blur_filter(self, img: Image.Image) -> Image.Image:
|
||||
return img.filter(ImageFilter.GaussianBlur(radius=2))
|
||||
|
||||
def _apply_sharpen_filter(self, img: Image.Image) -> Image.Image:
|
||||
return img.filter(ImageFilter.SHARPEN)
|
||||
|
||||
def _apply_brightness_filter(self, img: Image.Image) -> Image.Image:
|
||||
enhancer = ImageEnhance.Brightness(img)
|
||||
return enhancer.enhance(1.3)
|
||||
|
||||
def _apply_contrast_filter(self, img: Image.Image) -> Image.Image:
|
||||
enhancer = ImageEnhance.Contrast(img)
|
||||
return enhancer.enhance(1.2)
|
||||
|
||||
async def resize_image(
|
||||
self, image_path: str, max_width: int = 800, max_height: int = 800
|
||||
) -> str:
|
||||
"""Resize image while maintaining aspect ratio"""
|
||||
|
||||
local_path = image_path.lstrip("/")
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
raise ValueError(f"Image file not found: {local_path}")
|
||||
|
||||
try:
|
||||
with Image.open(local_path) as img:
|
||||
# Calculate new size maintaining aspect ratio
|
||||
img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# Generate resized image filename
|
||||
base_name = os.path.basename(image_path)
|
||||
name, ext = os.path.splitext(base_name)
|
||||
resized_filename = f"{name}_resized{ext}"
|
||||
resized_path = os.path.join(
|
||||
os.path.dirname(local_path), resized_filename
|
||||
)
|
||||
|
||||
# Save resized image
|
||||
img.save(resized_path, quality=85, optimize=True)
|
||||
|
||||
return f"/{resized_path}"
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to resize image: {str(e)}")
|
||||
|
||||
async def validate_image_file(self, filename: str, file_size: int):
|
||||
"""Validate image file type and size"""
|
||||
|
||||
allowed_extensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||
|
||||
# Check file extension
|
||||
_, ext = os.path.splitext(filename.lower())
|
||||
if ext not in allowed_extensions:
|
||||
raise ValueError("Unsupported file type")
|
||||
|
||||
if file_size > 20 * 1024 * 1024: # 20MB limit
|
||||
raise ValueError("File size must be less than 20MB")
|
||||
|
||||
def get_safe_filename(self, filename: str) -> str:
|
||||
"""Generate safe filename by removing dangerous characters"""
|
||||
|
||||
import re
|
||||
|
||||
# Keep only alphanumeric characters, dots, and hyphens
|
||||
safe_name = re.sub(r"[^a-zA-Z0-9._-]", "_", filename)
|
||||
|
||||
# Add timestamp to avoid collisions
|
||||
name, ext = os.path.splitext(safe_name)
|
||||
timestamp = int(datetime.now().timestamp())
|
||||
|
||||
return f"{name}_{timestamp}{ext}"
|
||||
|
||||
async def write_file_and_get_image_path(
|
||||
self, file: UploadFile, upload_dir: str
|
||||
) -> str:
|
||||
filename = self.get_safe_filename(file.filename)
|
||||
file_path = os.path.join(upload_dir, filename)
|
||||
|
||||
async with aiofiles.open(file_path, "wb") as f:
|
||||
content = await file.read()
|
||||
await f.write(content)
|
||||
|
||||
return file_path
|
||||
0
Backend/utils/queries/__init__.py
Normal file
0
Backend/utils/queries/__init__.py
Normal file
25
Backend/utils/queries/avatar.py
Normal file
25
Backend/utils/queries/avatar.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
class AvatarQueries:
|
||||
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS avatars (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER UNIQUE NOT NULL,
|
||||
avatar_type TEXT NOT NULL,
|
||||
top_clothe_type TEXT,
|
||||
bottom_clothe_type TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
SELECT_USER_AVATAR = """
|
||||
SELECT * FROM avatars WHERE user_id = ?
|
||||
"""
|
||||
|
||||
INSERT_AVATAR = """
|
||||
INSERT INTO avatars (user_id, avatar_type, top_clothe_type, bottom_clothe_type)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
UPDATE_AVATAR = """
|
||||
UPDATE avatars SET {fields} WHERE user_id = ?
|
||||
"""
|
||||
71
Backend/utils/queries/diary.py
Normal file
71
Backend/utils/queries/diary.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
class DiaryQueries:
|
||||
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS diaries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
images TEXT,
|
||||
category TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_submitted BOOLEAN DEFAULT FALSE,
|
||||
email_sent BOOLEAN DEFAULT FALSE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_DIARY = """
|
||||
INSERT INTO diaries (user_id, title, content, images, category, created_at, is_submitted, email_sent)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_USER_DIARIES = """
|
||||
SELECT * FROM diaries
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"""
|
||||
|
||||
SELECT_USER_DIARIES_BY_CATEGORY = """
|
||||
SELECT * FROM diaries
|
||||
WHERE user_id = ? AND category = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"""
|
||||
|
||||
SELECT_BY_ID = """
|
||||
SELECT * FROM diaries WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_BY_ID_WITH_USER_ID = """
|
||||
SELECT * FROM diaries WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
SELECT_LATEST_USER_DIARY = """
|
||||
SELECT * FROM diaries WHERE user_id = ? ORDER BY id DESC LIMIT 1
|
||||
"""
|
||||
|
||||
SELECT_IMAGES_BY_ID = """
|
||||
SELECT images FROM diaries WHERE id = ?
|
||||
"""
|
||||
|
||||
UPDATE_DIARY = """
|
||||
UPDATE diaries SET {fields} WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
DELETE_DIARY = """
|
||||
DELETE FROM diaries WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
UPDATE_SUBMISSION_STATUS = """
|
||||
UPDATE diaries SET is_submitted = ? WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
|
||||
UPDATE_EMAIL_SENT = """
|
||||
UPDATE diaries SET email_sent = ? WHERE id = ?
|
||||
"""
|
||||
|
||||
UPDATE_DIARY_IMAGE_BY_ID = """
|
||||
UPDATE diaries SET images = ? WHERE id = ?
|
||||
"""
|
||||
89
Backend/utils/queries/friendship.py
Normal file
89
Backend/utils/queries/friendship.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
class FriendshipQueries:
|
||||
"""친구 관계 관련 쿼리"""
|
||||
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS friendships (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
friend_id INTEGER NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (friend_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
UNIQUE(user_id, friend_id)
|
||||
)
|
||||
"""
|
||||
|
||||
SELECT_USER_BY_USERNAME = """
|
||||
SELECT id FROM users WHERE username = ?
|
||||
"""
|
||||
|
||||
SELECT_EXISTING_FRIENDSHIP = """
|
||||
SELECT * FROM friendships
|
||||
WHERE (user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)
|
||||
"""
|
||||
|
||||
INSERT_FRIENDSHIP = """
|
||||
INSERT INTO friendships (user_id, friend_id, status, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_FRIENDSHIP_BY_IDS = """
|
||||
SELECT * FROM friendships WHERE user_id = ? AND friend_id = ?
|
||||
"""
|
||||
|
||||
SELECT_FRIENDSHIP_FOR_ACCEPT = """
|
||||
SELECT f.*, u.username
|
||||
FROM friendships f
|
||||
JOIN users u ON f.user_id = u.id
|
||||
WHERE f.id = ? AND f.friend_id = ? AND f.status = ?
|
||||
"""
|
||||
|
||||
UPDATE_FRIENDSHIP_STATUS = """
|
||||
UPDATE friendships SET status = ? WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_USER_FRIENDSHIPS = """
|
||||
SELECT f.*, u.username
|
||||
FROM friendships f
|
||||
JOIN users u ON (
|
||||
CASE
|
||||
WHEN f.user_id = ? THEN f.friend_id = u.id
|
||||
ELSE f.user_id = u.id
|
||||
END
|
||||
)
|
||||
WHERE (f.user_id = ? OR f.friend_id = ?) AND f.status = ?
|
||||
ORDER BY f.created_at DESC
|
||||
"""
|
||||
|
||||
SELECT_USER_FRIENDSHIPS_BY_STATUS = """
|
||||
SELECT f.*, u.username
|
||||
FROM friendships f
|
||||
JOIN users u ON (
|
||||
CASE
|
||||
WHEN f.user_id = ? THEN f.friend_id = u.id
|
||||
ELSE f.user_id = u.id
|
||||
END
|
||||
)
|
||||
WHERE (f.user_id = ? OR f.friend_id = ?) AND f.status = ?
|
||||
ORDER BY f.created_at DESC
|
||||
"""
|
||||
|
||||
DELETE_FRIENDSHIP = """
|
||||
DELETE FROM friendships
|
||||
WHERE id = ? AND (user_id = ? OR friend_id = ?)
|
||||
"""
|
||||
|
||||
SELECT_PENDING_REQUESTS = """
|
||||
SELECT f.*, u.username
|
||||
FROM friendships f
|
||||
JOIN users u ON f.user_id = u.id
|
||||
WHERE f.friend_id = ? AND f.status = ?
|
||||
ORDER BY f.created_at DESC
|
||||
"""
|
||||
|
||||
CHECK_FRIENDSHIP_STATUS = """
|
||||
SELECT * FROM friendships
|
||||
WHERE ((user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?))
|
||||
AND status = 'accepted'
|
||||
"""
|
||||
40
Backend/utils/queries/guestbook.py
Normal file
40
Backend/utils/queries/guestbook.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
class GuestBookQueries:
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS guest_books (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
target_user_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_GUEST_BOOK = """
|
||||
INSERT INTO guest_books (target_user_id, user_id, content, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_TARGET_USER_GUEST_BOOKS = """
|
||||
SELECT * FROM guest_books
|
||||
WHERE target_user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"""
|
||||
|
||||
SELECT_GUEST_BOOK_BY_ID = """
|
||||
SELECT * FROM guest_books WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_GUEST_BOOK_BY_USER_ID = """
|
||||
SELECT * FROM guest_books WHERE user_id = ? ORDER BY created_at DESC LIMIT 1
|
||||
"""
|
||||
|
||||
UPDATE_GUEST_BOOK_BY_ID = """
|
||||
UPDATE guest_books SET content = ?, updated_at=CURRENT_TIMESTAMP WHERE id = ?
|
||||
"""
|
||||
|
||||
DELETE_GUEST_BOOK = """
|
||||
DELETE FROM guest_books WHERE id = ? AND user_id = ?
|
||||
"""
|
||||
81
Backend/utils/queries/room.py
Normal file
81
Backend/utils/queries/room.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
class RoomQueries:
|
||||
"""마이 룸 관련 쿼리"""
|
||||
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS rooms (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER UNIQUE,
|
||||
room_name TEXT,
|
||||
room_type TEXT
|
||||
)
|
||||
"""
|
||||
CREATE_TABLE_ROOM_FURNITURE = """
|
||||
CREATE TABLE IF NOT EXISTS room_furnitures (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
room_id INTEGER NOT NULL,
|
||||
furniture_name TEXT NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
|
||||
CREATE_TABLE_USER_FURNITURE = """
|
||||
CREATE TABLE IF NOT EXISTS user_furnitures (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
furniture_name TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_USER_FURNITURE = """
|
||||
INSERT INTO user_furnitures (user_id, furniture_name) VALUES (?, ?)
|
||||
"""
|
||||
|
||||
INSERT_ROOM = """
|
||||
INSERT INTO rooms (user_id, room_name, room_type) VALUES (?, ?, ?)
|
||||
"""
|
||||
|
||||
INSERT_ROOM_FURNITURE = """
|
||||
INSERT INTO room_furnitures (room_id, furniture_name, x, y) VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_ROOM_ID_BY_USER_ID = """
|
||||
SELECT id FROM rooms WHERE user_id = ?
|
||||
"""
|
||||
|
||||
SELECT_ROOM_BY_ID = """
|
||||
SELECT * FROM rooms WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_FURNITURE = """
|
||||
SELECT * FROM furnitures
|
||||
"""
|
||||
|
||||
SELECT_ROOM_FURNITURE = """
|
||||
SELECT id, furniture_name, x, y FROM room_furnitures WHERE room_id = ?
|
||||
"""
|
||||
|
||||
SELECT_FURNITURE_ID_BY_X_Y = """
|
||||
SELECT id FROM room_furnitures WHERE room_id = ? AND x = ? AND y = ?
|
||||
"""
|
||||
|
||||
SELECT_USER_FURNITURE = """
|
||||
SELECT * FROM user_furnitures WHERE user_id = ?
|
||||
"""
|
||||
|
||||
UPDATE_ROOM_NAME = """
|
||||
UPDATE rooms SET room_name = ? WHERE id = ?
|
||||
"""
|
||||
|
||||
UPDATE_ROOM_TYPE = """
|
||||
UPDATE rooms SET room_type = ? WHERE id = ?
|
||||
"""
|
||||
|
||||
DELETE_FURNITURE = """
|
||||
DELETE FROM room_furnitures WHERE room_id = ? AND x = ? AND y = ?
|
||||
"""
|
||||
|
||||
SELECT_ROOM_BY_USER_ID = """
|
||||
SELECT * FROM rooms WHERE user_id = ?
|
||||
"""
|
||||
61
Backend/utils/queries/user.py
Normal file
61
Backend/utils/queries/user.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
class UserQueries:
|
||||
"""사용자 관련 쿼리"""
|
||||
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
salt TEXT NOT NULL,
|
||||
profile_image_path TEXT DEFAULT 'upload/profile/default.jpg',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE
|
||||
)
|
||||
"""
|
||||
|
||||
INSERT_USER_WITHOUT_PROFILE = """
|
||||
INSERT INTO users (username, email, password_hash, salt)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
INSERT_USER_WITH_PROFILE = """
|
||||
INSERT INTO users (username, email, password_hash, salt, profile_image_path)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"""
|
||||
|
||||
SELECT_BY_USERNAME = """
|
||||
SELECT * FROM users WHERE username = ?
|
||||
"""
|
||||
|
||||
SELECT_BY_EMAIL = """
|
||||
SELECT * FROM users WHERE email = ?
|
||||
"""
|
||||
|
||||
SELECT_BY_ID = """
|
||||
SELECT * FROM users WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_BY_USERNAME_LIKE = """
|
||||
SELECT * FROM users WHERE username LIKE ?
|
||||
"""
|
||||
|
||||
DELETE_USER_BY_USERNAME = """
|
||||
DELETE FROM users WHERE username = ?
|
||||
"""
|
||||
|
||||
UPDATE_PROFILE_IMAGE_PATH_BY_USERNAME = """
|
||||
UPDATE users SET profile_image_path = ? WHERE username = ?
|
||||
"""
|
||||
|
||||
UPDATE_PROFILE_IMAGE_PATH_BY_ID = """
|
||||
UPDATE users SET profile_image_path = ? WHERE id = ?
|
||||
"""
|
||||
|
||||
UPDATE_USER_BY_ID = """
|
||||
UPDATE users SET {} WHERE id = ?
|
||||
"""
|
||||
|
||||
SELECT_USER_BY_EMAIL_AND_NOT_ID = """
|
||||
SELECT * FROM users WHERE email = ? AND id != ?
|
||||
"""
|
||||
64
Backend/utils/run_server.py
Normal file
64
Backend/utils/run_server.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
# 프로젝트 루트를 Python 경로에 추가
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="싸이월드 - 추억 속으로",
|
||||
description="2000년대 감성의 소셜 네트워크 서비스",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
|
||||
app.mount("/public", StaticFiles(directory="public"), name="public")
|
||||
|
||||
|
||||
# 정적 파일 서빙
|
||||
async def init_folders(app: FastAPI):
|
||||
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
|
||||
|
||||
|
||||
async def init_db():
|
||||
|
||||
for service in os.listdir("Backend/services"):
|
||||
try:
|
||||
if service.endswith(".py"):
|
||||
module_name = "Backend.services." + service[:-3]
|
||||
module = __import__(module_name, fromlist=[""])
|
||||
service_class_name = service[:-3].split("_")[0] + "Service"
|
||||
service_class_name = service_class_name.replace(
|
||||
service_class_name[0], service_class_name[0].upper(), 1
|
||||
)
|
||||
service_class = getattr(module, service_class_name)
|
||||
|
||||
await service_class.init_db()
|
||||
print(f"{service_class_name} : init_db")
|
||||
|
||||
except Exception as e:
|
||||
print(f"failed to init_db {service}")
|
||||
print(e)
|
||||
|
||||
|
||||
async def startup_event():
|
||||
await init_folders(app)
|
||||
await init_db()
|
||||
|
||||
|
||||
def init_FastAPI() -> FastAPI:
|
||||
app.add_event_handler("startup", startup_event)
|
||||
return app
|
||||
Loading…
Add table
Add a link
Reference in a new issue