v0.2
This commit is contained in:
parent
f4d0a290c3
commit
b78f7c8d3b
@ -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": "캐릭터가 삭제되었습니다."}
|
||||||
@ -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": "숙제가 삭제되었습니다."}
|
||||||
@ -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
|
||||||
@ -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:
|
||||||
|
|||||||
15
app/main.py
15
app/main.py
@ -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=["*"],
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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 이상 정수
|
||||||
@ -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
|
||||||
|
|
||||||
@ -29,3 +29,9 @@ class HomeworkSelectableResponse(BaseModel):
|
|||||||
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)
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user