Merge pull request #403 from trails-cool/stigi/disconnect-redirect-fix

Return to originating settings page after disconnecting a service
This commit is contained in:
Ullrich Schäfer 2026-05-23 20:52:36 +02:00 committed by GitHub
commit 5158298424
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 14 deletions

View file

@ -1,11 +1,11 @@
import type { JobDefinition } from "@trails-cool/jobs"; import type { JobDefinition } from "@trails-cool/jobs";
import { and, lt, inArray } from "drizzle-orm"; import { and, lt, inArray, count } from "drizzle-orm";
import { getDb } from "../lib/db.ts"; import { getDb } from "../lib/db.ts";
import { importBatches } from "@trails-cool/db/schema/journal"; import { importBatches, type ImportBatchStatus } from "@trails-cool/db/schema/journal";
import { logger } from "../lib/logger.server.ts"; import { logger } from "../lib/logger.server.ts";
// Batches stuck longer than this without completing are considered stale.
const STALE_MS = 10 * 60 * 1000; const STALE_MS = 10 * 60 * 1000;
const STALE_STATUSES: ImportBatchStatus[] = ["pending", "running"];
export const importBatchesSweepJob: JobDefinition = { export const importBatchesSweepJob: JobDefinition = {
name: "import-batches-sweep", name: "import-batches-sweep",
@ -15,24 +15,28 @@ export const importBatchesSweepJob: JobDefinition = {
async handler() { async handler() {
const db = getDb(); const db = getDb();
const cutoff = new Date(Date.now() - STALE_MS); const cutoff = new Date(Date.now() - STALE_MS);
const staleFilter = and(
inArray(importBatches.status, STALE_STATUSES),
lt(importBatches.startedAt, cutoff),
);
// Skip the write when nothing is stale to avoid an unconditional UPDATE every minute.
const rows = await db
.select({ staleCount: count() })
.from(importBatches)
.where(staleFilter);
if ((rows[0]?.staleCount ?? 0) === 0) return;
const result = await db const result = await db
.update(importBatches) .update(importBatches)
.set({ .set({
status: "failed", status: "failed" satisfies ImportBatchStatus,
errorMessage: "Import timed out — the server may have restarted mid-import. Click 'Run again' to retry.", errorMessage: "Import timed out — the server may have restarted mid-import. Click 'Run again' to retry.",
completedAt: new Date(), completedAt: new Date(),
}) })
.where( .where(staleFilter)
and(
inArray(importBatches.status, ["pending", "running"]),
lt(importBatches.startedAt, cutoff),
),
)
.returning({ id: importBatches.id }); .returning({ id: importBatches.id });
if (result.length > 0) { logger.info({ count: result.length }, "import-batches-sweep: marked stale batches as failed");
logger.info({ count: result.length }, "import-batches-sweep: marked stale batches as failed");
}
}, },
}; };

View file

@ -14,5 +14,12 @@ export async function action({ params, request }: Route.ActionArgs) {
// the local row regardless of revoke outcome. Imported activities are // the local row regardless of revoke outcome. Imported activities are
// retained (FK is set null on imports.activityId, not cascaded). // retained (FK is set null on imports.activityId, not cascaded).
await unlinkByUserProvider(user.id, manifest.id); await unlinkByUserProvider(user.id, manifest.id);
return redirect("/settings");
const referer = request.headers.get("referer");
const url = referer ? new URL(referer) : null;
let back = "/settings/connections";
if (url?.pathname.startsWith("/settings") && !url.pathname.startsWith("/settings/connections/")) {
back = url.pathname;
}
return redirect(back);
} }