feat(backend): capture status and the stretches the archive was not recording
This commit is contained in:
1 parent
a0e134756c
commit
9ae8b34005
3 files changed
+78
No files matched your search
@@ -21,6 +21,7 @@ from api.routers import (
|
|||||||
avatars,
|
avatars,
|
||||||
backfill,
|
backfill,
|
||||||
bots,
|
bots,
|
||||||
|
capture,
|
||||||
chats,
|
chats,
|
||||||
custom_emoji,
|
custom_emoji,
|
||||||
discover,
|
discover,
|
||||||
@@ -114,6 +115,7 @@ app.include_router(webhooks.router)
|
|||||||
app.include_router(shares.router)
|
app.include_router(shares.router)
|
||||||
app.include_router(storage.router)
|
app.include_router(storage.router)
|
||||||
app.include_router(files.router)
|
app.include_router(files.router)
|
||||||
|
app.include_router(capture.router)
|
||||||
|
|
||||||
app.mount("/mcp", mcp_app)
|
app.mount("/mcp", mcp_app)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
from dishka.integrations.fastapi import DishkaRoute, FromDishka
|
||||||
|
from fastapi import APIRouter, Query
|
||||||
|
|
||||||
|
from utils.read import capture
|
||||||
|
from utils.read.models import CaptureGap, CaptureStatus
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/capture", tags=["capture"], route_class=DishkaRoute)
|
||||||
|
|
||||||
|
AccountId = Annotated[int, Query()]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/status")
|
||||||
|
async def capture_status(
|
||||||
|
pool: FromDishka[asyncpg.Pool], account_id: AccountId
|
||||||
|
) -> CaptureStatus:
|
||||||
|
return await capture.capture_status(pool, account_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/gaps")
|
||||||
|
async def capture_gaps(
|
||||||
|
pool: FromDishka[asyncpg.Pool],
|
||||||
|
account_id: AccountId,
|
||||||
|
date_from: Annotated[datetime | None, Query()] = None,
|
||||||
|
date_to: Annotated[datetime | None, Query()] = None,
|
||||||
|
) -> list[CaptureGap]:
|
||||||
|
return await capture.capture_gaps(
|
||||||
|
pool, account_id, date_from=date_from, date_to=date_to
|
||||||
|
)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
|
||||||
|
from utils.read.models import CaptureGap, CaptureStatus
|
||||||
|
|
||||||
|
_STATUS = """
|
||||||
|
SELECT started_at, seen_at, seen_at >= now() - interval '150 seconds' AS live
|
||||||
|
FROM capture_uptime WHERE account_id = $1
|
||||||
|
ORDER BY started_at DESC LIMIT 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
_GAPS = """
|
||||||
|
SELECT seen_at AS start, next_start AS "end"
|
||||||
|
FROM (
|
||||||
|
SELECT seen_at,
|
||||||
|
lead(started_at) OVER (ORDER BY started_at) AS next_start
|
||||||
|
FROM capture_uptime WHERE account_id = $1
|
||||||
|
) stretches
|
||||||
|
WHERE next_start - seen_at > interval '30 minutes'
|
||||||
|
AND ($2::timestamptz IS NULL OR next_start >= $2)
|
||||||
|
AND ($3::timestamptz IS NULL OR seen_at <= $3)
|
||||||
|
ORDER BY seen_at
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
async def capture_status(pool: asyncpg.Pool, account_id: int) -> CaptureStatus:
|
||||||
|
row = await pool.fetchrow(_STATUS, account_id)
|
||||||
|
if row is None:
|
||||||
|
return CaptureStatus(live=False, since=None, last_seen=None)
|
||||||
|
return CaptureStatus(
|
||||||
|
live=row["live"], since=row["started_at"], last_seen=row["seen_at"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def capture_gaps(
|
||||||
|
pool: asyncpg.Pool,
|
||||||
|
account_id: int,
|
||||||
|
*,
|
||||||
|
date_from: datetime | None = None,
|
||||||
|
date_to: datetime | None = None,
|
||||||
|
) -> list[CaptureGap]:
|
||||||
|
rows = await pool.fetch(_GAPS, account_id, date_from, date_to)
|
||||||
|
return [CaptureGap(**dict(row)) for row in rows]
|
||||||
Reference in new issue
Block a user