Compare commits

..

No commits in common. "93d92a0686ec571e0cb4c8035c52d8c9baa5b691" and "694593aadd5e4dc350c9b8fb2c4ac8c7c4371c50" have entirely different histories.

25 changed files with 95 additions and 626 deletions

View File

@ -1,19 +0,0 @@
# SukjeNogi Backend
숙제노기 프로젝트의 백엔드 서버입니다. FastAPI로 작성되었으며 캐릭터별 과제 관리 기능 등을 REST API로 제공합니다.
## 실행 방법
### Docker 사용
```bash
docker build -t sukjenogi-backend .
docker run -d --env-file .env -p 8000:8000 sukjenogi-backend
```
### docker-compose 사용
`docker-compose.yml` 파일에 필요한 환경변수를 정의한 뒤 다음 명령으로 실행할 수 있습니다.
```bash
docker compose up -d --build
```
서버가 시작되면 `http://localhost:8000` 에서 서비스에 접근할 수 있습니다.

View File

@ -2,7 +2,7 @@
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from app.core.deps import get_db
from app.core.database import SessionLocal
from app.models.user import User
from app.core.security import verify_password, create_access_token
from pydantic import BaseModel, EmailStr
@ -11,6 +11,14 @@ from datetime import timedelta
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# 로그인 요청 스키마
class LoginRequest(BaseModel):
email: EmailStr
@ -41,4 +49,4 @@ def check_email_availability(
db: Session = Depends(get_db)
):
user = db.query(User).filter(User.email == email).first()
return {"available": user is None}
return {"available": user is None}

View File

