# Conflicts: # docs/DECISIONS.md # docs/DEVLOG.md # solution/backend/requirements.txt # solution/backend/scheduler/__init__.py # solution/backend/worker/handlers.py # solution/site/src/pages/HomePage.tsx # solution/site/src/sections/index.ts
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""승인 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<now()-interval '10 minutes'""")
|
|
)
|
|
await s.execute(
|
|
text("""UPDATE place_social_posts SET status='FAILED',
|
|
last_error='DRAFT_INTERRUPTED', updated_at=now()
|
|
WHERE deleted=false AND status='DRAFTING' AND updated_at<now()-interval '10 minutes'""")
|
|
)
|
|
|
|
await transaction(run)
|