Skip to content

Commit 1053ed3

Browse files
authored
Merge pull request #15 from 9git9git/SCRUM-59-BE-Memos-API-개발
[SCRUM-59] ✨ Memos api 개발
2 parents 0a8e222 + ab6d45f commit 1053ed3

File tree

7 files changed

+393
-2
lines changed

7 files changed

+393
-2
lines changed

app/api/v1/endpoints/memo.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from app.schemas.base import ResponseBase
2+
from app.schemas.memo import MemoCreate, MemoResponse, MemoUpdate
3+
from app.services.memo import (
4+
select_today_memos_service,
5+
select_month_memos_service,
6+
select_month_memos_by_category_id_service,
7+
add_memo_service,
8+
edit_memo_service,
9+
delete_memo_service,
10+
)
11+
from app.db.session import get_db
12+
from fastapi import APIRouter, Depends, HTTPException, status
13+
from sqlalchemy.ext.asyncio import AsyncSession
14+
from typing import List
15+
from uuid import UUID
16+
17+
router = APIRouter()
18+
19+
20+
@router.get("/", response_model=ResponseBase[List[MemoResponse]])
21+
async def get_today_memos(
22+
user_id: UUID, db: AsyncSession = Depends(get_db)
23+
) -> ResponseBase[List[MemoResponse]]:
24+
try:
25+
memos = await select_today_memos_service(db, user_id)
26+
return ResponseBase(status_code=status.HTTP_200_OK, data=memos)
27+
except HTTPException as e:
28+
return ResponseBase(status_code=e.status_code, error=e.detail)
29+
except Exception as e:
30+
return ResponseBase(
31+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, error=str(e)
32+
)
33+
34+
35+
@router.get("/", response_model=ResponseBase[List[MemoResponse]])
36+
async def get_month_memos(
37+
user_id: UUID, year: int, month: int, db: AsyncSession = Depends(get_db)
38+
) -> ResponseBase[List[MemoResponse]]:
39+
try:
40+
memos = await select_month_memos_service(db, user_id, year, month)
41+
return ResponseBase(status_code=status.HTTP_200_OK, data=memos)
42+
except HTTPException as e:
43+
return ResponseBase(status_code=e.status_code, error=e.detail)
44+
except Exception as e:
45+
return ResponseBase(
46+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, error=str(e)
47+
)
48+
49+
50+
@router.get("/", response_model=ResponseBase[List[MemoResponse]])
51+
async def get_month_memos_by_category(
52+
user_id: UUID,
53+
category_id: UUID,
54+
year: int,
55+
month: int,
56+
db: AsyncSession = Depends(get_db),
57+
) -> ResponseBase[List[MemoResponse]]:
58+
try:
59+
memos = await select_month_memos_by_category_id_service(
60+
db, user_id, category_id, year, month
61+
)
62+
return ResponseBase(status_code=status.HTTP_200_OK, data=memos)
63+
except HTTPException as e:
64+
return ResponseBase(status_code=e.status_code, error=e.detail)
65+
except Exception as e:
66+
return ResponseBase(
67+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, error=str(e)
68+
)
69+
70+
71+
@router.post("/", response_model=ResponseBase[MemoResponse])
72+
async def post_memo(
73+
memo: MemoCreate,
74+
user_id: UUID,
75+
category_id: UUID,
76+
db: AsyncSession = Depends(get_db),
77+
) -> ResponseBase[MemoResponse]:
78+
try:
79+
memo = await add_memo_service(db, user_id, category_id, memo)
80+
return ResponseBase(status_code=status.HTTP_201_CREATED, data=memo)
81+
except HTTPException as e:
82+
return ResponseBase(status_code=e.status_code, error=e.detail)
83+
except Exception as e:
84+
return ResponseBase(
85+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, error=str(e)
86+
)
87+
88+
89+
@router.put("/{memo_id}", response_model=ResponseBase[MemoResponse])
90+
async def update_memo(
91+
user_id: UUID,
92+
memo_id: UUID,
93+
memo: MemoUpdate,
94+
db: AsyncSession = Depends(get_db),
95+
) -> ResponseBase[MemoResponse]:
96+
try:
97+
memo = await edit_memo_service(db, user_id, memo_id, memo)
98+
return ResponseBase(status_code=status.HTTP_200_OK, data=memo)
99+
except HTTPException as e:
100+
return ResponseBase(status_code=e.status_code, error=e.detail)
101+
except Exception as e:
102+
return ResponseBase(
103+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, error=str(e)
104+
)
105+
106+
107+
@router.delete("/{memo_id}", response_model=ResponseBase[bool])
108+
async def delete_memo(
109+
user_id: UUID,
110+
memo_id: UUID,
111+
db: AsyncSession = Depends(get_db),
112+
) -> ResponseBase[bool]:
113+
try:
114+
result = await delete_memo_service(db, user_id, memo_id)
115+
return ResponseBase(status_code=status.HTTP_200_OK, data=result)
116+
except HTTPException as e:
117+
return ResponseBase(status_code=e.status_code, error=e.detail)
118+
except Exception as e:
119+
return ResponseBase(
120+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, error=str(e)
121+
)

