fix(sessions): remove IP from device fingerprint to prevent duplicate sessions on network change
All checks were successful
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 1m43s
Release / Docker / caddy (push) Successful in 44s
Release / Docker / backend (push) Successful in 2m40s
Release / Docker / runner (push) Successful in 4m16s
Release / Upload source maps (push) Successful in 1m41s
Release / Docker / ui (push) Successful in 2m47s
Release / Gitea Release (push) Successful in 40s
All checks were successful
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 1m43s
Release / Docker / caddy (push) Successful in 44s
Release / Docker / backend (push) Successful in 2m40s
Release / Docker / runner (push) Successful in 4m16s
Release / Upload source maps (push) Successful in 1m41s
Release / Docker / ui (push) Successful in 2m47s
Release / Gitea Release (push) Successful in 40s
- deviceFingerprint now hashes only User-Agent (not UA+IP) so switching networks (VPN, mobile data, wifi) no longer creates a new session row - On re-login with same device, also refresh the stored IP field so the sessions page shows the current network address - feat(library): bulk remove and bulk shelf-change actions on /books Long-press any card to enter selection mode; sticky action bar with Move to shelf dropdown and Remove button; POST /api/library/bulk-remove and POST /api/library/bulk-shelf endpoints - fix(catalogue): make Scrape button visible with solid amber-500 fill and dark text instead of low-opacity ghost style that blended into card
This commit is contained in:
33
ui/src/routes/api/library/bulk-remove/+server.ts
Normal file
33
ui/src/routes/api/library/bulk-remove/+server.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { unsaveBook } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
/**
|
||||
* POST /api/library/bulk-remove
|
||||
* Body: { slugs: string[] }
|
||||
* Removes multiple books from the user's library at once.
|
||||
*/
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const body = await request.json().catch(() => null);
|
||||
const slugs: unknown = body?.slugs;
|
||||
if (!Array.isArray(slugs) || slugs.length === 0) {
|
||||
error(400, 'slugs must be a non-empty array');
|
||||
}
|
||||
const validSlugs = (slugs as unknown[]).filter((s): s is string => typeof s === 'string');
|
||||
if (validSlugs.length === 0) error(400, 'no valid slugs provided');
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
validSlugs.map((slug) => unsaveBook(locals.sessionId, slug, locals.user?.id))
|
||||
);
|
||||
|
||||
const failed = results
|
||||
.map((r, i) => (r.status === 'rejected' ? validSlugs[i] : null))
|
||||
.filter(Boolean);
|
||||
|
||||
if (failed.length > 0) {
|
||||
log.error('library', 'bulk-remove partial failure', { failed });
|
||||
}
|
||||
|
||||
return json({ ok: true, removed: validSlugs.length - failed.length, failed });
|
||||
};
|
||||
44
ui/src/routes/api/library/bulk-shelf/+server.ts
Normal file
44
ui/src/routes/api/library/bulk-shelf/+server.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { updateBookShelf } from '$lib/server/pocketbase';
|
||||
import type { ShelfName } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
const VALID_SHELVES: ShelfName[] = ['', 'plan_to_read', 'completed', 'dropped'];
|
||||
|
||||
/**
|
||||
* POST /api/library/bulk-shelf
|
||||
* Body: { slugs: string[], shelf: ShelfName }
|
||||
* Moves multiple books to the given shelf at once.
|
||||
*/
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const body = await request.json().catch(() => null);
|
||||
const slugs: unknown = body?.slugs;
|
||||
const shelf: unknown = body?.shelf;
|
||||
|
||||
if (!Array.isArray(slugs) || slugs.length === 0) {
|
||||
error(400, 'slugs must be a non-empty array');
|
||||
}
|
||||
if (typeof shelf !== 'string' || !VALID_SHELVES.includes(shelf as ShelfName)) {
|
||||
error(400, 'invalid shelf value');
|
||||
}
|
||||
|
||||
const validSlugs = (slugs as unknown[]).filter((s): s is string => typeof s === 'string');
|
||||
if (validSlugs.length === 0) error(400, 'no valid slugs provided');
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
validSlugs.map((slug) =>
|
||||
updateBookShelf(locals.sessionId, slug, shelf as ShelfName, locals.user?.id)
|
||||
)
|
||||
);
|
||||
|
||||
const failed = results
|
||||
.map((r, i) => (r.status === 'rejected' ? validSlugs[i] : null))
|
||||
.filter(Boolean);
|
||||
|
||||
if (failed.length > 0) {
|
||||
log.error('library', 'bulk-shelf partial failure', { failed, shelf });
|
||||
}
|
||||
|
||||
return json({ ok: true, updated: validSlugs.length - failed.length, failed });
|
||||
};
|
||||
Reference in New Issue
Block a user