JHyeok5 commited on
Commit
b91a49b
·
verified ·
1 Parent(s): 464ccd8

Upload folder using huggingface_hub

Browse files
lib/job_manager.py CHANGED
@@ -3,7 +3,7 @@ Job status management for course_generation_jobs table
3
  Provides helpers to update job progress from HuggingFace Space backend
4
  """
5
  import logging
6
- from datetime import datetime
7
  from typing import Optional, Dict, Any
8
  from db import get_supabase
9
 
@@ -39,7 +39,7 @@ async def update_job_progress(
39
  result = supabase.table("course_generation_jobs").update({
40
  "progress": progress,
41
  "progress_message": message,
42
- "updated_at": datetime.utcnow().isoformat()
43
  }).eq("id", job_id).execute()
44
 
45
  if result.data:
@@ -82,8 +82,8 @@ async def complete_job(
82
  "result": result,
83
  "progress": 100,
84
  "progress_message": "완료",
85
- "completed_at": datetime.utcnow().isoformat(),
86
- "updated_at": datetime.utcnow().isoformat()
87
  }).eq("id", job_id).execute()
88
 
89
  if update_result.data:
@@ -125,8 +125,8 @@ async def fail_job(
125
  "status": "failed",
126
  "error": error,
127
  "progress_message": f"실패: {error}",
128
- "completed_at": datetime.utcnow().isoformat(),
129
- "updated_at": datetime.utcnow().isoformat()
130
  }).eq("id", job_id).execute()
131
 
132
  if update_result.data:
 
3
  Provides helpers to update job progress from HuggingFace Space backend
4
  """
5
  import logging
6
+ from datetime import datetime, timezone
7
  from typing import Optional, Dict, Any
8
  from db import get_supabase
9
 
 
39
  result = supabase.table("course_generation_jobs").update({
40
  "progress": progress,
41
  "progress_message": message,
42
+ "updated_at": datetime.now(timezone.utc).isoformat()
43
  }).eq("id", job_id).execute()
44
 
45
  if result.data:
 
82
  "result": result,
83
  "progress": 100,
84
  "progress_message": "완료",
85
+ "completed_at": datetime.now(timezone.utc).isoformat(),
86
+ "updated_at": datetime.now(timezone.utc).isoformat()
87
  }).eq("id", job_id).execute()
88
 
89
  if update_result.data:
 
125
  "status": "failed",
126
  "error": error,
127
  "progress_message": f"실패: {error}",
128
+ "completed_at": datetime.now(timezone.utc).isoformat(),
129
+ "updated_at": datetime.now(timezone.utc).isoformat()
130
  }).eq("id", job_id).execute()
131
 
132
  if update_result.data:
routers/course.py CHANGED
@@ -29,7 +29,7 @@ import json
29
  import logging
30
  import asyncio
31
  import time
32
- from datetime import datetime, timedelta
33
  from typing import Optional, List, Dict, Any, AsyncGenerator
34
 
35
  from fastapi import APIRouter
@@ -206,7 +206,7 @@ async def generate_course(request: CourseGenerateRequest):
206
  from db import get_supabase
207
  supabase = get_supabase()
208
 
209
- one_hour_ago = (datetime.utcnow() - timedelta(hours=1)).isoformat()
210
 
211
  cached_result = supabase.table("courses") \
212
  .select("*") \
@@ -1199,7 +1199,7 @@ async def generate_stories_batch_by_ids(request: BatchStoryByIdsRequest):
1199
  if not spots_dict:
