chore: check in pending work — queue preferences, QBO AR diagnostics, mobile engagement fixes, ops scripts
Bundles several in-progress efforts that were sitting uncommitted: - User queue-preferences (migration 087, API route, popover component) - QBO invoice soft-delete (migration 088) and AR diagnostics route - Dashboard/mobile engagement route and page adjustments - Docker Compose log-rotation config - One-off ticket/RMM investigation scripts (scripts/) - Planning docs: phase verification/pattern notes, mobile shell design spec - .gitignore: exclude local scratch financial/inventory data and Claude Code worktree/local-settings runtime state (never meant for version control) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
This commit is contained in:
parent
b638189cb0
commit
672f17b7f9
35 changed files with 2801 additions and 92 deletions
101
app/api/me/queue-preferences/route.ts
Normal file
101
app/api/me/queue-preferences/route.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth } from '@/lib/auth-utils';
|
||||
import { postgresClient } from '@/lib/services/postgres-client';
|
||||
|
||||
// GET /api/me/queue-preferences
|
||||
// -> { queues: [{ id, label, hidden }], hiddenIds: number[] }
|
||||
// Returns every active queue with a `hidden` flag for the calling user,
|
||||
// plus the bare list of hidden queue IDs (for clients that only need that).
|
||||
//
|
||||
// PUT /api/me/queue-preferences
|
||||
// body { hiddenIds: number[] } -> { hiddenIds: number[] }
|
||||
// Replaces the user's hidden-queue set atomically (full set semantics).
|
||||
//
|
||||
// Per-user only — writes target session.user.id, never an id from the body.
|
||||
|
||||
export async function GET(): Promise<NextResponse> {
|
||||
const { session, error } = await requireAuth();
|
||||
if (error) return error;
|
||||
|
||||
try {
|
||||
const result = await postgresClient.query<{
|
||||
value: number;
|
||||
label: string;
|
||||
hidden: boolean;
|
||||
}>(
|
||||
`SELECT q.value, q.label,
|
||||
(p.queue_id IS NOT NULL) AS hidden
|
||||
FROM queues q
|
||||
LEFT JOIN user_queue_preferences p
|
||||
ON p.queue_id = q.value AND p.user_id = $1
|
||||
WHERE q.is_active = true
|
||||
AND (q.is_deleted = false OR q.is_deleted IS NULL)
|
||||
ORDER BY q.label`,
|
||||
[session!.user.id],
|
||||
);
|
||||
const queues = result.rows.map((r) => ({
|
||||
id: r.value,
|
||||
label: r.label,
|
||||
hidden: r.hidden,
|
||||
}));
|
||||
const hiddenIds = queues.filter((q) => q.hidden).map((q) => q.id);
|
||||
return NextResponse.json({ queues, hiddenIds });
|
||||
} catch (e) {
|
||||
console.error('GET /api/me/queue-preferences failed:', e);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to read queue preferences', message: e instanceof Error ? e.message : 'unknown' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest): Promise<NextResponse> {
|
||||
const { session, error } = await requireAuth();
|
||||
if (error) return error;
|
||||
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid JSON', message: 'Request body must be JSON' },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const raw =
|
||||
body && typeof body === 'object' && 'hiddenIds' in body
|
||||
? (body as { hiddenIds: unknown }).hiddenIds
|
||||
: undefined;
|
||||
|
||||
if (!Array.isArray(raw) || !raw.every((v) => Number.isInteger(v))) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid input', message: 'hiddenIds must be an array of integers' },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
const hiddenIds = [...new Set(raw as number[])];
|
||||
|
||||
const userId = session!.user.id;
|
||||
|
||||
try {
|
||||
await postgresClient.transaction(async (client) => {
|
||||
await client.query('DELETE FROM user_queue_preferences WHERE user_id = $1', [userId]);
|
||||
if (hiddenIds.length > 0) {
|
||||
const placeholders = hiddenIds.map((_, i) => `($1, $${i + 2})`).join(', ');
|
||||
await client.query(
|
||||
`INSERT INTO user_queue_preferences (user_id, queue_id) VALUES ${placeholders}
|
||||
ON CONFLICT (user_id, queue_id) DO NOTHING`,
|
||||
[userId, ...hiddenIds],
|
||||
);
|
||||
}
|
||||
});
|
||||
return NextResponse.json({ hiddenIds });
|
||||
} catch (e) {
|
||||
console.error('PUT /api/me/queue-preferences failed:', e);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update queue preferences', message: e instanceof Error ? e.message : 'unknown' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue