"""승인 CAS와 큐 적재를 같은 트랜잭션으로 묶어 승인 후 잡 유실을 막는다.""" import json from sqlalchemy import text from common.enums import JobType from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import place_social_posts as Post async def transaction(fn): return await DB_SESSION_MNG.execute_lambda_write(Post.DBType(), fn) async def enqueue(s, post_id, job_type): await s.execute( text("""INSERT INTO jobs(job_type, payload, dedupe_key, max_attempts) VALUES (:type, CAST(:payload AS jsonb), :key, 1) ON CONFLICT (dedupe_key) WHERE status IN (1,2) AND dedupe_key IS NOT NULL DO NOTHING"""), { "type": job_type, "payload": json.dumps({"post_id": str(post_id)}), "key": f"social:{job_type}:{post_id}", }, ) await s.execute(text("SELECT pg_notify('web4ai_job', '')")) async def decide(s, post_id, sha, approve, via): row = ( await s.execute( text("""UPDATE place_social_posts SET status=:status, decided_at=now(), decided_via=:via, updated_at=now() WHERE post_id=:id AND deleted=false AND status='PENDING_APPROVAL' AND approval_token_sha=:sha AND approval_expires_at>now() RETURNING post_id, account_id"""), { "status": "APPROVED" if approve else "DECLINED", "via": via, "id": post_id, "sha": sha, }, ) ).first() if row and approve and row.account_id: await enqueue(s, post_id, JobType.SOCIAL_POST.value) return bool(row) async def sweep(): async def run(s): await s.execute( text("""UPDATE place_social_posts SET status='EXPIRED', updated_at=now() WHERE deleted=false AND status='PENDING_APPROVAL' AND approval_expires_at<=now()""") ) # POSTING은 외부가 받았을 수 있다. 시간을 근거로 APPROVED로 돌리지 않는다. await s.execute( text("""UPDATE place_social_posts SET status='UNKNOWN', last_error='POST_RESULT_UNKNOWN', updated_at=now() WHERE deleted=false AND status='POSTING' AND updated_at