1200
  return BatchStoryByIdsResponse(
1201
  stories=[],
1202
- generated_at=datetime.utcnow(),
1203
  model=STORY_BATCH_MODEL,
1204
  success=False,
1205
  error={
@@ -1216,7 +1216,7 @@ async def generate_stories_batch_by_ids(request: BatchStoryByIdsRequest):
1216
  if len(missing_ids) == len(request.spot_ids):
1217
  return BatchStoryByIdsResponse(
1218
  stories=[],
1219
- generated_at=datetime.utcnow(),
1220
  model=STORY_BATCH_MODEL,
1221
  success=False,
1222
  error={
@@ -1255,7 +1255,7 @@ async def generate_stories_batch_by_ids(request: BatchStoryByIdsRequest):
1255
  )
1256
  return BatchStoryByIdsResponse(
1257
  stories=[],
1258
- generated_at=datetime.utcnow(),
1259
  model=STORY_BATCH_MODEL,
1260
  success=False,
1261
  error={
@@ -1277,7 +1277,7 @@ async def generate_stories_batch_by_ids(request: BatchStoryByIdsRequest):
1277
  )
1278
  return BatchStoryByIdsResponse(
1279
  stories=[],
1280
- generated_at=datetime.utcnow(),
1281
  model=STORY_BATCH_MODEL,
1282
  success=False,
1283
  error={
@@ -1313,7 +1313,7 @@ async def generate_stories_batch_by_ids(request: BatchStoryByIdsRequest):
1313
 
1314
  response = BatchStoryByIdsResponse(
1315
  stories=stories,
1316
- generated_at=datetime.utcnow(),
1317
  model=STORY_BATCH_MODEL,
1318
  success=True
1319
  )
@@ -1337,7 +1337,7 @@ async def generate_stories_batch_by_ids(request: BatchStoryByIdsRequest):
1337
  end_token_session()
1338
  return BatchStoryByIdsResponse(
1339
  stories=[],
1340
- generated_at=datetime.utcnow(),
1341
  model=STORY_BATCH_MODEL,
1342
  success=False,
1343
  error={
 
29
  import logging
30
  import asyncio
31
  import time
32
+ from datetime import datetime, timedelta, timezone
33
  from typing import Optional, List, Dict, Any, AsyncGenerator
34
 
35
  from fastapi import APIRouter
 
206
  from db import get_supabase
207
  supabase = get_supabase()
208
 
209
+ one_hour_ago = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()
210
 
211
  cached_result = supabase.table("courses") \
212
  .select("*") \
 
1199
  if not spots_dict:
1200
  return BatchStoryByIdsResponse(
1201
  stories=[],
1202
+ generated_at=datetime.now(timezone.utc),
1203
  model=STORY_BATCH_MODEL,
1204
  success=False,
1205
  error={
 
1216
  if len(missing_ids) == len(request.spot_ids):
1217
  return BatchStoryByIdsResponse(
1218
  stories=[],
1219
+ generated_at=datetime.now(timezone.utc),
1220
  model=STORY_BATCH_MODEL,
1221
  success=False,
1222
  error={
 
1255
  )
1256
  return BatchStoryByIdsResponse(
1257
  stories=[],
1258
+ generated_at=datetime.now(timezone.utc),
1259
  model=STORY_BATCH_MODEL,
1260
  success=False,
1261
  error={
 
1277
  )
1278
  return BatchStoryByIdsResponse(
1279
  stories=[],
1280
+ generated_at=datetime.now(timezone.utc),
1281
  model=STORY_BATCH_MODEL,
1282
  success=False,
1283
  error={
 
1313
 
1314
  response = BatchStoryByIdsResponse(
1315
  stories=stories,
1316
+ generated_at=datetime.now(timezone.utc),
1317
  model=STORY_BATCH_MODEL,
1318
  success=True
1319
  )
 
1337
  end_token_session()
1338
  return BatchStoryByIdsResponse(
1339
  stories=[],
1340
+ generated_at=datetime.now(timezone.utc),
1341
  model=STORY_BATCH_MODEL,
1342
  success=False,
1343
  error={
routers/db_activity.py CHANGED
@@ -6,7 +6,7 @@ from typing import Optional, List, Any, Dict
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
- from datetime import datetime
10
 
11
  from db import get_supabase
12
  from utils.error_responses import internal_server_error
@@ -76,7 +76,7 @@ async def log_activity(request: LogActivityRequest) -> LogActivityResponse:
76
  "activity_type": request.activity_type,
77
  "details": request.details,
78
  "ip_address": request.ip_address,
79
- "created_at": datetime.utcnow().isoformat()
80
  }) \
81
  .execute()
82
 
 
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
+ from datetime import datetime, timezone
10
 
11
  from db import get_supabase
12
  from utils.error_responses import internal_server_error
 
76
  "activity_type": request.activity_type,
77
  "details": request.details,
78
  "ip_address": request.ip_address,
79
+ "created_at": datetime.now(timezone.utc).isoformat()
80
  }) \
81
  .execute()
82
 
routers/db_collection.py CHANGED
@@ -10,7 +10,7 @@ from typing import Optional, List
10
  import logging
11
  from fastapi import APIRouter, HTTPException
12
  from pydantic import BaseModel
13
- from datetime import datetime
14
 
15
  from db import get_supabase
16
  from utils.error_responses import internal_server_error
@@ -73,7 +73,7 @@ def _grant_collection_reward(supabase, user_id: str, amount: int, category_name:
73
  .upsert({
74
  "user_id": user_id,
75
  "balance": new_balance,
76
- "updated_at": datetime.utcnow().isoformat()
77
  }) \
78
  .execute()
79
 
@@ -364,8 +364,8 @@ async def update_collection_on_unlock(request: UpdateOnUnlockRequest):
364
  "collected_count": new_count,
365
  "collected_story_ids": collected_ids,
366
  "is_completed": now_completed,
367
- "completed_at": datetime.utcnow().isoformat() if now_completed else None,
368
- "last_collected_at": datetime.utcnow().isoformat()
369
  }) \
370
  .execute()
371
 
 
10
  import logging
11
  from fastapi import APIRouter, HTTPException
12
  from pydantic import BaseModel
13
+ from datetime import datetime, timezone
14
 
15
  from db import get_supabase
16
  from utils.error_responses import internal_server_error
 
73
  .upsert({
74
  "user_id": user_id,
75
  "balance": new_balance,
76
+ "updated_at": datetime.now(timezone.utc).isoformat()
77
  }) \
78
  .execute()
79
 
 
364
  "collected_count": new_count,
365
  "collected_story_ids": collected_ids,
366
  "is_completed": now_completed,
367
+ "completed_at": datetime.now(timezone.utc).isoformat() if now_completed else None,
368
+ "last_collected_at": datetime.now(timezone.utc).isoformat()
369
  }) \
370
  .execute()
371
 
routers/db_course.py CHANGED
@@ -17,7 +17,7 @@ from math import radians, cos
17
  logger = logging.getLogger(__name__)
18
  from fastapi import APIRouter, HTTPException
19
  from pydantic import BaseModel
20
- from datetime import datetime
21
 
22
  from db import get_supabase
23
  from utils.error_responses import (
@@ -433,7 +433,7 @@ async def create_courses(request: CreateCourseRequest):
433
  "main_image_url": course_input.main_image_url,
434
  "is_ai_generated": True,
435
  "generation_params": course_input.generation_params,
436
- "created_at": datetime.utcnow().isoformat()
437
  }
438
 
439
  result = supabase.table("courses") \
@@ -537,7 +537,7 @@ async def save_course(request: SaveCourseRequest):
537
  "course_id": request.course_id,
538
  "user_id": request.user_id,
539
  "memo": request.memo,
540
- "saved_at": datetime.utcnow().isoformat()
541
  }) \
542
  .execute()
543
 
@@ -1361,7 +1361,7 @@ async def save_taste_profile(request: SaveTasteProfileRequest):
1361
  .upsert({
1362
  "user_id": request.user_id,
1363
  "taste_profile": request.profile,
1364
- "updated_at": datetime.utcnow().isoformat()
1365
  }) \
1366
  .execute()
1367
 
@@ -1642,7 +1642,7 @@ async def get_course_batch(
1642
  }
1643
 
1644
  metadata = {
1645
- "loaded_at": datetime.utcnow().isoformat(),
1646
  "user_id": user_id,
1647
  }
1648
 
 
17
  logger = logging.getLogger(__name__)
18
  from fastapi import APIRouter, HTTPException
19
  from pydantic import BaseModel
20
+ from datetime import datetime, timezone
21
 
22
  from db import get_supabase
23
  from utils.error_responses import (
 
433
  "main_image_url": course_input.main_image_url,
434
  "is_ai_generated": True,
435
  "generation_params": course_input.generation_params,
436
+ "created_at": datetime.now(timezone.utc).isoformat()
437
  }
438
 
439
  result = supabase.table("courses") \
 
537
  "course_id": request.course_id,
538
  "user_id": request.user_id,
539
  "memo": request.memo,
540
+ "saved_at": datetime.now(timezone.utc).isoformat()
541
  }) \
542
  .execute()
543
 
 
1361
  .upsert({
1362
  "user_id": request.user_id,
1363
  "taste_profile": request.profile,
1364
+ "updated_at": datetime.now(timezone.utc).isoformat()
1365
  }) \
1366
  .execute()
1367
 
 
1642
  }
1643
 
1644
  metadata = {
1645
+ "loaded_at": datetime.now(timezone.utc).isoformat(),
1646
  "user_id": user_id,
1647
  }
1648
 
routers/db_mwohaen.py CHANGED
@@ -6,7 +6,7 @@ from typing import Optional, List
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
- from datetime import datetime
10
 
11
  from db import get_supabase
12
  from utils.error_responses import internal_server_error
@@ -320,7 +320,7 @@ async def update_stats_on_course_complete(request: UpdateStatsRequest):
320
  """코스 완료 시 통계 업데이트"""
321
  try:
322
  supabase = get_supabase()
323
- now = datetime.utcnow().isoformat()
324
 
325
  # 현재 통계 조회
326
  stats_result = supabase.table("user_travel_stats") \
@@ -439,7 +439,7 @@ async def _check_and_grant_badges(user_id: str, stats: dict) -> List[BadgeDefini
439
  .insert({
440
  "user_id": user_id,
441
  "badge_id": b["id"],
442
- "earned_at": datetime.utcnow().isoformat()
443
  }) \
444
  .execute()
445
 
 
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
+ from datetime import datetime, timezone
10
 
11
  from db import get_supabase
12
  from utils.error_responses import internal_server_error
 
320
  """코스 완료 시 통계 업데이트"""
321
  try:
322
  supabase = get_supabase()
323
+ now = datetime.now(timezone.utc).isoformat()
324
 
325
  # 현재 통계 조회
326
  stats_result = supabase.table("user_travel_stats") \
 
439
  .insert({
440
  "user_id": user_id,
441
  "badge_id": b["id"],
442
+ "earned_at": datetime.now(timezone.utc).isoformat()
443
  }) \
444
  .execute()
445
 
routers/db_share.py CHANGED
@@ -6,7 +6,7 @@ from typing import Optional, List, Dict, Any
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
- from datetime import datetime, timedelta
10
  import secrets
11
  import string
12
 
@@ -141,7 +141,7 @@ async def create_share_link(request: CreateShareRequest) -> CreateShareResponse:
141
  expires: datetime = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
142
 
143
  # 아직 유효한 공유가 있으면 재사용
144
- if expires > datetime.utcnow().replace(tzinfo=expires.tzinfo):
145
  return CreateShareResponse(
146
  share_code=share["share_code"],
147
  share_url=f"https://aewol.app/join/{share['share_code']}",
@@ -150,7 +150,7 @@ async def create_share_link(request: CreateShareRequest) -> CreateShareResponse:
150
 
151
  # 새 공유 코드 생성
152
  share_code: str = generate_share_code()
153
- expires_at: str = (datetime.utcnow() + timedelta(days=7)).isoformat()
154
 
155
  # DB에 저장
156
  result = supabase.table("course_shares") \
@@ -162,7 +162,7 @@ async def create_share_link(request: CreateShareRequest) -> CreateShareResponse:
162
  "current_members": 1, # 소유자 포함
163
  "is_active": True,
164
  "expires_at": expires_at,
165
- "created_at": datetime.utcnow().isoformat()
166
  }) \
167
  .execute()
168
 
@@ -172,7 +172,7 @@ async def create_share_link(request: CreateShareRequest) -> CreateShareResponse:
172
  "share_id": result.data[0]["id"],
173
  "user_id": request.user_id,
174
  "role": "owner",
175
- "joined_at": datetime.utcnow().isoformat()
176
  }) \
177
  .execute()
178
 
@@ -187,7 +187,7 @@ async def create_share_link(request: CreateShareRequest) -> CreateShareResponse:
187
  "share_code": share_code,
188
  "max_members": request.max_members
189
  },
190
- "created_at": datetime.utcnow().isoformat()
191
  }) \
