Files
AfiS-Aktensystem/web/app/api/search/quick/route.ts
T
Harlekin da4a067850 Zuletzt geöffnet als Sprungliste, ohne Verlauf
Die letzte offene Zeile aus TASKS.md. Sie blieb offen, weil eine Spur über
jeden Aktenaufruf schnell etwas anderes wird als eine Bequemlichkeit.

Vier Festlegungen halten sie auf der richtigen Seite:

  - Kein Verlauf. Der Primärschlüssel (userId, targetType, targetId) lässt je
    Nutzer und Datensatz genau eine Zeile zu, die überschrieben wird. Die
    Tabelle kann nicht sagen, wie oft oder in welcher Reihenfolge gearbeitet
    wurde. Zwanzig Einträge, älteres fällt hinten heraus.
  - Nur die eigene. Keine Ansicht, kein Adminbereich, keine Funktion mit
    fremder Kennung. Jede Abfrage nimmt p.userId.
  - Jederzeit löschbar, ohne Rückfrage und ohne Grabstein.
  - Vorabrufe zählen nicht. Ohne die Prüfung auf next-router-prefetch stünde
    dort, worüber jemand mit der Maus gefahren ist.

Gelesen wird über dieselben Sichtbarkeitsfragmente wie überall: eine
vorgemerkte Kennung ist keine Berechtigung. Steigt eine Akte über die
Freigabestufe, verschwindet sie ohne Lücke und ohne Platzhalter; Personalakten
brauchen zusätzlich personnel.read. Das Zugriffsprotokoll bleibt unberührt und
bleibt die einzige Auskunft darüber, wer wann was gelesen hat.

Sie steht auf der Übersicht und im leeren Feld der Kommandopalette. Der zweite
Ort ist der eigentliche.

Nebenbei: NEXT_DIST_DIR, damit ein Build neben einem laufenden
Entwicklungsserver nicht dasselbe .next-Verzeichnis abräumt.
2026-09-03 10:52:23 +02:00

82 lines
2.8 KiB
TypeScript

import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getCurrentUser } from '@/lib/auth/current'
import { visibleCaseWhere } from '@/lib/access/caseFilter'
import { visiblePersonnelWhere } from '@/lib/access/personnelFilter'
import { listRecent } from '@/lib/recent/service'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
/** So viele Treffer je Art zeigt die Palette. Mehr passt nicht auf den Schirm. */
const LIMIT = 5
/** So viele Sprünge zeigt die Palette, solange nichts im Feld steht. */
const RECENT_IN_PALETTE = 6
/**
* Schnellsuche für die Kommandopalette.
*
* Bewusst schmal: Aktenzeichen, Titel und Namen, kein Volltext über Inhalte.
* Wer die Palette öffnet, will springen, nicht recherchieren — dafür gibt es
* `/suche`.
*
* Der Trigram-Index auf `caseNumber` und `displayName` fängt Tippfehler ab,
* siehe docs/08-suche-und-export.md.
*/
export async function GET(request: Request): Promise<Response> {
const user = await getCurrentUser()
if (!user) return NextResponse.json({ cases: [], personnel: [] }, { status: 404 })
const params = new URL(request.url).searchParams
// Leeres Feld: die eigene Sprungliste. Wer die Palette öffnet, will meistens
// dorthin zurück, wo er eben war — und nicht erst tippen.
if (params.get('recent') === '1') {
const zuletzt = await listRecent(user.permissions, RECENT_IN_PALETTE)
return NextResponse.json({
cases: [],
personnel: [],
recent: zuletzt.map((eintrag) => ({
href: eintrag.href,
primary: eintrag.label,
secondary: eintrag.secondary,
classification: eintrag.classification,
})),
})
}
const begriff = (params.get('q') ?? '').trim().slice(0, 100)
if (begriff.length < 2) return NextResponse.json({ cases: [], personnel: [] })
const enthaelt = { contains: begriff, mode: 'insensitive' as const }
const darfPersonal =
user.permissions.isAdmin || user.permissions.permissions.has('personnel.read')
const [faelle, personen] = await Promise.all([
db.case.findMany({
where: {
AND: [
visibleCaseWhere(user.permissions),
{ OR: [{ caseNumber: enthaelt }, { title: enthaelt }] },
],
},
orderBy: { updatedAt: 'desc' },
take: LIMIT,
select: { id: true, caseNumber: true, title: true, classification: true },
}),
darfPersonal
? db.personnelFile.findMany({
where: {
AND: [visiblePersonnelWhere(user.permissions), { displayName: enthaelt }],
},
orderBy: { displayName: 'asc' },
take: LIMIT,
select: { id: true, displayName: true, rank: true, classification: true },
})
: Promise.resolve([]),
])
return NextResponse.json({ cases: faelle, personnel: personen })
}