Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- lib/job_manager.py +6 -6
- routers/course.py +8 -8
- routers/db_activity.py +2 -2
- routers/db_collection.py +4 -4
- routers/db_course.py +5 -5
- routers/db_mwohaen.py +3 -3
- routers/db_share.py +11 -11
- routers/db_social.py +5 -5
- routers/db_stories.py +4 -4
- routers/db_streak.py +7 -7
- routers/db_tokens.py +3 -3
- utils/logging_config.py +2 -2
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.
|
| 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.
|
| 86 |
-
"updated_at": datetime.
|
| 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.
|
| 129 |
-
"updated_at": datetime.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 368 |
-
"last_collected_at": datetime.
|
| 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.
|
| 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.
|
| 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.
|
| 1365 |
}) \
|
| 1366 |
.execute()
|
| 1367 |
|
|
@@ -1642,7 +1642,7 @@ async def get_course_batch(
|
|
| 1642 |
}
|
| 1643 |
|
| 1644 |
metadata = {
|
| 1645 |
-
"loaded_at": datetime.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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(),
|