192
  .execute()
193
  except Exception:
@@ -223,7 +223,7 @@ async def get_share_info(share_code: str) -> ShareInfoResponse:
223
 
224
  # 만료 체크
225
  expires = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
226
- if expires < datetime.utcnow().replace(tzinfo=expires.tzinfo):
227
  return ShareInfoResponse(valid=False)
228
 
229
  course_info = share.get("courses", {}) or {}
@@ -265,7 +265,7 @@ async def join_shared_course(request: JoinShareRequest):
265
 
266
  # 만료 체크
267
  expires = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
268
- if expires < datetime.utcnow().replace(tzinfo=expires.tzinfo):
269
  return JoinShareResponse(success=False, error="expired")
270
 
271
  # 이미 참여했는지 확인
@@ -295,7 +295,7 @@ async def join_shared_course(request: JoinShareRequest):
295
  "share_id": share["id"],
296
  "user_id": request.user_id,
297
  "role": "member",
298
- "joined_at": datetime.utcnow().isoformat()
299
  }) \
300
  .execute()
301
 
@@ -320,7 +320,7 @@ async def join_shared_course(request: JoinShareRequest):
320
  "course_id": share["course_id"],
321
  "share_code": request.share_code
322
  },
323
- "created_at": datetime.utcnow().isoformat()
324
  }) \
