This commit is contained in:
SR07 2025-05-26 16:59:35 +09:00
parent f4d0a290c3
commit b78f7c8d3b
9 changed files with 153 additions and 15 deletions

View File

@ -2,7 +2,7 @@ from typing import List
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.schemas.character import CharacterCreate, CharacterResponse from app.schemas.character import CharacterCreate, CharacterResponse, CharacterUpdateRequest
from app.schemas.homework import HomeworkSelectableResponse from app.schemas.homework import HomeworkSelectableResponse
from app.crud.character import create_character, get_characters_by_user from app.crud.character import create_character, get_characters_by_user
from app.services.character_homework_service import get_homeworks_with_assignment_status, assign_homework_to_character, unassign_homework_from_character from app.services.character_homework_service import get_homeworks_with_assignment_status, assign_homework_to_character, unassign_homework_from_character
@ -60,3 +60,36 @@ def unassign_homework(
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):
return unassign_homework_from_character(db, current_user.id, character_id, homework_type_id) return unassign_homework_from_character(db, current_user.id, character_id, homework_type_id)
@router.put("/{character_id}")
def update_character(
character_id: int,
req: CharacterUpdateRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
character = db.query(Character).filter(Character.id == character_id).first()
if not character or character.user_id != current_user.id:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
character.name = req.name
character.server = req.server
character.power = req.power
db.commit()
return {"message": "캐릭터가 수정되었습니다."}
@router.delete("/{character_id}")
def delete_character(
character_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
character = db.query(Character).filter(Character.id == character_id).first()
if not character or character.user_id != current_user.id:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
db.delete(character)
db.commit()
return {"message": "캐릭터가 삭제되었습니다."}

View File

@ -1,12 +1,12 @@
from datetime import datetime, time from datetime import datetime, time
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from app.core.deps import get_db, get_current_user from app.core.deps import get_db, get_current_user
from app.models.homework import HomeworkType from app.models.homework import HomeworkType
from app.models.user import User from app.models.user import User
from app.schemas.homework import HomeworkTypeCreate, HomeworkTypeResponse from app.schemas.homework import HomeworkTypeCreate, HomeworkTypeResponse, HomeworkTypeUpdateRequest
from app.crud.homework import create_homework_type, get_homework_types_by_user from app.crud.homework import create_homework_type, get_homework_types_by_user
router = APIRouter() router = APIRouter()
@ -37,3 +37,42 @@ def list_homework_types(
current_user: User = Depends(get_current_user) current_user: User = Depends(get_current_user)
): ):
return get_homework_types_by_user(current_user.id, db) return get_homework_types_by_user(current_user.id, db)
@router.put("/{homework_type_id}")
def update_homework_type(
homework_type_id: int,
req: HomeworkTypeUpdateRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
current_user = db.merge(current_user)
homework_type = db.query(HomeworkType).filter(HomeworkType.id == homework_type_id).first()
if not homework_type or homework_type.user_id != current_user.id:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
homework_type.name = req.name
homework_type.description = req.description
homework_type.repeat_type = req.repeat_type
homework_type.repeat_count = req.repeat_count
db.commit()
return {"message": "숙제가 수정되었습니다."}
@router.delete("/{homework_type_id}")
def delete_homework_type(
homework_type_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
current_user = db.merge(current_user)
homework_type = db.query(HomeworkType).filter(HomeworkType.id == homework_type_id).first()
if not homework_type or homework_type.user_id != current_user.id:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
db.delete(homework_type)
db.commit()
return {"message": "숙제가 삭제되었습니다."}

View File

@ -1,12 +1,16 @@
# app/api/user.py # app/api/user.py
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.logger import logger
import traceback
import sys
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.schemas.user import UserCreate, UserResponse from app.schemas.user import UserCreate, UserResponse, PasswordUpdateRequest
from app.crud.user import create_user from app.crud.user import create_user
from app.models.user import User from app.models.user import User
from app.core.database import SessionLocal from app.core.database import SessionLocal
from app.core.deps import get_current_user from app.core.deps import get_current_user
from app.core.security import verify_password, get_password_hash
router = APIRouter() router = APIRouter()
@ -25,3 +29,31 @@ def register_user(user_create: UserCreate, db: Session = Depends(get_db)):
@router.get("/me", response_model=UserResponse) @router.get("/me", response_model=UserResponse)
def get_my_profile(current_user: User = Depends(get_current_user)): def get_my_profile(current_user: User = Depends(get_current_user)):
return current_user return current_user
@router.put("/me/password", status_code=204)
def update_password(
pw_req: PasswordUpdateRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
try:
if not verify_password(pw_req.current_password, current_user.password_hash):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="현재 비밀번호가 일치하지 않습니다."
)
print("기존:", current_user.password_hash)
print("신규:", get_password_hash(pw_req.new_password))
current_user = db.merge(current_user) # 세션에 붙임
current_user.password_hash = get_password_hash(pw_req.new_password)
db.flush()
db.expunge(current_user)
print("✅ flush 완료됨. 커밋 시도 중...")
db.commit()
print("✅ 커밋 완료됨.")
except Exception as e:
logger.error(f"❌ 비밀번호 변경 중 예외 발생: {e}")
traceback.print_exc(file=sys.stdout) # ← 여기가 핵심
raise

View File

@ -1,13 +1,20 @@
# app/core/database.py # app/core/database.py
from sqlalchemy import create_engine from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker, declarative_base from sqlalchemy.orm import sessionmaker, declarative_base
from app.core.config import settings from app.core.config import settings
from sqlalchemy.engine import Engine
import traceback
engine = create_engine(settings.database_url, echo=True) engine = create_engine(settings.database_url, echo=True, future=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() Base = declarative_base()
@event.listens_for(Engine, "handle_error")
def receive_handle_error(exception_context):
print("🔥 SQLAlchemy DB 에러 감지!")
traceback.print_exc()
def get_db(): def get_db():
db = SessionLocal() db = SessionLocal()
try: try:

View File

@ -1,7 +1,8 @@
#pp/main.py #pp/main.py
from fastapi import FastAPI from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
import traceback
from app.api import user, auth, character, homework, character_homework, dashboard from app.api import user, auth, character, homework, character_homework, dashboard
@ -17,13 +18,23 @@ app = FastAPI(
root_path="/api" root_path="/api"
) )
@app.middleware("http")
async def log_exceptions_middleware(request: Request, call_next):
try:
response = await call_next(request)
return response
except Exception as exc:
print("❌ 전역 예외 발생:")
traceback.print_exc()
raise
origins = [ origins = [
"https://sukjenogi.biryu2000.kr", # 프론트 도메인 "https://sukjenogi.biryu2000.kr", # 프론트 도메인
] ]
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=origins, allow_origins=["*"],
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],

View File

@ -1,6 +1,7 @@
# app/models/user.py # app/models/user.py
from sqlalchemy import Column, Integer, String, DateTime from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from datetime import datetime from datetime import datetime
from app.core.config import Base from app.core.config import Base
@ -10,8 +11,8 @@ class User(Base):
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, nullable=False, index=True) email = Column(String, unique=True, nullable=False, index=True)
password_hash = Column(String, nullable=False) password_hash = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow) created_at = Column(DateTime, server_default=func.now(), nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
characters = relationship("Character", back_populates="user") characters = relationship("Character", back_populates="user")
homework_types = relationship("HomeworkType", back_populates="user", cascade="all, delete") homework_types = relationship("HomeworkType", back_populates="user", cascade="all, delete")

View File

@ -1,6 +1,6 @@
# app/schemas/character.py # app/schemas/character.py
from pydantic import BaseModel from pydantic import BaseModel, constr, conint
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
@ -23,3 +23,8 @@ class CharacterResponse(BaseModel):
class Config: class Config:
orm_mode = True orm_mode = True
class CharacterUpdateRequest(BaseModel):
name: constr(min_length=1)
server: constr(min_length=1)
power: conint(ge=0) # 0 이상 정수

View File

@ -1,6 +1,6 @@
# app/schemas/homework.py # app/schemas/homework.py
from pydantic import BaseModel from pydantic import BaseModel, constr, conint
from datetime import time, datetime from datetime import time, datetime
from typing import Optional from typing import Optional
@ -28,4 +28,10 @@ class HomeworkSelectableResponse(BaseModel):
title: str title: str
assigned: str # 'Y' or 'N' assigned: str # 'Y' or 'N'
reset_type: str reset_type: str
clear_count: int clear_count: int
class HomeworkTypeUpdateRequest(BaseModel):
name: constr(min_length=1)
description: str | None = None
repeat_type: constr(min_length=1)
repeat_count: conint(ge=1)

View File

@ -1,6 +1,6 @@
# app/schemas/user.py # app/schemas/user.py
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr, constr
from datetime import datetime from datetime import datetime
# 사용자 생성 요청용 (회원가입) # 사용자 생성 요청용 (회원가입)
@ -14,5 +14,9 @@ class UserResponse(BaseModel):
email: EmailStr email: EmailStr
created_at: datetime created_at: datetime
class PasswordUpdateRequest(BaseModel):
current_password: constr(min_length=4)
new_password: constr(min_length=6)
class Config: class Config:
orm_mode = True orm_mode = True