First MVP
This commit is contained in:
136
src/gitea_codex_bot/services/jobs.py
Normal file
136
src/gitea_codex_bot/services/jobs.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from gitea_codex_bot.models import JobStatus, ReviewJob, ReviewRun, RunStatus, WebhookEvent
|
||||
from gitea_codex_bot.services.security import payload_digest
|
||||
from gitea_codex_bot.types import ParsedCommand
|
||||
|
||||
|
||||
def persist_webhook_event(
|
||||
session: Session,
|
||||
*,
|
||||
delivery_id: str | None,
|
||||
event_name: str,
|
||||
repo: str,
|
||||
comment_id: int | None,
|
||||
payload: bytes,
|
||||
) -> bool:
|
||||
event = WebhookEvent(
|
||||
delivery_id=delivery_id,
|
||||
event_name=event_name,
|
||||
repo=repo,
|
||||
comment_id=comment_id,
|
||||
payload_sha256=payload_digest(payload),
|
||||
)
|
||||
session.add(event)
|
||||
try:
|
||||
session.commit()
|
||||
return True
|
||||
except IntegrityError:
|
||||
session.rollback()
|
||||
return False
|
||||
|
||||
|
||||
def cooldown_remaining_seconds(session: Session, repo: str, pr_number: int, cooldown_seconds: int) -> int:
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(seconds=cooldown_seconds)
|
||||
row = session.execute(
|
||||
select(ReviewJob)
|
||||
.where(ReviewJob.repo == repo, ReviewJob.pr_number == pr_number, ReviewJob.created_at >= cutoff)
|
||||
.order_by(ReviewJob.created_at.desc())
|
||||
.limit(1)
|
||||
).scalar_one_or_none()
|
||||
if not row:
|
||||
return 0
|
||||
created_at = row.created_at
|
||||
if created_at.tzinfo is None:
|
||||
created_at = created_at.replace(tzinfo=timezone.utc)
|
||||
age = (datetime.now(timezone.utc) - created_at).total_seconds()
|
||||
remaining = int(max(cooldown_seconds - age, 0))
|
||||
return remaining
|
||||
|
||||
|
||||
def enqueue_job(
|
||||
session: Session,
|
||||
*,
|
||||
repo: str,
|
||||
pr_number: int,
|
||||
head_sha: str,
|
||||
trigger_comment_id: int,
|
||||
requested_by: str,
|
||||
command: ParsedCommand,
|
||||
) -> ReviewJob:
|
||||
job = ReviewJob(
|
||||
repo=repo,
|
||||
pr_number=pr_number,
|
||||
head_sha=head_sha,
|
||||
trigger_comment_id=trigger_comment_id,
|
||||
command=command.name,
|
||||
command_args=" ".join(command.arguments) if command.arguments else None,
|
||||
requested_by=requested_by,
|
||||
status=JobStatus.queued,
|
||||
)
|
||||
session.add(job)
|
||||
session.commit()
|
||||
session.refresh(job)
|
||||
return job
|
||||
|
||||
|
||||
def claim_next_job(session: Session) -> ReviewJob | None:
|
||||
job = session.execute(
|
||||
select(ReviewJob).where(ReviewJob.status == JobStatus.queued).order_by(ReviewJob.created_at.asc()).limit(1).with_for_update(skip_locked=True)
|
||||
).scalar_one_or_none()
|
||||
if not job:
|
||||
session.rollback()
|
||||
return None
|
||||
job.status = JobStatus.running
|
||||
job.started_at = datetime.now(timezone.utc)
|
||||
run = ReviewRun(job_id=job.id, status=RunStatus.running)
|
||||
session.add(run)
|
||||
session.commit()
|
||||
session.refresh(job)
|
||||
return job
|
||||
|
||||
|
||||
def finish_job(
|
||||
session: Session,
|
||||
*,
|
||||
job_id: int,
|
||||
success: bool,
|
||||
skipped: bool,
|
||||
result: dict | None,
|
||||
error_message: str | None,
|
||||
) -> None:
|
||||
job = session.get(ReviewJob, job_id)
|
||||
if not job:
|
||||
return
|
||||
latest_run = (
|
||||
session.execute(select(ReviewRun).where(ReviewRun.job_id == job_id).order_by(ReviewRun.id.desc()).limit(1)).scalar_one_or_none()
|
||||
)
|
||||
if skipped:
|
||||
job.status = JobStatus.skipped
|
||||
run_status = RunStatus.skipped
|
||||
elif success:
|
||||
job.status = JobStatus.succeeded
|
||||
run_status = RunStatus.succeeded
|
||||
else:
|
||||
job.status = JobStatus.failed
|
||||
run_status = RunStatus.failed
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
job.finished_at = now
|
||||
job.last_error = error_message
|
||||
if result is not None:
|
||||
job.result_json = result
|
||||
|
||||
if latest_run:
|
||||
latest_run.status = run_status
|
||||
latest_run.finished_at = now
|
||||
latest_run.result_json = result
|
||||
latest_run.error_message = error_message
|
||||
|
||||
session.commit()
|
||||
Reference in New Issue
Block a user