325
  .execute()
326
  except Exception:
@@ -407,7 +407,7 @@ async def get_share_status(share_id: str):
407
 
408
  # 만료 체크
409
  expires = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
410
- is_expired = expires < datetime.utcnow().replace(tzinfo=expires.tzinfo)
411
 
412
  return ShareStatusResponse(
413
  id=share["id"],
 
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
+ from datetime import datetime, timedelta, timezone
10
  import secrets
11
  import string
12
 
 
141
  expires: datetime = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
142
 
143
  # 아직 유효한 공유가 있으면 재사용
144
+ if expires > datetime.now(timezone.utc).replace(tzinfo=expires.tzinfo):
145
  return CreateShareResponse(
146
  share_code=share["share_code"],
147
  share_url=f"https://aewol.app/join/{share['share_code']}",
 
150
 
151
  # 새 공유 코드 생성
152
  share_code: str = generate_share_code()
153
+ expires_at: str = (datetime.now(timezone.utc) + timedelta(days=7)).isoformat()
154
 
155
  # DB에 저장
156
  result = supabase.table("course_shares") \
 
162
  "current_members": 1, # 소유자 포함
163
  "is_active": True,
164
  "expires_at": expires_at,
165
+ "created_at": datetime.now(timezone.utc).isoformat()
166
  }) \
