[37] Storage-backed LawNet BFF and frontend #47
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/37-bff-storage-frontend"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements a storage-backed LawNet BFF and frontend with both file-system (mock) and PostgreSQL storage backends.
What was done
Flyway V2 migration (
V2__lawnet_catalog.sql): Creates thecatalogschema withlawnet_record,lawnet_rendition, andlawnet_artifacttables including indexes and constraints.Storage contract (
src/db/storage.ts): Defines aLawnetStorageinterface withlistRecords,getRecord, andgetArtifactmethods.File-system storage (
src/db/fs-storage.ts): Reads mock data fromservices/web/mock/records.jsonand artifact files fromservices/web/mock/artifacts/. Includes 4 sample records with HTML and PDF artifacts.PostgreSQL storage (
src/db/pg-storage.ts): Queries thecatalogtables usingpg. Handles BIGINT → number conversion forsize_bytes.Storage factory (
src/db/factory.ts): Selects storage backend viaLAWNET_STORAGEenvironment variable. Defaults to file-system.BFF API routes:
GET /api/lawnet/records?limit=&offset=— paginated catalog listingGET /api/lawnet/records/:id— single record detail with renditions and artifactsGET /api/lawnet/artifacts/:id— artifact streaming with correct content-type and security headersFrontend pages:
/lawnet— dashboard with a sortable table showing citation, title, court, date, and rendition badges. Includes pagination controls./lawnet/:id— detail page showing metadata (court, decision, judges, parties, catchwords), rendition list with View HTML / Sanitized links, PDF downloads with file sizes, and an artifact details table.Navigation: Added nav bar to
__root.tsxwith links to Home and Catalog.Tests: 15 tests covering file-system storage (list/get/artifact), storage factory, and health endpoint.
How it works
LAWNET_STORAGE=filesystem(default) to use mock JSON + local HTML/PDF files. No PostgreSQL or Docker needed.LAWNET_STORAGE=postgresandDATABASE_URLto use the PostgreSQL catalog tables.Screenshots
Catalog page:
/lawnetDetail page:
/lawnet/1Testing
Closes: #37
591a1350d692e4e57998@ -9,1 +9,4 @@INTERNAL_API_TOKEN=# Storage backend: "filesystem" (default, uses mock JSON + local files) or "postgres"LAWNET_STORAGE=filesystemI think the environment variable shouldn't just be between storage and the file system/Postgres. It should point to the path of the JSON file or the storage directory. If no directory is provided, it falls back to Postgres. This is better and lets us change the mocks later.
@ -0,0 +24,4 @@COMMENT ON COLUMN catalog.lawnet_record.n_citation IS 'Neutral citation string, e.g. [2025] SGHC 1.';COMMENT ON COLUMN catalog.lawnet_record.source_metadata IS 'Raw source metadata payload from LawNet API responses.';CREATE TABLE catalog.lawnet_rendition (I'm wondering why we have a separate table for the rendition and the record. Can we flatten it? If not, can you give a reason why we can't? Let's think of the pros and cons. I'd like to combine these tables if possible and there are no big costs.
@ -0,0 +4,4 @@LawnetListResult,LawnetArtifactContent,} from "./storage";import pg from "pg";What is this import, just import thr component directly
92e4e57998133cfa848a@ -0,0 +1,74 @@CREATE SCHEMA IF NOT EXISTS catalog;I think we can have a better schema name than catalog.
@ -14,2 +17,3 @@import { Route as ApiLawnetCasesCaseIdRouteImport } from './routes/api/lawnet/cases.$caseId'import { Route as ApiLawnetArtifactsArtifactIdRouteImport } from './routes/api/lawnet/artifacts.$artifactId'const HealthRoute = HealthRouteImport.update({Why delete API health? Restore the health checkpoint.
@ -0,0 +3,4 @@import { createLawNetStorageFromEnv } from "../../../server/lawnet/config.server";import type { LawNetAvailability } from "../../../server/lawnet/types";export const Route = createFileRoute("/api/lawnet/cases")({Why so many query parameters for this route? Can you keep it minimal? Regarding the plan, I think there are too many unnecessary ones.
@ -0,0 +56,4 @@filename: string;};export type ListLawNetCasesInput = {Only accept limit and offset for list cases.
Pull request closed