Updated updated path for the new table layout, moved models to a new module
This commit is contained in:
1 parent
5296be3a80
commit
79e8de124a
3 files changed
+88
-48
No files matched your search
+15
-1
@@ -9,4 +9,18 @@ CREATE TABLE `seadex_state` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
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
|
||||||
|
);
|
||||||
+52
-47
@@ -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 "
|
||||||
request.app.state.notifier.send(
|
"FROM seadex_state_subscriptions "
|
||||||
title=anime_name,
|
"WHERE seadex_state_id = %s AND active = 1",
|
||||||
body="SeaDex entry updated",
|
(seadex_state_id,),
|
||||||
link=f"https://seadex.moe/{anilist_id}/",
|
|
||||||
user_id=user_id,
|
|
||||||
)
|
)
|
||||||
|
subscribers = cursor.fetchall()
|
||||||
|
|
||||||
with db.cursor() as cursor:
|
sent = 0
|
||||||
cursor.execute(
|
failed = 0
|
||||||
"UPDATE seadex_state SET last_updated = %s WHERE id = %s AND user_id = %s",
|
for user_id, custom_name in subscribers:
|
||||||
(current, row_id, user_id),
|
try:
|
||||||
|
request.app.state.notifier.send(
|
||||||
|
title=custom_name or anime_name,
|
||||||
|
body="SeaDex entry updated",
|
||||||
|
link=f"https://seadex.moe/{anilist_id}/",
|
||||||
|
user_id=user_id,
|
||||||
)
|
)
|
||||||
db.commit()
|
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
|
||||||
|
|
||||||
logger.debug(
|
with db.cursor() as cursor:
|
||||||
f"[FastAPI] Notified and updated db for id={row_id}, "
|
cursor.execute(
|
||||||
f"Anilist ID={anilist_id}, User ID: {user_id}"
|
"UPDATE seadex_state SET last_updated = %s WHERE id = %s",
|
||||||
|
(current, seadex_state_id),
|
||||||
)
|
)
|
||||||
results["new"] += 1
|
db.commit()
|
||||||
|
logger.debug(f"[FastAPI] Notification sent and database updated for Anime: {anime_name}")
|
||||||
|
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}")
|
||||||
|
|||||||
@@ -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))
|
||||||
Reference in new issue
Block a user