167
  .execute()
168
 
 
172
  "share_id": result.data[0]["id"],
173
  "user_id": request.user_id,
174
  "role": "owner",
175
+ "joined_at": datetime.now(timezone.utc).isoformat()
176
  }) \
177
  .execute()
178
 
 
187
  "share_code": share_code,
188
  "max_members": request.max_members
189
  },
190
+ "created_at": datetime.now(timezone.utc).isoformat()
191
  }) \
192
  .execute()
193
  except Exception:
 
223
 
224
  # 만료 체크
225
  expires = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
226
+ if expires < datetime.now(timezone.utc).replace(tzinfo=expires.tzinfo):
227
  return ShareInfoResponse(valid=False)
228
 
229
  course_info = share.get("courses", {}) or {}
 
265
 
266
  # 만료 체크
267
  expires = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
268
+ if expires < datetime.now(timezone.utc).replace(tzinfo=expires.tzinfo):
269
  return JoinShareResponse(success=False, error="expired")
270
 
271
  # 이미 참여했는지 확인
 
295
  "share_id": share["id"],
296
  "user_id": request.user_id,
297
  "role": "member",
298
+ "joined_at": datetime.now(timezone.utc).isoformat()
299
  }) \
300
  .execute()
301
 
 
320
  "course_id": share["course_id"],