@ -75,8 +75,7 @@ def update_character(
character.name = req.name
character.server = req.server
character.combat_power = req.power
character.is_public = req.is_public
character.power = req.power
db.commit()
return {"message": "캐릭터가 수정되었습니다."}
@ -119,6 +118,7 @@ def update_character_order(
for update in updates:
character = db.query(Character).filter_by(id=update.id, user_id=user.id).first()
if character:
character.order = update.order
character.order = update.order
db.add(character)
db.commit()

View File

@ -1,104 +0,0 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.core.deps import get_db, get_current_user
from app.schemas.friend import (
FriendRequestCreate,
FriendRequestResponse,
FriendResponse,
FriendListItem,
)
from app.schemas.character import CharacterResponse
from app.schemas.dashboard import DashboardHomework
from app.services import friend_service
from app.models.user import User
router = APIRouter()
@router.post("/request", response_model=FriendRequestResponse)
def send_request(
request_data: FriendRequestCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return friend_service.send_friend_request(db, current_user.id, request_data.to_user_email)
@router.get("/requests/received", response_model=list[FriendRequestResponse])
def get_received_requests(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return friend_service.get_received_requests(db, current_user.id)
@router.get("/requests/sent", response_model=list[FriendRequestResponse])
def get_sent_requests(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return friend_service.get_sent_requests(db, current_user.id)
@router.post("/requests/{request_id}/cancel")
def cancel_sent_request(
request_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
friend_service.cancel_sent_request(db, request_id, current_user.id)
return {"detail": "요청을 취소했습니다."}
@router.post("/requests/{request_id}/respond")
def respond_to_request(
request_id: int,
accept: bool,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
friend_service.respond_to_request(db, request_id, current_user.id, accept)
return {"detail": "요청을 처리했습니다."}
@router.get("/list", response_model=list[FriendListItem])
def get_friend_list(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return friend_service.get_friend_list(db, current_user.id)
@router.get("/{friend_id}/characters", response_model=list[CharacterResponse])
def get_friend_characters(
friend_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return friend_service.get_public_characters_of_friend(db, current_user.id, friend_id)
@router.get(
"/{friend_id}/characters/{character_id}/homeworks",
response_model=list[DashboardHomework],
)
def get_friend_character_homeworks(
friend_id: int,
character_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return friend_service.get_public_homeworks_of_friend_character(
db,
current_user.id,
friend_id,
character_id,
)
@router.delete("/{friend_id}")
def delete_friend(
friend_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
friend_service.delete_friend(db, current_user.id, friend_id)
return {"detail": "친구가 삭제되었습니다."}

View File

@ -24,7 +24,6 @@ def register_homework_type(
reset_type=homework_data.reset_type,
reset_time=homework_data.reset_time or time(6, 0),
clear_count=homework_data.clear_count or 0,
is_public=homework_data.is_public,
created_at=datetime.utcnow(),
)
db.add(homework_type)
@ -53,11 +52,10 @@ def update_homework_type(
if not homework_type or homework_type.user_id != current_user.id:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
homework_type.title = req.title
homework_type.name = req.name
homework_type.description = req.description
homework_type.reset_type = req.reset_type
homework_type.clear_count = req.clear_count
homework_type.is_public = req.is_public
homework_type.repeat_type = req.repeat_type
homework_type.repeat_count = req.repeat_count
db.commit()
return {"message": "숙제가 수정되었습니다."}

View File

@ -1,17 +1,16 @@
# app/api/user.py
from fastapi import APIRouter, Depends, HTTPException, status, Query
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.logger import logger
import traceback
import sys
from sqlalchemy.orm import Session
from app.schemas.user import UserCreate, UserResponse, PasswordUpdateRequest, UserPublicInfoResponse, UserByCharacterResponse
from app.schemas.user import UserCreate, UserResponse, PasswordUpdateRequest
from app.crud.user import create_user
from app.models.user import User
from app.core.database import SessionLocal
from app.core.deps import get_current_user
from app.core.security import verify_password, get_password_hash
from app.services import user_service
router = APIRouter()
@ -57,20 +56,4 @@ def update_password(
except Exception as e:
logger.error(f"❌ 비밀번호 변경 중 예외 발생: {e}")
traceback.print_exc(file=sys.stdout) # ← 여기가 핵심
raise
@router.get("/public-info", response_model=UserPublicInfoResponse)
def get_public_info(
email: str = Query(...),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
return user_service.get_user_public_info(db, current_user.id, email)
@router.get("/by-character", response_model=UserByCharacterResponse)
def get_by_character(
server: str,
name: str,
db: Session = Depends(get_db)
):
return user_service.get_user_by_character(db, server, name)
raise

View File

@ -1,6 +1,7 @@
# app/core/config.py
from pydantic_settings import BaseSettings
from sqlalchemy.orm import declarative_base
class Settings(BaseSettings):
database_url: str
@ -13,4 +14,6 @@ class Settings(BaseSettings):
settings = Settings()
# 베이스 클래스
Base = declarative_base()

View File

@ -10,8 +10,6 @@ engine = create_engine(settings.database_url, echo=True, future=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
import app.models # ✅ 그대로 유지
@event.listens_for(Engine, "handle_error")
def receive_handle_error(exception_context):
print("🔥 SQLAlchemy DB 에러 감지!")
@ -24,3 +22,5 @@ def get_db():
finally:
db.close()
# 세션 클래스 생성
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

@ -21,7 +21,6 @@ def create_character(user_id: int, character_data: CharacterCreate, db: Session)
server=character_data.server,
job=character_data.job,
combat_power=character_data.combat_power, # ← 수동 입력 허용
is_public=character_data.is_public,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow(),
)

View File

@ -5,12 +5,9 @@ from app.schemas.homework import HomeworkTypeCreate
def create_homework_type(user_id: int, data: HomeworkTypeCreate, db: Session):
new_homework = HomeworkType(
user_id=user_id,
title=data.title,
description=data.description,
reset_type=data.reset_type,
name=data.name,
reset_time=data.reset_time,
clear_count=data.clear_count,
is_public=data.is_public,
)
db.add(new_homework)
db.commit()

View File

@ -1,5 +1,4 @@
#pp/main.py
from app.models import User, Character, HomeworkType, Friend, FriendRequest # 👈 명시적 import!
from fastapi import FastAPI, Request, Depends
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
@ -8,37 +7,37 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordBearer
import traceback
from app.api import user, auth, character, homework, character_homework, dashboard, friend
from app.api import user, auth, character, homework, character_homework, dashboard
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
# app = FastAPI(
# title="숙제노기 API",
# description="마비노기 모바일 숙제 관리용 백엔드 API",
# version="0.1.0",
# docs_url="/docs",
# redoc_url=None,
# openapi_url="/openapi.json",
# root_path="/api"
# )
app = FastAPI(
title="숙제노기 API",
description="마비노기 모바일 숙제 관리용 백엔드 API",
version="0.1.0",
docs_url="/docs",
docs_url=None,
redoc_url=None,
openapi_url="/openapi.json",
root_path="/api"
openapi_url=None
)
# app = FastAPI(
# docs_url=None,
# redoc_url=None,
# openapi_url=None
# )
#
# @app.get("/docs", include_in_schema=False)
# def custom_docs(user=Depends(get_current_user)):
# return get_swagger_ui_html(openapi_url="/openapi.json", title="Sukjenogi API Docs")
#
# @app.get("/openapi.json", include_in_schema=False)
# def custom_openapi(user=Depends(get_current_user)):
# return get_openapi(
# title="Sukjenogi API",
# version="0.2",
# routes=app.routes
# )
@app.get("/docs", include_in_schema=False)
def custom_docs(user=Depends(get_current_user)):
return get_swagger_ui_html(openapi_url="/openapi.json", title="Sukjenogi API Docs")
@app.get("/openapi.json", include_in_schema=False)
def custom_openapi(user=Depends(get_current_user)):
return get_openapi(
title="Sukjenogi API",
version="0.2",
routes=app.routes
)
@app.middleware("http")
async def log_exceptions_middleware(request: Request, call_next):
@ -58,8 +57,7 @@ origins = [
app.add_middleware(
CORSMiddleware,
# allow_origins=origins,
allow_origins=["*"],
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@ -71,7 +69,6 @@ app.include_router(character.router, prefix="/characters", tags=["Characters"])
app.include_router(homework.router, prefix="/homeworks", tags=["Homeworks"])
app.include_router(character_homework.router, prefix="/characterHomework", tags=["Character Homeworks"])
app.include_router(dashboard.router, prefix="/dashboard", tags=["Dashboard"])
app.include_router(friend.router, prefix="/friends", tags=["Friends"])
@app.get("/")
def read_root():

View File

@ -1,5 +0,0 @@
# app/models/__init__.py
from app.models.user import User
from app.models.character import Character
from app.models.homework import HomeworkType
from app.models.friend import Friend, FriendRequest

View File

@ -1,7 +1,7 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, func
from sqlalchemy.orm import relationship
from datetime import datetime
from app.core.database import Base
from app.core.config import Base
class Character(Base):
@ -22,8 +22,6 @@ class Character(Base):
order = Column(Integer, default=0)
is_public = Column(Boolean, default=False, nullable=False)
class CharacterHomework(Base):
__tablename__ = "character_homeworks"

View File

@ -1,41 +0,0 @@
from sqlalchemy import Column, Integer, Enum, ForeignKey, DateTime, CheckConstraint, UniqueConstraint
from sqlalchemy.orm import relationship
from datetime import datetime
from app.core.database import Base
import enum
class FriendRequestStatus(enum.Enum):
pending = "pending"
accepted = "accepted"
rejected = "rejected"
cancelled = "cancelled"
class FriendRequest(Base):
__tablename__ = "friend_requests"
id = Column(Integer, primary_key=True, index=True)
from_user_id = Column(Integer, ForeignKey("users.id"))
to_user_id = Column(Integer, ForeignKey("users.id"))
status = Column(Enum(FriendRequestStatus), default=FriendRequestStatus.pending, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow)
# 🔽 문자열로 참조
from_user = relationship("User", foreign_keys=[from_user_id], back_populates="sent_requests")
to_user = relationship("User", foreign_keys=[to_user_id], back_populates="received_requests")
class Friend(Base):
__tablename__ = "friends"
id = Column(Integer, primary_key=True, index=True)
user_id_1 = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
user_id_2 = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
__table_args__ = (
UniqueConstraint("user_id_1", "user_id_2", name="unique_friend_pair"),
CheckConstraint("user_id_1 < user_id_2", name="check_user_order"),
)
user1 = relationship("User", foreign_keys=[user_id_1], back_populates="friendships1")
user2 = relationship("User", foreign_keys=[user_id_2], back_populates="friendships2")

View File

@ -1,8 +1,8 @@
from sqlalchemy import Column, Integer, String, Time, ForeignKey, DateTime, Boolean
from sqlalchemy import Column, Integer, String, Time, ForeignKey, DateTime
from sqlalchemy.orm import relationship
from datetime import time, datetime
from app.core.database import Base
from app.core.config import Base
class HomeworkType(Base):
__tablename__ = "homework_types"
@ -20,5 +20,3 @@ class HomeworkType(Base):
assigned_characters = relationship("CharacterHomework", back_populates="homework_type", cascade="all, delete")
order = Column(Integer, default=0)
is_public = Column(Boolean, default=False, nullable=False)

View File

@ -1,8 +1,9 @@
# app/models/user.py
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from datetime import datetime
from app.core.database import Base
from app.core.config import Base
class User(Base):
__tablename__ = "users"
@ -13,12 +14,5 @@ class User(Base):
created_at = Column(DateTime, server_default=func.now(), nullable=False)
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
from app.models.character import Character
characters = relationship(Character, back_populates="user")
characters = relationship("Character", back_populates="user")
homework_types = relationship("HomeworkType", back_populates="user", cascade="all, delete")
# 🔽 문자열만 사용하고 foreign_keys 생략 (권장)
sent_requests = relationship("FriendRequest", back_populates="from_user", foreign_keys="FriendRequest.from_user_id")
received_requests = relationship("FriendRequest", back_populates="to_user", foreign_keys="FriendRequest.to_user_id")
friendships1 = relationship("Friend", back_populates="user1", foreign_keys="Friend.user_id_1")
friendships2 = relationship("Friend", back_populates="user2", foreign_keys="Friend.user_id_2")

View File

@ -10,7 +10,6 @@ class CharacterCreate(BaseModel):
server: Optional[str] = None
job: Optional[str] = None
combat_power: Optional[int] = None # ← 추가
is_public: bool = False
# 캐릭터 응답용
class CharacterResponse(BaseModel):
@ -19,7 +18,6 @@ class CharacterResponse(BaseModel):
server: Optional[str]
job: Optional[str]
combat_power: Optional[int] # ← 추가
is_public: bool
auto_synced_at: Optional[datetime]
created_at: datetime
@ -30,7 +28,6 @@ class CharacterUpdateRequest(BaseModel):
name: constr(min_length=1)
server: constr(min_length=1)
power: conint(ge=0) # 0 이상 정수
is_public: bool
class CharacterDetailResponse(BaseModel):
id: int
@ -38,7 +35,6 @@ class CharacterDetailResponse(BaseModel):
server: str
combat_power: int
user_id: int
is_public: bool
created_at: datetime
updated_at: datetime

View File

@ -1,46 +0,0 @@
from pydantic import BaseModel
from datetime import datetime
from enum import Enum
class FriendRequestStatus(str, Enum):
pending = "pending"
accepted = "accepted"
rejected = "rejected"
cancelled = "cancelled"
class FriendRequestCreate(BaseModel):
to_user_email: str
class FriendRequestResponse(BaseModel):
id: int
from_user_id: int
to_user_id: int
from_user_email: str | None = None
to_user_email: str | None = None
status: FriendRequestStatus
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class FriendResponse(BaseModel):
id: int
user_id_1: int
user_id_2: int
created_at: datetime
class Config:
orm_mode = True
class FriendListItem(BaseModel):
id: int
email: str
class Config:
orm_mode = True

View File

@ -10,7 +10,6 @@ class HomeworkTypeCreate(BaseModel):
reset_type: str
reset_time: Optional[time] = None
clear_count: Optional[int] = 0
is_public: bool = False
class HomeworkTypeResponse(BaseModel):
id: int
@ -19,7 +18,6 @@ class HomeworkTypeResponse(BaseModel):
reset_type: str
reset_time: time
clear_count: int
is_public: bool
created_at: datetime
class Config:
@ -33,11 +31,10 @@ class HomeworkSelectableResponse(BaseModel):
clear_count: int
class HomeworkTypeUpdateRequest(BaseModel):
title: constr(min_length=1)
name: constr(min_length=1)
description: str | None = None
reset_type: constr(min_length=1)
clear_count: conint(ge=1)
is_public: bool
repeat_type: constr(min_length=1)
repeat_count: conint(ge=1)
class HomeworkTypeDetailResponse(BaseModel):
id: int
@ -47,7 +44,6 @@ class HomeworkTypeDetailResponse(BaseModel):
reset_type: str
reset_time: time
clear_count: int
is_public: bool
created_at: datetime
model_config = {

View File

@ -20,19 +20,3 @@ class PasswordUpdateRequest(BaseModel):
class Config:
orm_mode = True
class UserPublicInfoResponse(BaseModel):
id: int
email: str
is_friend: bool
request_sent: bool
request_received: bool
class UserByCharacterResponse(BaseModel):
user_id: int
email: str
character_id: int
character_name: str
server: str
is_public: bool

View File

@ -117,3 +117,36 @@ def update_homework_completion(
db.commit()
return {"message": "숙제 완료 상태가 업데이트되었습니다."}
def update_homework_completion(
db: Session,
user_id: int,
character_id: int,
homework_type_id: int,
new_complete_cnt: int
):
character = db.query(Character).filter_by(id=character_id, user_id=user_id).first()
if not character:
raise HTTPException(status_code=404, detail="캐릭터가 없습니다.")
homework = db.query(HomeworkType).filter_by(id=homework_type_id, user_id=user_id).first()
if not homework:
raise HTTPException(status_code=404, detail="숙제를 찾을 수 없습니다.")
ch = db.query(CharacterHomework).filter_by(
character_id=character_id,
homework_type_id=homework_type_id
).first()
if not ch:
raise HTTPException(status_code=404, detail="지정된 숙제가 없습니다.")
if new_complete_cnt > homework.clear_count:
raise HTTPException(status_code=400, detail="완료 횟수가 숙제 클리어 기준을 초과했습니다.")
ch.complete_cnt = new_complete_cnt
ch.last_completed_at = datetime.utcnow()
ch.updated_at = datetime.utcnow()
ch.is_done = (new_complete_cnt == homework.clear_count)
db.commit()
return {"message": "숙제 완료 상태가 업데이트되었습니다."}

View File

@ -1,232 +0,0 @@
from sqlalchemy.orm import Session, aliased
from sqlalchemy import select
from app.models.friend import FriendRequest, Friend, FriendRequestStatus
from app.models.user import User
from app.models.character import Character, CharacterHomework
from app.models.homework import HomeworkType
from fastapi import HTTPException, status
from datetime import datetime
def send_friend_request(db: Session, from_user_id: int, to_user_email: str):
to_user = db.query(User).filter(User.email == to_user_email).first()
if not to_user:
raise HTTPException(status_code=404, detail="해당 이메일의 유저를 찾을 수 없습니다.")
if to_user.id == from_user_id:
raise HTTPException(status_code=400, detail="자기 자신에게 친구 요청을 보낼 수 없습니다.")
# 이미 친구인지 확인
user_ids = sorted([from_user_id, to_user.id])
existing_friend = db.query(Friend).filter(
Friend.user_id_1 == user_ids[0],
Friend.user_id_2 == user_ids[1]
).first()
if existing_friend:
raise HTTPException(status_code=400, detail="이미 친구 상태입니다.")
# 이미 pending 요청이 있는지 확인
existing_request = db.query(FriendRequest).filter(
FriendRequest.from_user_id == from_user_id,
FriendRequest.to_user_id == to_user.id,
FriendRequest.status == FriendRequestStatus.pending
).first()
if existing_request:
raise HTTPException(status_code=400, detail="이미 친구 요청을 보낸 상태입니다.")
friend_request = FriendRequest(
from_user_id=from_user_id,
to_user_id=to_user.id,
)
db.add(friend_request)
db.commit()
db.refresh(friend_request)
return friend_request
def get_received_requests(db: Session, user_id: int):
sender = aliased(User)
receiver = aliased(User)
stmt = (
select(
FriendRequest.id,
FriendRequest.from_user_id,
FriendRequest.to_user_id,
sender.email.label("from_user_email"),
receiver.email.label("to_user_email"),
FriendRequest.status,
FriendRequest.created_at,
FriendRequest.updated_at,
)
.join(sender, FriendRequest.from_user_id == sender.id)
.join(receiver, FriendRequest.to_user_id == receiver.id)
.where(
FriendRequest.to_user_id == user_id,
FriendRequest.status == FriendRequestStatus.pending,
)
)
return db.execute(stmt).mappings().all()
def get_sent_requests(db: Session, user_id: int):
sender = aliased(User)
receiver = aliased(User)
stmt = (
select(
FriendRequest.id,
FriendRequest.from_user_id,
FriendRequest.to_user_id,
sender.email.label("from_user_email"),
receiver.email.label("to_user_email"),
FriendRequest.status,
FriendRequest.created_at,
FriendRequest.updated_at,
)
.join(sender, FriendRequest.from_user_id == sender.id)
.join(receiver, FriendRequest.to_user_id == receiver.id)
.where(
FriendRequest.from_user_id == user_id,
FriendRequest.status == FriendRequestStatus.pending,
)
)
return db.execute(stmt).mappings().all()
def cancel_sent_request(db: Session, request_id: int, user_id: int):
request = db.query(FriendRequest).filter(
FriendRequest.id == request_id,
FriendRequest.from_user_id == user_id
).first()
if not request:
raise HTTPException(status_code=404, detail="요청을 찾을 수 없습니다.")
request.status = FriendRequestStatus.cancelled
request.updated_at = datetime.utcnow()
db.commit()
def respond_to_request(db: Session, request_id: int, user_id: int, accept: bool):
request = db.query(FriendRequest).filter(
FriendRequest.id == request_id,
FriendRequest.to_user_id == user_id,
FriendRequest.status == FriendRequestStatus.pending
).first()
if not request:
raise HTTPException(status_code=404, detail="요청을 찾을 수 없습니다.")
request.status = FriendRequestStatus.accepted if accept else FriendRequestStatus.rejected
request.updated_at = datetime.utcnow()
# 친구 수락 시 friends 테이블에도 저장
if accept:
user_ids = sorted([request.from_user_id, request.to_user_id])
friend = Friend(
user_id_1=user_ids[0],
user_id_2=user_ids[1]
)
db.add(friend)
db.commit()
def get_friend_list(db: Session, user_id: int):
friends = db.query(Friend).filter(
(Friend.user_id_1 == user_id) | (Friend.user_id_2 == user_id)
).all()
result = []
for f in friends:
friend_id = f.user_id_2 if f.user_id_1 == user_id else f.user_id_1
friend = db.query(User).filter(User.id == friend_id).first()
friend_email = friend.email if friend else None
result.append({"id": friend_id, "email": friend_email})
return result
def get_public_characters_of_friend(db: Session, current_user_id: int, friend_id: int):
# 친구 관계 확인
user_ids = sorted([current_user_id, friend_id])
is_friend = db.query(Friend).filter(
Friend.user_id_1 == user_ids[0],
Friend.user_id_2 == user_ids[1]
).first()
if not is_friend:
raise HTTPException(status_code=403, detail="친구가 아닙니다.")
# 공개된 캐릭터 목록
characters = db.query(Character).filter(
Character.user_id == friend_id,
Character.is_public == True
).all()
return characters
def get_public_homeworks_of_friend_character(
db: Session,
current_user_id: int,
friend_id: int,
character_id: int
):
# 1. 친구인지 확인
user_ids = sorted([current_user_id, friend_id])
is_friend = db.query(Friend).filter(
Friend.user_id_1 == user_ids[0],
Friend.user_id_2 == user_ids[1]
).first()
if not is_friend:
raise HTTPException(status_code=403, detail="친구가 아닙니다.")
# 2. 해당 캐릭터가 공개인지 확인
character = db.query(Character).filter(
Character.id == character_id,
Character.user_id == friend_id,
Character.is_public == True
).first()
if not character:
raise HTTPException(status_code=404, detail="공개된 캐릭터를 찾을 수 없습니다.")
# 3. 공개된 숙제만 조회하며 완료 횟수도 포함
rows = (
db.query(
HomeworkType.id.label("homework_id"),
HomeworkType.title,
HomeworkType.reset_type,
HomeworkType.clear_count,
CharacterHomework.complete_cnt,
)
.join(CharacterHomework, CharacterHomework.homework_type_id == HomeworkType.id)
.filter(
CharacterHomework.character_id == character_id,
HomeworkType.is_public == True,
)
.order_by(HomeworkType.order.asc())
.all()
)
return [
{
"homework_id": row[0],
"title": row[1],
"reset_type": row[2],
"clear_count": row[3],
"complete_cnt": row[4],
}
for row in rows
]
def delete_friend(db: Session, user_id: int, friend_id: int):
user_ids = sorted([user_id, friend_id])
friend = db.query(Friend).filter(
Friend.user_id_1 == user_ids[0],
Friend.user_id_2 == user_ids[1]
).first()
if not friend:
raise HTTPException(status_code=404, detail="친구 관계가 존재하지 않습니다.")
db.delete(friend)
db.commit()

View File

@ -1,68 +0,0 @@
from fastapi import HTTPException
from sqlalchemy.orm import Session
from app.models.user import User
from app.models.friend import Friend, FriendRequest, FriendRequestStatus
from app.models.character import Character
def get_user_public_info(
db: Session,
current_user_id: int,
target_email: str
):
target_user = db.query(User).filter(User.email == target_email).first()
if not target_user:
raise HTTPException(status_code=404, detail="해당 유저를 찾을 수 없습니다.")
if target_user.id == current_user_id:
raise HTTPException(status_code=400, detail="자기 자신은 검색할 수 없습니다.")
user_ids = sorted([current_user_id, target_user.id])
is_friend = db.query(Friend).filter(
Friend.user_id_1 == user_ids[0],
Friend.user_id_2 == user_ids[1]
).first() is not None
request_sent = db.query(FriendRequest).filter(
FriendRequest.from_user_id == current_user_id,
FriendRequest.to_user_id == target_user.id,
FriendRequest.status == FriendRequestStatus.pending
).first() is not None
request_received = db.query(FriendRequest).filter(
FriendRequest.from_user_id == target_user.id,
FriendRequest.to_user_id == current_user_id,
FriendRequest.status == FriendRequestStatus.pending
).first() is not None
return {
"id": target_user.id,
"email": target_user.email,
"is_friend": is_friend,
"request_sent": request_sent,
"request_received": request_received,
}
def get_user_by_character(
db: Session,
server: str,
name: str
):
character = db.query(Character).filter(
Character.server == server,
Character.name == name
).first()
if not character:
raise HTTPException(status_code=404, detail="캐릭터를 찾을 수 없습니다.")
user = db.query(User).filter(User.id == character.user_id).first()
return {
"user_id": user.id,
"email": user.email,
"character_id": character.id,
"character_name": character.name,
"server": character.server,
"is_public": character.is_public
}

View File

@ -1,10 +1,10 @@
# create_db.py
from app.core.database import Base, engine
from app.core.config import Base
from app.core.database import engine
from app.models.user import User
from app.models.character import Character
from app.models.homework import HomeworkType
from app.models.character import CharacterHomework
from app.models.homework import HomeworkType, CharacterHomework
print("📦 DB 테이블 생성 중...")
Base.metadata.create_all(bind=engine)

View File

@ -7,4 +7,4 @@ python-jose[cryptography]
passlib[bcrypt]
python-dotenv
pydantic[email]
pydantic-settings
pydantic-settings