mirror of
https://github.com/Chevron7Locked/kima-hub.git
synced 2026-06-19 07:37:17 +00:00
fix: review remediations -- scan overlap guard, Subsonic star best-effort, podcast upsert
- Library auto-sync cron skips enqueuing when a scan is already active/waiting, so it can't stack a redundant full rescan behind a manual or webhook scan. - Subsonic star.view is now best-effort: it attempts every id, skips missing tracks (P2003), logs genuine failures, and never early-returns mid-loop (which left some tracks starred while reporting failure). It reports an error only when a real failure occurred and nothing got starred. - refreshPodcastFeed upserts episodes on (podcastId, guid) instead of find-then-create, closing a TOCTOU race between the manual refresh route and the auto-refresh job that could throw on the unique constraint. - Onboarding: rename the shadowing 'user' var in the recovery path for clarity.
This commit is contained in:
@@ -1764,8 +1764,14 @@ export async function refreshPodcastFeed(podcastId: string): Promise<{ newEpisod
|
||||
for (const ep of result.episodes) {
|
||||
if (existingGuids.has(ep.guid)) continue;
|
||||
|
||||
await prisma.podcastEpisode.create({
|
||||
data: {
|
||||
// upsert, not create: the manual refresh route and the auto-refresh job
|
||||
// can run concurrently, and find-then-create is a TOCTOU race -- both
|
||||
// would see "not existing" and the second create would throw on the
|
||||
// (podcastId, guid) unique constraint. The update branch is a no-op so
|
||||
// an episode that already exists is left untouched.
|
||||
await prisma.podcastEpisode.upsert({
|
||||
where: { podcastId_guid: { podcastId, guid: ep.guid } },
|
||||
create: {
|
||||
podcastId,
|
||||
guid: ep.guid,
|
||||
title: ep.title,
|
||||
@@ -1779,6 +1785,7 @@ export async function refreshPodcastFeed(podcastId: string): Promise<{ newEpisod
|
||||
fileSize: ep.fileSize,
|
||||
mimeType: ep.mimeType,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
newEpisodesCount++;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,15 @@ starredRouter.all("/star.view", wrap(async (req, res) => {
|
||||
const userId = req.user!.id;
|
||||
const ids = parseRepeatedQueryParam(req.query.id);
|
||||
|
||||
// Best-effort: attempt every id, skip ones whose track doesn't exist
|
||||
// (P2003), and log genuine failures rather than swallowing them. Don't
|
||||
// early-return mid-loop -- that would leave some tracks starred while
|
||||
// telling the client it failed. Only report an error if a real failure
|
||||
// occurred and nothing got starred (e.g. the DB is down); a partial
|
||||
// failure is logged and reported ok so the client keeps its successes
|
||||
// (the upsert is idempotent, so a retry is safe).
|
||||
let anySucceeded = false;
|
||||
let realFailure = false;
|
||||
for (const trackId of ids) {
|
||||
try {
|
||||
await prisma.likedTrack.upsert({
|
||||
@@ -88,13 +97,16 @@ starredRouter.all("/star.view", wrap(async (req, res) => {
|
||||
create: { userId, trackId },
|
||||
update: {},
|
||||
});
|
||||
anySucceeded = true;
|
||||
} catch (err) {
|
||||
// P2003 = FK violation: trackId doesn't exist. Absorb silently.
|
||||
if ((err as { code?: string }).code === "P2003") continue;
|
||||
logger.warn("[Subsonic] star failed:", err);
|
||||
return subsonicError(req, res, SubsonicError.GENERIC, "Failed to star track");
|
||||
realFailure = true;
|
||||
logger.warn(`[Subsonic] star failed for track ${trackId}:`, err);
|
||||
}
|
||||
}
|
||||
if (realFailure && !anySucceeded) {
|
||||
return subsonicError(req, res, SubsonicError.GENERIC, "Failed to star track");
|
||||
}
|
||||
return subsonicOk(req, res);
|
||||
}));
|
||||
|
||||
|
||||
@@ -31,6 +31,15 @@ export function startLibrarySyncCron() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if a scan (manual, webhook, or a prior auto-sync) is already
|
||||
// active or waiting -- no point queuing a redundant full rescan
|
||||
// behind one that's about to cover the same files.
|
||||
const counts = await scanQueue.getJobCounts("active", "waiting");
|
||||
if ((counts.active ?? 0) + (counts.waiting ?? 0) > 0) {
|
||||
logger.debug("[LibrarySync] scan already in progress, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
await scanQueue.add("scan", {
|
||||
musicPath: config.music.musicPath,
|
||||
source: "auto-sync",
|
||||
|
||||
@@ -109,12 +109,12 @@ export default function OnboardingPage() {
|
||||
// "refresh" instruction that can't recover the session, try
|
||||
// logging in with the same credentials and continue.
|
||||
try {
|
||||
const user = await api.login(username, password);
|
||||
if (user.requires2FA) {
|
||||
const loggedInUser = await api.login(username, password);
|
||||
if (loggedInUser.requires2FA) {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
if (user.onboardingComplete) {
|
||||
if (loggedInUser.onboardingComplete) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user