321
  "share_code": request.share_code
322
  },
323
+ "created_at": datetime.now(timezone.utc).isoformat()
324
  }) \
325
  .execute()
326
  except Exception:
 
407
 
408
  # 만료 체크
409
  expires = datetime.fromisoformat(share["expires_at"].replace("Z", "+00:00"))
410
+ is_expired = expires < datetime.now(timezone.utc).replace(tzinfo=expires.tzinfo)
411
 
412
  return ShareStatusResponse(
413
  id=share["id"],
routers/db_social.py CHANGED
@@ -6,7 +6,7 @@ from typing import Optional, List
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
- from datetime import datetime
10
 
11
  from db import get_supabase
12
  from utils.error_responses import (
@@ -107,7 +107,7 @@ async def toggle_like(request: ToggleLikeRequest):
107
  .insert({
108
  "spot_id": request.spot_id,
109
  "user_id": request.user_id,
110
- "created_at": datetime.utcnow().isoformat()
111
  }) \
112
  .execute()
113
  liked = True
@@ -129,7 +129,7 @@ async def toggle_like(request: ToggleLikeRequest):
129
  "liked": liked,
130
  "total_likes": count_result.count or 0
131
  },
132
- "created_at": datetime.utcnow().isoformat()
133
  }) \
134
  .execute()
135
  except Exception:
@@ -197,7 +197,7 @@ async def create_review(request: CreateReviewRequest):
197
  "spot_id": request.spot_id,
198
  "user_id": request.user_id,
199
  "content": request.content,
200
- "created_at": datetime.utcnow().isoformat()
201
  }) \
202
  .execute()
203
 
@@ -263,7 +263,7 @@ async def report_review(request: ReportReviewRequest):
263
  "review_id": request.review_id,
264
  "reporter_id": request.user_id,
265
  "reason": request.reason,
266
- "created_at": datetime.utcnow().isoformat()
267
  }) \
268
  .execute()
269
 
 
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
+ from datetime import datetime, timezone
10
 
11
  from db import get_supabase
12
  from utils.error_responses import (
 
107
  .insert({
108
  "spot_id": request.spot_id,
109
  "user_id": request.user_id,
110
+ "created_at": datetime.now(timezone.utc).isoformat()
111
  }) \
112
  .execute()
113
  liked = True
 
129
  "liked": liked,
130
  "total_likes": count_result.count or 0
131
  },
132
+ "created_at": datetime.now(timezone.utc).isoformat()
133
  }) \
134
  .execute()
135
  except Exception:
 
197
  "spot_id": request.spot_id,
198
  "user_id": request.user_id,
199
  "content": request.content,
200
+ "created_at": datetime.now(timezone.utc).isoformat()
201
  }) \
202
  .execute()
203
 
 
263
  "review_id": request.review_id,
264
  "reporter_id": request.user_id,
265
  "reason": request.reason,
266
+ "created_at": datetime.now(timezone.utc).isoformat()
267
  }) \
268
  .execute()
269
 
routers/db_stories.py CHANGED
@@ -6,7 +6,7 @@ from typing import Optional, List
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
- from datetime import datetime
10
 
11
  from db import get_supabase
12
  from utils.error_responses import (
@@ -211,7 +211,7 @@ async def unlock_course(request: UnlockCourseRequest):
211
  "user_id": request.user_id,
212
  "spot_id": spot_id,
213
  "course_id": request.course_id,
214
- "unlocked_at": datetime.utcnow().isoformat()
215
  }) \
216
  .execute()
217
 
@@ -246,7 +246,7 @@ async def unlock_course(request: UnlockCourseRequest):
246
  .upsert({
247
  "user_id": request.user_id,
248
  "course_id": request.course_id,
249
- "unlocked_at": datetime.utcnow().isoformat()
250
  }) \