app/api/v1/router.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
character,
66
user_character,
77
comprehensive_evaluation,
8+
memo,
89
)
910

1011
router = APIRouter()
@@ -20,3 +21,8 @@
2021
prefix="/users/{user_id}/comprehensive_evaluations",
2122
tags=["comprehensive_evaluations"],
2223
)
24+
router.include_router(
25+
memo.router,
26+
prefix="/users/{user_id}/categories/{category_id}/memos",
27+
tags=["memos"],
28+
)

app/crud/memo.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from sqlalchemy.ext.asyncio import AsyncSession
2+
from uuid import UUID
3+
from datetime import datetime
4+
from typing import List
5+
from app.models.category import Memo
6+
from app.schemas.memo import MemoCreate, MemoResponse, MemoUpdate
7+
from app.utils.to_snake_case import camel_to_snake
8+
9+
10+
async def create_memo(
11+
db: AsyncSession, user_id: UUID, category_id: UUID, memo: MemoCreate
12+
) -> MemoResponse:
13+
14+
db_memo = Memo(
15+
user_id=user_id,
16+
category_id=category_id,
17+
title=memo.title,
18+
content=memo.content,
19+
start_date=memo.startDate,
20+
end_date=memo.endDate,
21+
)
22+
23+
db.add(db_memo)
24+
await db.commit()
25+
await db.refresh(db_memo)
26+
return db_memo
27+
28+
29+
async def read_memos(db: AsyncSession, user_id: UUID) -> List[MemoResponse]:
30+
db_memo = await db.execute(select(Memo).where(Memo.user_id == user_id))
31+
return db_memo.scalars().all()
32+
33+
34+
async def read_memo_by_id(
35+
db: AsyncSession, user_id: UUID, memo_id: UUID
36+
) -> MemoResponse:
37+
db_memo = await db.execute(
38+
select(Memo).where(Memo.id == memo_id, Memo.user_id == user_id)
39+
)
40+
return db_memo.scalars().first()
41+
42+
43+
async def read_memos_by_date(
44+
db: AsyncSession, user_id: UUID, date: datetime
45+
) -> List[MemoResponse]:
46+
db_memo = await db.execute(
47+
select(Memo).where(
48+
Memo.user_id == user_id, Memo.start_date <= date, date <= Memo.end_date
49+
)
50+
)
51+
return db_memo.scalars().all()
52+
53+
54+
async def read_memos_by_period(
55+
db: AsyncSession, user_id: UUID, start_date: datetime, end_date: datetime
56+
) -> List[MemoResponse]:
57+
db_memo = await db.execute(
58+
select(Memo).where(
59+
Memo.user_id == user_id,
60+
Memo.start_date >= start_date,
61+
Memo.end_date <= end_date,
62+
)
63+
)
64+
return db_memo.scalars().all()
65+
66+
67+
async def read_memos_by_category_id(
68+
db: AsyncSession, user_id: UUID, category_id: UUID
69+
) -> List[MemoResponse]:
70+
db_memo = await db.execute(
71+
select(Memo).where(Memo.user_id == user_id, Memo.category_id == category_id)
72+
)
73+
return db_memo.scalars().all()
74+
75+
76+
async def read_memos_by_period_and_category_id(
77+
db: AsyncSession,
78+
user_id: UUID,
79+
category_id: UUID,
80+
start_date: datetime,
81+
end_date: datetime,
82+
) -> List[MemoResponse]:
83+
db_memo = await db.execute(
84+
select(Memo).where(
85+
Memo.user_id == user_id,
86+
Memo.category_id == category_id,
87+
Memo.start_date >= start_date,
88+
Memo.end_date <= end_date,
89+
)
90+
)
91+
return db_memo.scalars().all()
92+
93+
94+
async def update_memo(
95+
db: AsyncSession, user_id: UUID, memo_id: UUID, memo_data: MemoUpdate
96+
) -> MemoResponse:
97+
db_memo = await read_memo_by_id(db, user_id, memo_id)
98+
99+
update_data = memo_data.model_dump(exclude_unset=True)
100+
101+
for key, value in update_data.items():
102+
snake_key = camel_to_snake(key)
103+
setattr(db_memo, snake_key, value)
104+
105+
await db.commit()
106+
await db.refresh(db_memo)
107+
return db_memo
108+
109+
110+
async def delete_memo(db: AsyncSession, user_id: UUID, memo_id: UUID) -> bool:
111+
delete_statement = delete(Memo).where(Memo.id == memo_id, Memo.user_id == user_id)
112+
113+
try:
114+
await db.execute(delete_statement)
115+
await db.commit()
116+
except Exception as e:
117+
await db.rollback()
118+
raise e
119+
return True

