Updated updated path for the new table layout, moved models to a new module

This commit is contained in:
florian committed 2026-11-04 11:17:16 +01:00
1 parent 5296be3a80
commit 79e8de124a
3 files changed
+80 -40

No files matched your search

+14
View File
@@ -10,3 +10,17 @@ CREATE TABLE `seadex_state` (
UNIQUE KEY `uq_user_anilist` (`user_id`,`anilist_id`), UNIQUE KEY `uq_user_anilist` (`user_id`,`anilist_id`),
CONSTRAINT `fk_seadex_state_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE CONSTRAINT `fk_seadex_state_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE
) )
CREATE TABLE `seadex_state_subscriptions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`seadex_state_id` int(10) unsigned NOT NULL,
`anime_name` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_user_state` (`user_id`,`seadex_state_id`),
KEY `idx_subscriptions_state` (`seadex_state_id`),
CONSTRAINT `fk_subscriptions_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE,
CONSTRAINT `fk_subscriptions_state` FOREIGN KEY (`seadex_state_id`) REFERENCES `seadex_state` (`id`) ON DELETE CASCADE
);
+45 -40
View File
@@ -5,7 +5,6 @@ from datetime import UTC, datetime
import uvicorn import uvicorn
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request
from pydantic import BaseModel, Field, computed_field
from seadex import EntryNotFoundError, SeaDexEntry from seadex import EntryNotFoundError, SeaDexEntry
from service_auth import UserId from service_auth import UserId
@@ -19,30 +18,13 @@ from db_module import (
) )
from metrics_server import REQUEST_COUNTER, metrics_api from metrics_server import REQUEST_COUNTER, metrics_api
from secret_manager import cleanup_secret_files from secret_manager import cleanup_secret_files
from send_notifications import NotificationClient from send_notifications import NotificationClient, NotificationUnavailableError, NotificationError
from simple_logger_handler import LOG_LEVEL, setup_logger from simple_logger_handler import LOG_LEVEL, setup_logger
from models import AnimeCreate,AnimeEntry,AnimeList
logger = setup_logger(__name__) logger = setup_logger(__name__)
class AnimeCreate(BaseModel):
anilist_id: int = Field(gt=0)
class AnimeEntry(BaseModel):
name: str
last_updated: str | None = None
class Counts(BaseModel):
active: int
inactive: int
class AnimeList(BaseModel):
active: list[AnimeEntry] = Field(default_factory=list)
inactive: list[AnimeEntry] = Field(default_factory=list)
@computed_field
@property
def counts(self) -> Counts:
return Counts(active=len(self.active),inactive=len(self.inactive))
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
@@ -113,52 +95,75 @@ def check_seadex(request:Request, db=Depends(get_db)):
with db.cursor() as cursor: with db.cursor() as cursor:
cursor.execute( cursor.execute(
"SELECT id, anilist_id, anime_name, last_updated, user_id " "SELECT id, anilist_id, anime_name, last_updated "
"FROM seadex_state WHERE active = 1" "FROM seadex_state WHERE active = 1"
) )
anime = cursor.fetchall() anime = cursor.fetchall()
cache: dict[int, str | None] = {} logger.debug(f"[FastAPI] Found {len(anime)} active anime to check.")
if not anime[0]:
logger.info("[FastAPI] No active Seadex entries")
return results
with SeaDexEntry() as seadex: with SeaDexEntry() as seadex:
for row_id, anilist_id, anime_name, last_updated, user_id in anime: for seadex_state_id, anilist_id, anime_name, last_updated in anime:
results["checked"] += 1 results["checked"] += 1
try: try:
if anilist_id not in cache: current = check_for_update(seadex, anilist_id)
cache[anilist_id] = check_for_update(seadex, anilist_id)
current = cache[anilist_id]
if current is None: if current is None:
logger.debug(f"[FastAPI] No entry yet for {anime_name} on SeaDex") logger.debug(f"[FastAPI] No entry yet for Anime: {anime_name} on SeaDex")
continue
if current == last_updated:
continue continue
if current != last_updated: logger.info(f"[FastAPI] Anime: {anime_name} updated, Anilist ID: {anilist_id}")
logger.info( with db.cursor() as cursor:
f"[FastAPI] {anime_name} updated, Anilist ID: {anilist_id}, User ID: {user_id}" cursor.execute(
"SELECT user_id, anime_name as custom_name "
"FROM seadex_state_subscriptions "
"WHERE seadex_state_id = %s AND active = 1",
(seadex_state_id,),
) )
subscribers = cursor.fetchall()
sent = 0
failed = 0
for user_id, custom_name in subscribers:
try:
request.app.state.notifier.send( request.app.state.notifier.send(
title=anime_name, title=custom_name or anime_name,
body="SeaDex entry updated", body="SeaDex entry updated",
link=f"https://seadex.moe/{anilist_id}/", link=f"https://seadex.moe/{anilist_id}/",
user_id=user_id, user_id=user_id,
) )
sent += 1
except NotificationUnavailableError:
failed = len(subscribers) - sent
logger.error (f"[FastAPI] Notifications backend unavailable, skipping "
f"{failed} remaining user(s) for Anime: {anime_name}")
break
except NotificationError as e:
failed += 1
logger.error(f"[FastAPI] Failed to send notification to user_id={user_id} for Anime: {anime_name} "
f"status={getattr(e, 'status_code',None)}")
if subscribers and sent == 0:
results["errors"] += 1
logger.error(f"[FastAPI] All notifications failed for Anime: {anime_name}, will retry later")
continue
with db.cursor() as cursor: with db.cursor() as cursor:
cursor.execute( cursor.execute(
"UPDATE seadex_state SET last_updated = %s WHERE id = %s AND user_id = %s", "UPDATE seadex_state SET last_updated = %s WHERE id = %s",
(current, row_id, user_id), (current, seadex_state_id),
) )
db.commit() db.commit()
logger.debug(f"[FastAPI] Notification sent and database updated for Anime: {anime_name}")
logger.debug(
f"[FastAPI] Notified and updated db for id={row_id}, "
f"Anilist ID={anilist_id}, User ID: {user_id}"
)
results["new"] += 1 results["new"] += 1
except Exception: except Exception:
db.rollback() db.rollback()
results["errors"] += 1 results["errors"] += 1
logger.exception(f"[FastAPI] Failed for id={row_id} Anilist={anilist_id}, User ID: {user_id}",) logger.exception(f"[FastAPI] Failed for id: {seadex_state_id} Anilist: {anilist_id}",)
if results["errors"]: if results["errors"]:
logger.warning(f"[FastAPI] SeaDex check finished with errors: {results}") logger.warning(f"[FastAPI] SeaDex check finished with errors: {results}")
+21
View File
@@ -0,0 +1,21 @@
from pydantic import BaseModel, Field, computed_field
class AnimeCreate(BaseModel):
anilist_id: int = Field(gt=0)
class AnimeEntry(BaseModel):
name: str
last_updated: str | None = None
class Counts(BaseModel):
active: int
inactive: int
class AnimeList(BaseModel):
active: list[AnimeEntry] = Field(default_factory=list)
inactive: list[AnimeEntry] = Field(default_factory=list)
@computed_field
@property
def counts(self) -> Counts:
return Counts(active=len(self.active),inactive=len(self.inactive))