251
  .execute()
252
 
@@ -267,7 +267,7 @@ async def unlock_course(request: UnlockCourseRequest):
267
  "spot_ids": unlocked_spots,
268
  "unlocked_count": len(unlocked_spots)
269
  },
270
- "created_at": datetime.utcnow().isoformat()
271
  }) \
272
  .execute()
273
  except Exception:
 
6
  import logging
7
  from fastapi import APIRouter, HTTPException
8
  from pydantic import BaseModel
9
+ from datetime import datetime, timezone
10
 
11
  from db import get_supabase
12
  from utils.error_responses import (
 
211
  "user_id": request.user_id,
212
  "spot_id": spot_id,
213
  "course_id": request.course_id,
214
+ "unlocked_at": datetime.now(timezone.utc).isoformat()
215
  }) \
216
  .execute()
217
 
 
246
  .upsert({
247
  "user_id": request.user_id,
248
  "course_id": request.course_id,
249
+ "unlocked_at": datetime.now(timezone.utc).isoformat()
250
  }) \
251
  .execute()
252
 
 
267
  "spot_ids": unlocked_spots,
268
  "unlocked_count": len(unlocked_spots)
269
  },
270
+ "created_at": datetime.now(timezone.utc).isoformat()
271
  }) \
272
  .execute()
273
  except Exception:
routers/db_streak.py CHANGED
@@ -17,7 +17,7 @@ Supabase 테이블:
17
  from typing import Optional, List
18
  from fastapi import APIRouter, HTTPException
19
  from pydantic import BaseModel, Field, field_validator
20
- from datetime import datetime, date, timedelta
21
  import logging
22
 
23
  from db import get_supabase
@@ -200,7 +200,7 @@ class ClaimChallengeRewardResponse(BaseModel):
200
  def _get_today_date_str() -> str:
201
  """오늘 날짜 문자열 (UTC 기준, KST 고려하려면 +9시간)"""
202
  # 한국 시간 기준으로 날짜 계산 (UTC+9)
203
- now_kst = datetime.utcnow() + timedelta(hours=9)
204
  return now_kst.strftime("%Y-%m-%d")
205
 
206
 
@@ -268,7 +268,7 @@ def _grant_tokens_fallback(supabase, user_id: str, amount: int, reason: str) ->
268
  .upsert({
269
  "user_id": user_id,
270
  "balance": new_balance,
271
- "updated_at": datetime.utcnow().isoformat()
272
  }) \
273
  .execute()
274
 
@@ -307,7 +307,7 @@ async def streak_check_in(request: StreakCheckInRequest):
307
  """
308
  try:
309
  supabase = get_supabase()
310
- now = datetime.utcnow().isoformat()
311
  today_str = _get_today_date_str()
312
  today = date.fromisoformat(today_str)
313
 
@@ -552,7 +552,7 @@ async def get_active_challenges():
552
  """
553
  try:
554
  supabase = get_supabase()
555
- now = datetime.utcnow().isoformat()
556
 
557
  try:
558
  result = supabase.table("challenges") \
@@ -703,7 +703,7 @@ async def update_challenge_progress(request: UpdateChallengeProgressRequest):
703
  """
704
  try:
705
  supabase = get_supabase()
706
- now = datetime.utcnow().isoformat()
707
  today_str = _get_today_date_str()
708
 
709
  # 해당 target_type의 활성 챌린지 조회
@@ -863,7 +863,7 @@ async def claim_challenge_reward(request: ClaimChallengeRewardRequest):
863
  """
864
  try:
865
  supabase = get_supabase()
866
- now = datetime.utcnow().isoformat()
867
 
868
  # 챌린지 정의 조회
869
  try:
 
17
  from typing import Optional, List
18
  from fastapi import APIRouter, HTTPException
19
  from pydantic import BaseModel, Field, field_validator
20
+ from datetime import datetime, date, timedelta, timezone
21
  import logging
22
 
23
  from db import get_supabase
 
200
  def _get_today_date_str() -> str:
201
  """오늘 날짜 문자열 (UTC 기준, KST 고려하려면 +9시간)"""
202
  # 한국 시간 기준으로 날짜 계산 (UTC+9)
203
+ now_kst = datetime.now(timezone.utc) + timedelta(hours=9)
204
  return now_kst.strftime("%Y-%m-%d")
205
 
206
 
 
268
  .upsert({
269
  "user_id": user_id,
270
  "balance": new_balance,
271
+ "updated_at": datetime.now(timezone.utc).isoformat()
272
  }) \
273
  .execute()
274
 
 
307
  """
308
  try:
309
  supabase = get_supabase()
310
+ now = datetime.now(timezone.utc).isoformat()
311
  today_str = _get_today_date_str()
312
  today = date.fromisoformat(today_str)
313
 
 
552
  """
553
  try:
554
  supabase = get_supabase()
555
+ now = datetime.now(timezone.utc).isoformat()
556
 
557
  try:
558
  result = supabase.table("challenges") \
 
703
  """
704
  try:
705
  supabase = get_supabase()
706
+ now = datetime.now(timezone.utc).isoformat()
707
  today_str = _get_today_date_str()
708
 
709
  # 해당 target_type의 활성 챌린지 조회
 
863
  """
864
  try:
865
  supabase = get_supabase()
866
+ now = datetime.now(timezone.utc).isoformat()
867
 
868
  # 챌린지 정의 조회
869
  try:
routers/db_tokens.py CHANGED
@@ -13,7 +13,7 @@ Token API endpoints
13
  from typing import Optional, List
14
  from fastapi import APIRouter, HTTPException, Query
15
  from pydantic import BaseModel, Field, field_validator
16
- from datetime import datetime
17
  import logging
18
 
19
  from db import get_supabase
@@ -366,7 +366,7 @@ async def add_tokens(request: AddTokensRequest):
366
  .upsert({
367
  "user_id": request.user_id,
368
  "balance": new_balance,
369
- "updated_at": datetime.utcnow().isoformat()
370
  }) \
371
  .execute()
372
 
@@ -463,7 +463,7 @@ async def grant_bonus(request: GrantBonusRequest):
463
  .upsert({
464
  "user_id": request.user_id,
465
  "balance": new_balance,
466
- "updated_at": datetime.utcnow().isoformat()
467
  }) \
468
  .execute()
469
 
 
13
  from typing import Optional, List
14
  from fastapi import APIRouter, HTTPException, Query
15
  from pydantic import BaseModel, Field, field_validator
16
+ from datetime import datetime, timezone
17
  import logging
18
 
19
  from db import get_supabase
 
366
  .upsert({
367
  "user_id": request.user_id,
368
  "balance": new_balance,
369
+ "updated_at": datetime.now(timezone.utc).isoformat()
370
  }) \
371
  .execute()
372
 
 
463
  .upsert({
464
  "user_id": request.user_id,
465
  "balance": new_balance,
466
+ "updated_at": datetime.now(timezone.utc).isoformat()
467
  }) \
468
  .execute()
469
 
utils/logging_config.py CHANGED
@@ -15,7 +15,7 @@ import logging.handlers
15
  import json
16
  import sys
17
  import os
18
- from datetime import datetime
19
  from typing import Any, Dict, Optional
20
  from pathlib import Path
21
 
@@ -48,7 +48,7 @@ class JSONFormatter(logging.Formatter):
48
  JSON 문자열
49
  """
50
  log_data: Dict[str, Any] = {
51
- "timestamp": datetime.utcnow().isoformat() + "Z",
52
  "level": record.levelname,
53
  "logger": record.name,
54
  "message": record.getMessage(),
 
15
  import json
16
  import sys
17
  import os
18
+ from datetime import datetime, timezone
19
  from typing import Any, Dict, Optional
20
  from pathlib import Path
21
 
 
48
  JSON 문자열
49
  """
50
  log_data: Dict[str, Any] = {
51
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
52
  "level": record.levelname,
53
  "logger": record.name,
54
  "message": record.getMessage(),