generated from Dicken/dickendock
round14: Uebernehmen-Bug und Suggestion-Debounce fixen, Read-Later-Verwaltungsseite, konfigurierbares Read-Later-Limit
This commit is contained in:
@@ -55,6 +55,8 @@ export function ensureSchema(): void {
|
||||
visible INTEGER NOT NULL DEFAULT 1,
|
||||
display_name_edited_manually INTEGER NOT NULL DEFAULT 0,
|
||||
category_edited_manually INTEGER NOT NULL DEFAULT 0,
|
||||
last_suggested_display_name TEXT,
|
||||
last_suggested_category TEXT,
|
||||
port INTEGER NOT NULL,
|
||||
favicon TEXT,
|
||||
description TEXT,
|
||||
@@ -130,6 +132,8 @@ export function ensureSchema(): void {
|
||||
// 0 (nicht manuell bearbeitet) ist für Bestandsdaten die richtige Annahme.
|
||||
ensureColumn("services", "display_name_edited_manually", "INTEGER NOT NULL DEFAULT 0");
|
||||
ensureColumn("services", "category_edited_manually", "INTEGER NOT NULL DEFAULT 0");
|
||||
ensureColumn("services", "last_suggested_display_name", "TEXT");
|
||||
ensureColumn("services", "last_suggested_category", "TEXT");
|
||||
}
|
||||
|
||||
function ensureColumn(table: string, column: string, definition: string): void {
|
||||
|
||||
@@ -215,6 +215,12 @@ function findByDeviceAndPort(deviceId: string, port: number): Service | null {
|
||||
* wird das über nameChanges zur manuellen Bestätigung zurückgegeben, statt
|
||||
* entweder stillschweigend ignoriert oder automatisch übernommen zu werden.
|
||||
*
|
||||
* Ein abweichender Vorschlag wird ERST gemeldet, wenn er zwei Scans in Folge
|
||||
* derselbe ist (siehe lastSuggestedDisplayName/lastSuggestedCategory in
|
||||
* schema.ts) - Titel-Erkennung kann pro Scan leicht schwanken (z. B. eine
|
||||
* SPA, deren <title> nur gelegentlich rechtzeitig geladen ist), ein
|
||||
* einmaliger Ausrutscher soll nicht sofort als Änderung gemeldet werden.
|
||||
*
|
||||
* suggestedDisplayName/suggestedCategory/suggestedIcon/suggestedVisible
|
||||
* bestimmen den Anfangswert NUR beim erstmaligen Anlegen.
|
||||
*/
|
||||
@@ -223,21 +229,27 @@ export function upsertServiceFromScan(input: ServiceScanInput): ScanUpsertResult
|
||||
const timestamp = nowIso();
|
||||
|
||||
if (existing) {
|
||||
// Rohzeile inkl. der *EditedManually-Flags laden, die mapRow() bewusst
|
||||
// nicht nach außen gibt (nur intern für den Vergleich hier relevant).
|
||||
// Rohzeile inkl. der *EditedManually-/lastSuggested*-Felder laden, die
|
||||
// mapRow() bewusst nicht nach außen gibt (nur intern hier relevant).
|
||||
const existingRow = db.select().from(services).where(eq(services.id, existing.id)).get()!;
|
||||
|
||||
db.update(services)
|
||||
.set({
|
||||
favicon: input.favicon ?? existing.favicon,
|
||||
description: input.description ?? existing.description,
|
||||
lastSuggestedDisplayName: input.suggestedDisplayName ?? existingRow.lastSuggestedDisplayName,
|
||||
lastSuggestedCategory: input.suggestedCategory ?? existingRow.lastSuggestedCategory,
|
||||
updatedAt: timestamp,
|
||||
})
|
||||
.where(eq(services.id, existing.id))
|
||||
.run();
|
||||
|
||||
const nameChanges: ScanNameChange[] = [];
|
||||
if (input.suggestedDisplayName && input.suggestedDisplayName !== existing.displayName) {
|
||||
if (
|
||||
input.suggestedDisplayName &&
|
||||
input.suggestedDisplayName !== existing.displayName &&
|
||||
input.suggestedDisplayName === existingRow.lastSuggestedDisplayName
|
||||
) {
|
||||
nameChanges.push({
|
||||
field: "displayName",
|
||||
current: existing.displayName,
|
||||
@@ -248,7 +260,8 @@ export function upsertServiceFromScan(input: ServiceScanInput): ScanUpsertResult
|
||||
if (
|
||||
input.suggestedCategory !== undefined &&
|
||||
(input.suggestedCategory ?? null) !== existing.category &&
|
||||
input.suggestedCategory
|
||||
input.suggestedCategory &&
|
||||
input.suggestedCategory === existingRow.lastSuggestedCategory
|
||||
) {
|
||||
nameChanges.push({
|
||||
field: "category",
|
||||
@@ -279,6 +292,8 @@ export function upsertServiceFromScan(input: ServiceScanInput): ScanUpsertResult
|
||||
port: input.port,
|
||||
favicon: input.favicon ?? null,
|
||||
description: input.description ?? null,
|
||||
lastSuggestedDisplayName: input.suggestedDisplayName,
|
||||
lastSuggestedCategory: input.suggestedCategory ?? null,
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp,
|
||||
})
|
||||
|
||||
@@ -53,6 +53,14 @@ export const services = sqliteTable("services", {
|
||||
categoryEditedManually: integer("category_edited_manually", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(false),
|
||||
// Merkt sich den zuletzt vom Scanner berechneten Vorschlag (unabhängig
|
||||
// davon, ob er zu einer Änderungsmeldung führte). Dient dazu, einmalige
|
||||
// "Ausrutscher" abzufedern (z. B. eine SPA, deren <title> der Scanner nur
|
||||
// gelegentlich rechtzeitig sieht): ein neuer, abweichender Vorschlag wird
|
||||
// erst als Änderung gemeldet, wenn er beim ZWEITEN Mal in Folge wieder
|
||||
// auftaucht - siehe upsertServiceFromScan.
|
||||
lastSuggestedDisplayName: text("last_suggested_display_name"),
|
||||
lastSuggestedCategory: text("last_suggested_category"),
|
||||
|
||||
// Bei jedem Scan aktualisierbar
|
||||
port: integer("port").notNull(),
|
||||
|
||||
@@ -6,11 +6,14 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
|
||||
const all = settingsRepo.listSettings();
|
||||
return {
|
||||
recentVisitsLimit: Number(all.recentVisitsLimit ?? 5),
|
||||
readLaterLimit: Number(all.readLaterLimit ?? 5),
|
||||
};
|
||||
});
|
||||
|
||||
app.patch("/api/settings", async (request, reply) => {
|
||||
const body = request.body as { recentVisitsLimit?: number } | undefined;
|
||||
const body = request.body as
|
||||
| { recentVisitsLimit?: number; readLaterLimit?: number }
|
||||
| undefined;
|
||||
|
||||
if (body?.recentVisitsLimit !== undefined) {
|
||||
const value = Number(body.recentVisitsLimit);
|
||||
@@ -20,7 +23,18 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
|
||||
settingsRepo.setSetting("recentVisitsLimit", String(Math.round(value)));
|
||||
}
|
||||
|
||||
if (body?.readLaterLimit !== undefined) {
|
||||
const value = Number(body.readLaterLimit);
|
||||
if (!Number.isFinite(value) || value < 0 || value > 50) {
|
||||
return reply.code(400).send({ error: "readLaterLimit muss zwischen 0 und 50 liegen" });
|
||||
}
|
||||
settingsRepo.setSetting("readLaterLimit", String(Math.round(value)));
|
||||
}
|
||||
|
||||
const all = settingsRepo.listSettings();
|
||||
return { recentVisitsLimit: Number(all.recentVisitsLimit ?? 5) };
|
||||
return {
|
||||
recentVisitsLimit: Number(all.recentVisitsLimit ?? 5),
|
||||
readLaterLimit: Number(all.readLaterLimit ?? 5),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user