app/schemas/memo.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from app.schemas.base import BaseModel
2+
from uuid import UUID
3+
from datetime import datetime
4+
5+
6+
class MemoCreate(BaseModel):
7+
title: str
8+
content: str
9+
startDate: datetime
10+
endDate: datetime
11+
12+
13+
class MemoResponse(BaseModel):
14+
id: UUID
15+
category_id: UUID
16+
title: str
17+
content: str
18+
start_date: datetime
19+
end_date: datetime
20+
21+
22+
class MemoUpdate(BaseModel):
23+
title: str
24+
content: str
25+
startDate: datetime
26+
endDate: datetime

app/services/memo.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from app.schemas.memo import MemoCreate, MemoResponse, MemoUpdate
2+
from app.crud.memo import (
3+
create_memo,
4+
read_memo_by_id,
5+
read_memos_by_date,
6+
read_memos_by_period,
7+
read_memos_by_period_and_category_id,
8+
update_memo,
9+
delete_memo,
10+
)
11+
from typing import List
12+
from uuid import UUID
13+
from sqlalchemy.ext.asyncio import AsyncSession
14+
from fastapi import HTTPException, status
15+
from datetime import datetime, timedelta
16+
17+
18+
async def select_today_memos_service(
19+
db: AsyncSession, user_id: UUID
20+
) -> List[MemoResponse]:
21+
today = datetime.now().date()
22+
return await read_memos_by_date(db, user_id, today)
23+
24+
25+
async def select_month_memos_service(
26+
db: AsyncSession, user_id: UUID, year: int, month: int
27+
) -> List[MemoResponse]:
28+
29+
start_date = datetime(year, month, 1)
30+
end_date = datetime(year, month + 1, 1) - timedelta(days=1)
31+
return await read_memos_by_period(db, user_id, start_date, end_date)
32+
33+
34+
async def select_month_memos_by_category_id_service(
35+
db: AsyncSession, user_id: UUID, category_id: UUID, year: int, month: int
36+
) -> List[MemoResponse]:
37+
start_date = datetime(year, month, 1)
38+
end_date = datetime(year, month + 1, 1) - timedelta(days=1)
39+
return await read_memos_by_period_and_category_id(
40+
db, user_id, category_id, start_date, end_date
41+
)
42+
43+
44+
async def add_memo_service(
45+
db: AsyncSession, user_id: UUID, category_id: UUID, memo: MemoCreate
46+
) -> MemoResponse:
47+
return await create_memo(db, user_id, category_id, memo)
48+
49+
50+
async def edit_memo_service(
51+
db: AsyncSession, user_id: UUID, memo_id: UUID, memo: MemoUpdate
52+
) -> MemoResponse:
53+
try:
54+
db_memo = await read_memo_by_id(db, user_id, memo_id)
55+
if not db_memo:
56+
raise HTTPException(
57+
status_code=status.HTTP_404_NOT_FOUND, detail="메모를 찾을 수 없습니다."
58+
)
59+
60+
return await update_memo(db, user_id, memo_id, memo)
61+
except Exception as e:
62+
raise HTTPException(
63+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
64+
detail="데이터베이스 오류가 발생했습니다.",
65+
) from e
66+
67+
68+
async def delete_memo_service(db: AsyncSession, user_id: UUID, memo_id: UUID) -> bool:
69+
try:
70+
db_memo = await read_memo_by_id(db, user_id, memo_id)
71+
if not db_memo:
72+
raise HTTPException(
73+
status_code=status.HTTP_404_NOT_FOUND, detail="메모를 찾을 수 없습니다."
74+
)
75+
76+
return await delete_memo(db, user_id, memo_id)
77+
except Exception as e:
78+
raise HTTPException(
79+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
80+
detail="데이터베이스 오류가 발생했습니다.",
81+
) from e

0 commit comments

Comments
 (0)