Skip to content

feat: implement multi-site bibliography API endpoint#25

Merged
Rindrics merged 15 commits intomainfrom
issue-24-api
Feb 12, 2026
Merged

feat: implement multi-site bibliography API endpoint#25
Rindrics merged 15 commits intomainfrom
issue-24-api

Conversation

@Rindrics
Copy link
Owner

@Rindrics Rindrics commented Feb 8, 2026

Summary by CodeRabbit

  • New Features

    • New API endpoint to fetch bibliography filtered by site ID with validation and caching.
    • Bibliography entries now support per-site custom metadata (tags, reviews, read dates).
    • App page now loads bibliography from local generated data for a default site.
  • Tests

    • Added comprehensive tests for site-based filtering and siteId validation.
  • Chores

    • Switched data generation to emit a TypeScript data module and added test tooling configuration.

Open with Devin

@qodo-free-for-open-source-projects

Review Summary by Qodo

Implement multi-site bibliography API endpoint with filtering

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement multi-site bibliography API endpoint with siteId filtering
• Add filter utility function for site-specific bibliography items
• Include input validation and comprehensive error handling
• Configure HTTP caching headers for performance optimization
Diagram
flowchart LR
  A["GET /api/bibliography?siteId"] --> B["Validate siteId parameter"]
  B --> C["Fetch bibliography.json"]
  C --> D["Filter by siteId"]
  D --> E["Return filtered items with cache headers"]
Loading

Grey Divider

File Changes

1. web/src/lib/api/filter.ts ✨ Enhancement +27/-0

Add site-based bibliography filtering utility

• Create filterBySiteId function to filter bibliography items by site identifier
• Return items with defined customInfo metadata for the specified site
• Include documentation noting current limitation to 'akirahayashi_com' site
• Document future multi-site data generation pipeline requirements

web/src/lib/api/filter.ts


2. web/src/routes/api/bibliography/+server.ts ✨ Enhancement +59/-0

Create bibliography API endpoint with validation

• Implement GET endpoint at /api/bibliography with siteId query parameter
• Validate siteId format using regex pattern for alphanumeric, underscore, and dot characters
• Fetch full bibliography data from static JSON file and filter by site
• Return filtered results with HTTP caching headers (max-age=3600, stale-while-revalidate=86400)
• Handle error cases: missing parameter (400), invalid format (400), not found (404), load failure
 (500)

web/src/routes/api/bibliography/+server.ts


Grey Divider

Qodo Logo

@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds multi-site customInfo support across parsing, generation, types, and frontend; introduces a siteId-filtering utility and a GET /api/bibliography endpoint that returns site-scoped bibliography data with ETag caching; adds generated TypeScript data, tests, and Vitest config.

Changes

Cohort / File(s) Summary
Types
web/src/lib/types.ts
Changed BibliographyItem.customInfo from CustomInfoFrontend to a map { [siteId: string]: CustomInfoFrontend }.
Parser & Generation
web/src/lib/parsers/yaml.ts, web/scripts/generate-data.ts, web/src/lib/data/bibliography.ts
parseCustomInfo now builds per-entry per-site maps; generate-data emits a TS module bibliography.ts exporting bibliographyData and aggregates per-site frontend info. New data module added.
Filter & API
web/src/lib/api/filter.ts, web/src/routes/api/bibliography/+server.ts
Added filterBySiteId(items, siteId) and a GET /api/bibliography handler that validates siteId, filters data, attaches site-specific customInfo, computes ETag, and returns JSON with Cache-Control.
Frontend
web/src/routes/+page.ts
Replaced async fetch load with synchronous transform of local bibliographyData; uses DEFAULT_SITE_ID for site-specific lookup and omits memo fields.
Lib cleanup
web/src/lib/merge.ts
Removed legacy mergeBibliography helper (logic moved to generation script).
Tests & Tooling
web/src/lib/api/__tests__/filter.test.ts, web/vitest.config.ts, web/package.json
Added Vitest config and tests for filterBySiteId (filtering and siteId validation); updated package scripts to run data generation before checks and added vitest devDependency.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server as API
  participant Data as bibliographyData
  participant Filter as filterBySiteId
  participant Crypto as WebCrypto

  Client->>API: GET /api/bibliography?siteId=site.example
  API->>API: validate siteId (regex)
  API->>Data: read bibliographyData (in-memory)
  API->>Filter: filterBySiteId(items, siteId)
  Filter-->>API: filtered items
  API->>API: attach item.customInfo[siteId] to each item
  API->>Crypto: compute ETag from JSON
  Crypto-->>API: ETag
  API-->>Client: 200 JSON + ETag + Cache-Control
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Poem

🐇 I hopped through YAML, code, and seed,

Per-site notes planted like a deed.
Filters hum, the API sings,
Biblios bloom with tidy things.
Hop on — the sites now dance and read.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing a new multi-site bibliography API endpoint with filtering and ETag caching capabilities.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-24-api

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 8, 2026

Deploying kotetsu with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2788931
Status: ✅  Deploy successful!
Preview URL: https://9cb695a0.kotetsu.pages.dev
Branch Preview URL: https://issue-24-api.kotetsu.pages.dev

View logs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@web/src/routes/api/bibliography/`+server.ts:
- Around line 36-41: The catch block around the fetch/response.json in the GET
handler should use SvelteKit's isHttpError() instead of instanceof HttpError;
import isHttpError from '@sveltejs/kit' and replace any instanceof HttpError
checks with isHttpError(err) so HTTP errors are detected correctly and rethrown
(or handled) while other errors return a 500 — update the error handling around
the fetch('/data/bibliography.json') and response.json() calls accordingly to
use isHttpError(err) and rethrow the original error when true.
🧹 Nitpick comments (2)
web/src/routes/api/bibliography/+server.ts (2)

43-43: Type assertion without runtime validation.

response.json() returns any; the annotation BibliographyItem[] is a compile-time-only assertion. If bibliography.json has a schema mismatch (e.g., after a pipeline change), malformed data silently passes through to clients. For a static file you control this is low-risk, but consider adding a lightweight runtime check (e.g., Array.isArray(fullData)) as a safety net.


53-58: Redundant Content-Type header.

SvelteKit's json() helper already sets Content-Type: application/json. The explicit header on Line 56 is unnecessary.

♻️ Proposed fix
 	return json(filtered, {
 		headers: {
-			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
-			'Content-Type': 'application/json'
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400'
 		}
 	});

Comment on lines 36 to 41
// 2. Fetch full data from static file
const response = await fetch('/data/bibliography.json');

if (!response.ok) {
throw error(500, { message: 'Failed to load bibliography data' });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n web/src/routes/api/bibliography/+server.ts

Repository: Rindrics/kotetsu

Length of output: 2176


🌐 Web query:

SvelteKit HttpError export from @sveltejs/kit 2.x documentation

💡 Result:

In SvelteKit 2.x, HttpError is a TypeScript type/interface exported from @sveltejs/kit (it represents the object produced by calling error(...)). You typically import it as a type:

import type { HttpError } from '@sveltejs/kit';

Docs describe it as the object returned by error, with:

  • status: number (400–599)
  • body: App.Error [1]

If you need to detect these thrown errors (e.g. in hooks or try/catch), use SvelteKit’s type guard:

import { isHttpError } from '@sveltejs/kit';

(Also documented as an export from @sveltejs/kit.) [2]

Note: internally SvelteKit relies on instanceof checks against its HttpError implementation, which is why having multiple bundled copies of @sveltejs/kit can cause issues; this is discussed in SvelteKit’s changelog/PR notes. [3]

Sources:
[1] @sveltejs/kit module docs (HttpError section) https://svelte.nodejs.cn/docs/kit/%40sveltejs-kit
[2] SvelteKit “modules” docs (exports list includes isHttpError) https://kit.svelte.js.cn/docs/modules
[3] PR discussion mentioning HttpError instanceof checks https://svelte-changelog.dev/pull/sveltejs/kit/13971


Use isHttpError() to check for SvelteKit errors in the catch block.

Wrapping fetch and response.json() in a try/catch is good practice, but the proposed fix should use SvelteKit's isHttpError() function instead of instanceof HttpError to avoid bundling issues. Update the imports and error handling:

♻️ Corrected fix
-import { json, error } from '@sveltejs/kit';
+import { json, error, isHttpError } from '@sveltejs/kit';
-	const response = await fetch('/data/bibliography.json');
-
-	if (!response.ok) {
-		throw error(500, { message: 'Failed to load bibliography data' });
-	}
-
-	const fullData: BibliographyItem[] = await response.json();
+	let fullData: BibliographyItem[];
+	try {
+		const response = await fetch('/data/bibliography.json');
+		if (!response.ok) {
+			throw error(500, { message: 'Failed to load bibliography data' });
+		}
+		fullData = await response.json();
+	} catch (e) {
+		if (isHttpError(e)) throw e;
+		throw error(500, { message: 'Failed to load bibliography data' });
+	}
🤖 Prompt for AI Agents
In `@web/src/routes/api/bibliography/`+server.ts around lines 36 - 41, The catch
block around the fetch/response.json in the GET handler should use SvelteKit's
isHttpError() instead of instanceof HttpError; import isHttpError from
'@sveltejs/kit' and replace any instanceof HttpError checks with
isHttpError(err) so HTTP errors are detected correctly and rethrown (or handled)
while other errors return a 500 — update the error handling around the
fetch('/data/bibliography.json') and response.json() calls accordingly to use
isHttpError(err) and rethrow the original error when true.

@qodo-free-for-open-source-projects

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (6)

Grey Divider


Action required

1. Worker script file missing 📎 Requirement gap ✓ Correctness
Description
• The PR implements /api/bibliography as a SvelteKit route but does not add the required
  Cloudflare Workers script at /infrastructure/workers/bibliography-api.ts.
• Without the Worker script, the intended edge API implementation cannot be deployed or reviewed for
  correctness/security.
Code

web/src/routes/api/bibliography/+server.ts[R1-59]

+import type { RequestHandler } from './$types';
+import type { BibliographyItem } from '$lib/types';
+import { json, error } from '@sveltejs/kit';
+import { filterBySiteId } from '$lib/api/filter';
+
+/**
+ * GET /api/bibliography?siteId=<site-id>
+ *
+ * Returns filtered bibliography data for a specific site.
+ *
+ * Query Parameters:
+ *   - siteId (required): The site identifier (alphanumeric, underscore, dot)
+ *
+ * Response:
+ *   - 200: JSON array of BibliographyItem objects
+ *   - 400: Missing or invalid siteId parameter
+ *   - 404: siteId not found in database
+ *   - 500: Failed to load bibliography data
+ *
+ * Example:
+ *   GET /api/bibliography?siteId=akirahayashi_com
+ */
+export const GET: RequestHandler = async ({ url, fetch }) => {
+	// 1. Validate siteId parameter
+	const siteId = url.searchParams.get('siteId');
+
+	if (!siteId) {
+		throw error(400, { message: 'Missing required parameter: siteId' });
+	}
+
+	// Validate siteId format: alphanumeric, underscore, dot only
+	if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
+		throw error(400, { message: 'Invalid siteId format' });
+	}
+
+	// 2. Fetch full data from static file
+	const response = await fetch('/data/bibliography.json');
+
+	if (!response.ok) {
+		throw error(500, { message: 'Failed to load bibliography data' });
+	}
+
+	const fullData: BibliographyItem[] = await response.json();
+
+	// 3. Filter by siteId
+	const filtered = filterBySiteId(fullData, siteId);
+
+	if (filtered.length === 0) {
+		throw error(404, { message: `siteId not found: ${siteId}` });
+	}
+
+	// 4. Return with cache headers
+	return json(filtered, {
+		headers: {
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
+			'Content-Type': 'application/json'
+		}
+	});
+};
Evidence
Compliance requires a Worker implementation at a specific path. The PR adds an API handler under
web/src/routes/api/bibliography/+server.ts, and the existing Pulumi code shown does not define any
Worker resources, indicating the required Worker script/implementation is not present as specified.

Cloudflare Worker script created for Multi-Site Bibliography API
web/src/routes/api/bibliography/+server.ts[23-59]
infrastructure/index.ts[1-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The compliance checklist requires a Cloudflare Worker script at `/infrastructure/workers/bibliography-api.ts`, but the PR currently implements the endpoint as a SvelteKit route only.

## Issue Context
The multi-site bibliography API is intended to run at the edge via Cloudflare Workers.

## Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[1-59]
- infrastructure/index.ts[1-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. filterBySiteId hardcodes site 📎 Requirement gap ✓ Correctness
Description
filterBySiteId() only supports akirahayashi_com and returns [] for any other siteId,
  rather than filtering by customInfo[siteId] (or equivalent multi-site association).
• This does not meet the requirement to filter entries by the requested siteId, and will
  incorrectly treat other valid sites as not found.
Code

web/src/lib/api/filter.ts[R20-26]

+	// Current data only contains customInfo for 'akirahayashi_com'
+	if (siteId !== 'akirahayashi_com') {
+		return [];
+	}
+
+	// Return items that have customInfo (indicating they're tagged for this site)
+	return items.filter(item => item.customInfo !== undefined);
Evidence
The compliance requirement is to filter bibliography entries by the requested siteId using site
association data (e.g., customInfo[siteId]). The current implementation hardcodes a single site
and filters only on existence of customInfo, not on an association for the requested siteId.

API filters bibliography entries by required siteId query parameter
web/src/lib/api/filter.ts[16-26]
web/src/routes/api/bibliography/+server.ts[45-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`filterBySiteId()` hardcodes `akirahayashi_com` and does not filter using a per-site association (e.g., `customInfo[siteId]`). This breaks the intended multi-site behavior.

## Issue Context
Compliance requires `GET /api/bibliography?siteId=&lt;site-id&gt;` to return only entries relevant to that site based on site association metadata.

## Fix Focus Areas
- web/src/lib/api/filter.ts[16-27]
- web/src/routes/api/bibliography/+server.ts[23-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Error payload uses message 📎 Requirement gap ✓ Correctness
Description
• The 400 (missing/invalid siteId) and 404 (unknown siteId) errors use { message: ... } rather
  than the defined { "error": ... } payload shape.
• This makes client handling inconsistent with the ticket-defined contract and can break consumers
  expecting the specified error schema.
Code

web/src/routes/api/bibliography/+server.ts[R27-50]

+	if (!siteId) {
+		throw error(400, { message: 'Missing required parameter: siteId' });
+	}
+
+	// Validate siteId format: alphanumeric, underscore, dot only
+	if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
+		throw error(400, { message: 'Invalid siteId format' });
+	}
+
+	// 2. Fetch full data from static file
+	const response = await fetch('/data/bibliography.json');
+
+	if (!response.ok) {
+		throw error(500, { message: 'Failed to load bibliography data' });
+	}
+
+	const fullData: BibliographyItem[] = await response.json();
+
+	// 3. Filter by siteId
+	const filtered = filterBySiteId(fullData, siteId);
+
+	if (filtered.length === 0) {
+		throw error(404, { message: `siteId not found: ${siteId}` });
+	}
Evidence
The compliance checklist defines specific JSON error payloads for missing and unknown siteId. The
implementation throws SvelteKit errors with a message field instead of the required error field,
violating the specified response contract.

Missing siteId returns 400 Bad Request with defined error payload
Unknown siteId returns 404 Not Found with defined error payload
web/src/routes/api/bibliography/+server.ts[27-34]
web/src/routes/api/bibliography/+server.ts[48-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The endpoint returns 400/404 JSON payloads using a `message` key, but compliance requires a defined error payload shape (e.g., `{ &quot;error&quot;: &quot;...&quot; }`).

## Issue Context
External clients may parse the error JSON and depend on the exact key.

## Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[27-34]
- web/src/routes/api/bibliography/+server.ts[48-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. Missing ETag header 📎 Requirement gap ➹ Performance
Description
• Successful responses set Content-Type and Cache-Control, but do not include an ETag header
  required for cache validation.
• Cache-Control is not set to the specified value (public, max-age=3600) because it adds
  stale-while-revalidate=86400, which does not match the required configuration.
Code

web/src/routes/api/bibliography/+server.ts[R53-58]

+	return json(filtered, {
+		headers: {
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
+			'Content-Type': 'application/json'
+		}
+	});
Evidence
The compliance checklist requires Content-Type: application/json, `Cache-Control: public,
max-age=3600, and an ETag header on successful responses. The implementation omits ETag` and
uses a different Cache-Control value than specified.

Correct response headers set (Content-Type, Cache-Control, ETag)
web/src/routes/api/bibliography/+server.ts[53-58]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The endpoint is missing the required `ETag` header and sets `Cache-Control` to a value that differs from the required configuration.

## Issue Context
ETag enables conditional requests and correct cache validation for external site builds.

## Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[52-58]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Pulumi lacks Worker route 📎 Requirement gap ⛯ Reliability
Description
• The infrastructure-as-code currently provisions a Cloudflare Pages domain but does not deploy a
  Worker or bind the /api/bibliography route.
• Without Pulumi configuration for the Worker and route mapping, the API deployment is
  manual/undefined and does not meet the required exposure/configuration criteria.
Code

web/src/routes/api/bibliography/+server.ts[R1-59]

+import type { RequestHandler } from './$types';
+import type { BibliographyItem } from '$lib/types';
+import { json, error } from '@sveltejs/kit';
+import { filterBySiteId } from '$lib/api/filter';
+
+/**
+ * GET /api/bibliography?siteId=<site-id>
+ *
+ * Returns filtered bibliography data for a specific site.
+ *
+ * Query Parameters:
+ *   - siteId (required): The site identifier (alphanumeric, underscore, dot)
+ *
+ * Response:
+ *   - 200: JSON array of BibliographyItem objects
+ *   - 400: Missing or invalid siteId parameter
+ *   - 404: siteId not found in database
+ *   - 500: Failed to load bibliography data
+ *
+ * Example:
+ *   GET /api/bibliography?siteId=akirahayashi_com
+ */
+export const GET: RequestHandler = async ({ url, fetch }) => {
+	// 1. Validate siteId parameter
+	const siteId = url.searchParams.get('siteId');
+
+	if (!siteId) {
+		throw error(400, { message: 'Missing required parameter: siteId' });
+	}
+
+	// Validate siteId format: alphanumeric, underscore, dot only
+	if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
+		throw error(400, { message: 'Invalid siteId format' });
+	}
+
+	// 2. Fetch full data from static file
+	const response = await fetch('/data/bibliography.json');
+
+	if (!response.ok) {
+		throw error(500, { message: 'Failed to load bibliography data' });
+	}
+
+	const fullData: BibliographyItem[] = await response.json();
+
+	// 3. Filter by siteId
+	const filtered = filterBySiteId(fullData, siteId);
+
+	if (filtered.length === 0) {
+		throw error(404, { message: `siteId not found: ${siteId}` });
+	}
+
+	// 4. Return with cache headers
+	return json(filtered, {
+		headers: {
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
+			'Content-Type': 'application/json'
+		}
+	});
+};
Evidence
Compliance requires Pulumi to deploy the Worker and bind it to /api/bibliography. The current
Pulumi program only configures a Pages domain and contains no Worker/route resources, indicating the
required deployment and binding are missing.

Pulumi deploys Worker and binds /api/bibliography route with required configuration
infrastructure/index.ts[1-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pulumi does not currently deploy a Worker or bind the `/api/bibliography` route, which is required for the multi-site bibliography API.

## Issue Context
The endpoint is intended to be exposed via Cloudflare Workers at `/api/bibliography`.

## Fix Focus Areas
- infrastructure/index.ts[1-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Unhandled parse failures 🐞 Bug ⛯ Reliability
Description
fetch() network errors or response.json() parse errors are not caught; these will bubble up as
  unhandled exceptions.
• In those cases clients may receive SvelteKit’s default 500 response (potentially non-JSON),
  despite the endpoint doc claiming a structured 500 for “Failed to load bibliography data”.
• This reduces reliability and makes client-side error handling inconsistent.
Code

web/src/routes/api/bibliography/+server.ts[R36-44]

+	// 2. Fetch full data from static file
+	const response = await fetch('/data/bibliography.json');
+
+	if (!response.ok) {
+		throw error(500, { message: 'Failed to load bibliography data' });
+	}
+
+	const fullData: BibliographyItem[] = await response.json();
+
Evidence
The API handler only checks response.ok and then calls response.json() directly, with no
try/catch around either the fetch or JSON parse step. Existing page-load code demonstrates the repo
already expects these operations to fail and handles them explicitly with try/catch.

web/src/routes/api/bibliography/+server.ts[36-44]
web/src/routes/api/bibliography/+server.ts[14-19]
web/src/routes/+page.ts[5-19]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`GET /api/bibliography` does not catch exceptions from `fetch()` or `response.json()`. Network errors or malformed JSON will cause unhandled exceptions and may return non-JSON 500 responses, breaking the documented API contract.

### Issue Context
The page loader already uses try/catch for the same `/data/bibliography.json` fetch, indicating this repo expects and handles such failures.

### Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[36-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. No endpoint tests provided 📎 Requirement gap ⛯ Reliability
Description
• The PR adds the /api/bibliography endpoint but does not include automated tests or documented
  manual verification covering the required scenarios.
• This increases the risk of regressions in required behaviors (valid filtering, 400/404 cases, memo
  non-exposure, and required caching headers).
Code

web/src/routes/api/bibliography/+server.ts[R23-59]

+export const GET: RequestHandler = async ({ url, fetch }) => {
+	// 1. Validate siteId parameter
+	const siteId = url.searchParams.get('siteId');
+
+	if (!siteId) {
+		throw error(400, { message: 'Missing required parameter: siteId' });
+	}
+
+	// Validate siteId format: alphanumeric, underscore, dot only
+	if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
+		throw error(400, { message: 'Invalid siteId format' });
+	}
+
+	// 2. Fetch full data from static file
+	const response = await fetch('/data/bibliography.json');
+
+	if (!response.ok) {
+		throw error(500, { message: 'Failed to load bibliography data' });
+	}
+
+	const fullData: BibliographyItem[] = await response.json();
+
+	// 3. Filter by siteId
+	const filtered = filterBySiteId(fullData, siteId);
+
+	if (filtered.length === 0) {
+		throw error(404, { message: `siteId not found: ${siteId}` });
+	}
+
+	// 4. Return with cache headers
+	return json(filtered, {
+		headers: {
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
+			'Content-Type': 'application/json'
+		}
+	});
+};
Evidence
The compliance checklist requires evidence of testing covering valid/invalid inputs, memo
non-exposure, and caching headers. The PR description provides no testing notes, and the changes
shown introduce the endpoint without accompanying test additions in the PR diff.

Endpoint tested for valid/invalid inputs and memo non-exposure
web/src/routes/api/bibliography/+server.ts[23-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
There is no evidence of testing for the new `/api/bibliography` endpoint against the required acceptance criteria.

## Issue Context
Compliance requires coverage for: valid `siteId` filtering, missing `siteId` = 400, unknown `siteId` = 404, memo non-exposure, and caching headers.

## Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[23-59]
- web/src/lib/api/filter.ts[16-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

8. Single-site limit undocumented 🐞 Bug ✓ Correctness
Description
• The endpoint documentation reads like a general multi-site API, but the actual implementation only
  supports akirahayashi_com (everything else becomes a 404).
• This is easy to misinterpret for callers since the endpoint’s JSDoc doesn’t mention the
  limitation, while the filter helper’s comment does.
• Clarifying the limitation (or returning a distinct 400 for “unsupported siteId”) will reduce
  integration confusion and support load.
Code

web/src/routes/api/bibliography/+server.ts[R6-22]

+/**
+ * GET /api/bibliography?siteId=<site-id>
+ *
+ * Returns filtered bibliography data for a specific site.
+ *
+ * Query Parameters:
+ *   - siteId (required): The site identifier (alphanumeric, underscore, dot)
+ *
+ * Response:
+ *   - 200: JSON array of BibliographyItem objects
+ *   - 400: Missing or invalid siteId parameter
+ *   - 404: siteId not found in database
+ *   - 500: Failed to load bibliography data
+ *
+ * Example:
+ *   GET /api/bibliography?siteId=akirahayashi_com
+ */
Evidence
The endpoint’s comment describes a generic siteId-based API but does not state it is currently
limited to one site. The filtering helper explicitly documents the single-site limitation and
enforces it by returning an empty list for all other siteIds.

web/src/routes/api/bibliography/+server.ts[6-22]
web/src/lib/api/filter.ts[4-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The endpoint appears to support arbitrary `siteId` values, but the actual implementation only supports a single site. This limitation is documented in the filter helper but not at the API surface.

### Issue Context
Callers will likely consult the endpoint documentation rather than internal helper comments.

### Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[6-22]
- web/src/lib/api/filter.ts[4-10]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines 1 to 59
import type { RequestHandler } from './$types';
import type { BibliographyItem } from '$lib/types';
import { json, error } from '@sveltejs/kit';
import { filterBySiteId } from '$lib/api/filter';

/**
* GET /api/bibliography?siteId=<site-id>
*
* Returns filtered bibliography data for a specific site.
*
* Query Parameters:
* - siteId (required): The site identifier (alphanumeric, underscore, dot)
*
* Response:
* - 200: JSON array of BibliographyItem objects
* - 400: Missing or invalid siteId parameter
* - 404: siteId not found in database
* - 500: Failed to load bibliography data
*
* Example:
* GET /api/bibliography?siteId=akirahayashi_com
*/
export const GET: RequestHandler = async ({ url, fetch }) => {
// 1. Validate siteId parameter
const siteId = url.searchParams.get('siteId');

if (!siteId) {
throw error(400, { message: 'Missing required parameter: siteId' });
}

// Validate siteId format: alphanumeric, underscore, dot only
if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
throw error(400, { message: 'Invalid siteId format' });
}

// 2. Fetch full data from static file
const response = await fetch('/data/bibliography.json');

if (!response.ok) {
throw error(500, { message: 'Failed to load bibliography data' });
}

const fullData: BibliographyItem[] = await response.json();

// 3. Filter by siteId
const filtered = filterBySiteId(fullData, siteId);

if (filtered.length === 0) {
throw error(404, { message: `siteId not found: ${siteId}` });
}

// 4. Return with cache headers
return json(filtered, {
headers: {
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
'Content-Type': 'application/json'
}
});
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Worker script file missing 📎 Requirement gap ✓ Correctness

• The PR implements /api/bibliography as a SvelteKit route but does not add the required
  Cloudflare Workers script at /infrastructure/workers/bibliography-api.ts.
• Without the Worker script, the intended edge API implementation cannot be deployed or reviewed for
  correctness/security.
Agent Prompt
## Issue description
The compliance checklist requires a Cloudflare Worker script at `/infrastructure/workers/bibliography-api.ts`, but the PR currently implements the endpoint as a SvelteKit route only.

## Issue Context
The multi-site bibliography API is intended to run at the edge via Cloudflare Workers.

## Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[1-59]
- infrastructure/index.ts[1-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 27 to 50
if (!siteId) {
throw error(400, { message: 'Missing required parameter: siteId' });
}

// Validate siteId format: alphanumeric, underscore, dot only
if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
throw error(400, { message: 'Invalid siteId format' });
}

// 2. Fetch full data from static file
const response = await fetch('/data/bibliography.json');

if (!response.ok) {
throw error(500, { message: 'Failed to load bibliography data' });
}

const fullData: BibliographyItem[] = await response.json();

// 3. Filter by siteId
const filtered = filterBySiteId(fullData, siteId);

if (filtered.length === 0) {
throw error(404, { message: `siteId not found: ${siteId}` });
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Error payload uses message 📎 Requirement gap ✓ Correctness

• The 400 (missing/invalid siteId) and 404 (unknown siteId) errors use { message: ... } rather
  than the defined { "error": ... } payload shape.
• This makes client handling inconsistent with the ticket-defined contract and can break consumers
  expecting the specified error schema.
Agent Prompt
## Issue description
The endpoint returns 400/404 JSON payloads using a `message` key, but compliance requires a defined error payload shape (e.g., `{ "error": "..." }`).

## Issue Context
External clients may parse the error JSON and depend on the exact key.

## Fix Focus Areas
- web/src/routes/api/bibliography/+server.ts[27-34]
- web/src/routes/api/bibliography/+server.ts[48-50]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 1 to 59
import type { RequestHandler } from './$types';
import type { BibliographyItem } from '$lib/types';
import { json, error } from '@sveltejs/kit';
import { filterBySiteId } from '$lib/api/filter';

/**
* GET /api/bibliography?siteId=<site-id>
*
* Returns filtered bibliography data for a specific site.
*
* Query Parameters:
* - siteId (required): The site identifier (alphanumeric, underscore, dot)
*
* Response:
* - 200: JSON array of BibliographyItem objects
* - 400: Missing or invalid siteId parameter
* - 404: siteId not found in database
* - 500: Failed to load bibliography data
*
* Example:
* GET /api/bibliography?siteId=akirahayashi_com
*/
export const GET: RequestHandler = async ({ url, fetch }) => {
// 1. Validate siteId parameter
const siteId = url.searchParams.get('siteId');

if (!siteId) {
throw error(400, { message: 'Missing required parameter: siteId' });
}

// Validate siteId format: alphanumeric, underscore, dot only
if (!/^[a-zA-Z0-9_.]+$/.test(siteId)) {
throw error(400, { message: 'Invalid siteId format' });
}

// 2. Fetch full data from static file
const response = await fetch('/data/bibliography.json');

if (!response.ok) {
throw error(500, { message: 'Failed to load bibliography data' });
}

const fullData: BibliographyItem[] = await response.json();

// 3. Filter by siteId
const filtered = filterBySiteId(fullData, siteId);

if (filtered.length === 0) {
throw error(404, { message: `siteId not found: ${siteId}` });
}

// 4. Return with cache headers
return json(filtered, {
headers: {
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
'Content-Type': 'application/json'
}
});
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Pulumi lacks worker route 📎 Requirement gap ⛯ Reliability

• The infrastructure-as-code currently provisions a Cloudflare Pages domain but does not deploy a
  Worker or bind the /api/bibliography route.
• Without Pulumi configuration for the Worker and route mapping, the API deployment is
  manual/undefined and does not meet the required exposure/configuration criteria.
Agent Prompt
## Issue description
Pulumi does not currently deploy a Worker or bind the `/api/bibliography` route, which is required for the multi-site bibliography API.

## Issue Context
The endpoint is intended to be exposed via Cloudflare Workers at `/api/bibliography`.

## Fix Focus Areas
- infrastructure/index.ts[1-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/scripts/generate-data.ts (2)

1-3: ⚠️ Potential issue | 🟡 Minor

Stale comment: still references bibliography.json.

The doc comment on line 2 says "Generate bibliography.json" but the script now generates bibliography.ts.

Proposed fix
 /**
- * Build script to generate bibliography.json from bib and yaml files
+ * Build script to generate bibliography.ts from bib and yaml files
  * Run with: npx tsx scripts/generate-data.ts
  */

113-113: ⚠️ Potential issue | 🟡 Minor

Stale log message: still says bibliography.json.

-	console.log('Generating bibliography.json...');
+	console.log('Generating bibliography data...');
🤖 Fix all issues with AI agents
In `@docs/issues/pr-25.md`:
- Around line 20-28: The "filterBySiteId hardcoding" issue is stale because the
current implementation of filterBySiteId already uses item.customInfo?.[siteId];
remove or mark the issue resolved in the docs to avoid confusion by deleting the
stale entry or adding a short note referencing the updated implementation
(filterBySiteId function) and the relevant server handler (bibliography +server
handler) so reviewers know the bug was fixed; ensure the docs/no-opting issue
list no longer claims hardcoded akirahayashi_com.
- Line 1: Remove the accidental Japanese prompt string "それぞれ英語の conventional
commit message を示しつつ直して" from the document content and replace it with proper
English documentation showing conventional commit examples (e.g., "feat: add X",
"fix: correct Y", "docs: update Z") and a brief one-line explanation of the
format; locate the stray prompt by searching for that exact phrase and update
the surrounding text to be user-facing documentation in English rather than an
instruction.

In `@web/scripts/generate-data.ts`:
- Around line 125-131: The generated TypeScript module (created in
generate-data.ts via writeFileSync to outputPath as bibliography.ts) is missing
during CI type-checks and breaks imports in +page.ts/+server.ts; add a
declaration file named bibliography.d.ts alongside where bibliography.ts will be
generated that declares export const bibliographyData: BibliographyItem[]
(importing BibliographyItem from your types) so the TS compiler sees the symbol
during pnpm check, or alternatively ensure your CI runs npx tsx
scripts/generate-data.ts before pnpm check or commit a minimal stub
bibliography.ts exporting an empty array.

In `@web/src/routes/api/bibliography/`+server.ts:
- Line 4: CI is failing because the generated module imported as
bibliographyData (import { bibliographyData } from '$lib/data/bibliography')
does not exist during type checking; fix by either updating the CI pipeline to
run the generator (run web/scripts/generate-data.ts before pnpm check/build so
web/src/lib/data/bibliography.ts is created) or add a committed type
stub/declaration (e.g., declare module '$lib/data/bibliography' exporting
bibliographyData) to satisfy TypeScript during CI; choose one approach and apply
it so the import in +server.ts resolves during the check step.
🧹 Nitpick comments (5)
web/src/lib/parsers/yaml.ts (1)

14-41: validateCustomInfo type guard doesn't reject unknown fields.

The type guard asserts value is CustomInfoFull but only checks known fields, allowing objects with extra properties to pass through. This is generally acceptable since downstream toFrontendInfo in generate-data.ts explicitly picks only the safe fields (tags, review, readDate). However, if this guard is ever used in a context where the object is passed through directly, unexpected fields could leak.

web/src/routes/api/bibliography/+server.ts (3)

39-41: 404 for empty results is ambiguous.

A valid siteId format with no matching entries returns 404, which is indistinguishable from a truly unknown/non-existent site. Consider whether returning an empty 200 array would be more appropriate for "valid site, no entries" vs a true "site not found" check against a known list of site IDs.


50-55: Content-Type header is redundant.

SvelteKit's json() helper already sets Content-Type: application/json. Explicitly setting it is harmless but unnecessary.

Proposed fix
 	return json(transformed, {
 		headers: {
-			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
-			'Content-Type': 'application/json'
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400'
 		}
 	});

44-47: Transform logic duplicated with +page.ts.

Lines 44–47 mirror the same spread-and-extract pattern in web/src/routes/+page.ts (lines 10–14). Consider extracting a shared helper (e.g., transformForSite(items, siteId)) to keep the transformation in one place.

web/src/routes/+page.ts (1)

6-6: DEFAULT_SITE_ID is a magic string — consider sharing it.

This constant is defined locally and the same value 'akirahayashi_com' implicitly drives the API endpoint behavior as well. If this default is project-wide, extracting it to a shared config/constants module would prevent drift.

Comment on lines +125 to +131
// Write TypeScript module
const outputPath = join(outputDir, 'bibliography.ts');
const moduleContent = `import type { BibliographyItem } from '../types';

export const bibliographyData: BibliographyItem[] = ${JSON.stringify(items, null, 2)};
`;
writeFileSync(outputPath, moduleContent);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Generated .ts module is the root cause of CI pipeline failures.

The script writes bibliography.ts to src/lib/data/, which is then imported by +page.ts and +server.ts. Since this file is generated at build time and likely .gitignored, CI's pnpm check fails because the module doesn't exist yet.

Options to fix:

  1. Run the generation script before type-checking in CI (e.g., add npx tsx scripts/generate-data.ts step before pnpm check).
  2. Commit a placeholder/stub bibliography.ts with an empty array export.
  3. Add a type declaration file (bibliography.d.ts) so type-checking passes without the generated file.

Option 3 is the most robust as it decouples type-checking from data generation:

// web/src/lib/data/bibliography.d.ts
import type { BibliographyItem } from '../types';
export declare const bibliographyData: BibliographyItem[];
🤖 Prompt for AI Agents
In `@web/scripts/generate-data.ts` around lines 125 - 131, The generated
TypeScript module (created in generate-data.ts via writeFileSync to outputPath
as bibliography.ts) is missing during CI type-checks and breaks imports in
+page.ts/+server.ts; add a declaration file named bibliography.d.ts alongside
where bibliography.ts will be generated that declares export const
bibliographyData: BibliographyItem[] (importing BibliographyItem from your
types) so the TS compiler sees the symbol during pnpm check, or alternatively
ensure your CI runs npx tsx scripts/generate-data.ts before pnpm check or commit
a minimal stub bibliography.ts exporting an empty array.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@web/package.json`:
- Around line 31-32: The package.json currently lists incompatible versions
"vite": "^7.2.6" and "vitest": "^1.6.0"; update the vitest dependency to a 3.2+
release (e.g., change "vitest" to "^3.2.0") so it supports Vite 7, then
reinstall/update the lockfile (npm/yarn/pnpm) and run the test suite; if
tests/configs reference Vitest APIs, adjust config files (vite/vitest config or
test setup) for any 3.x breaking changes.

In `@web/src/routes/`+page.ts:
- Around line 8-15: The current load() maps bibliographyData and assigns
customInfo: item.customInfo?.[DEFAULT_SITE_ID], but that runtime object is
CustomInfoFull and may include the sensitive memo field; update the mapping in
load() to destructure the site-specific custom info and exclude memo (e.g.,
const { memo, ...frontend } = item.customInfo?.[DEFAULT_SITE_ID] || {}) and
assign customInfo to the frontend object (typed as CustomInfoFrontend |
undefined) so memo is not serialized to the client; refer to load,
bibliographyData, DEFAULT_SITE_ID, CustomInfoFrontend and the memo property when
making the change.

In `@web/src/routes/api/bibliography/`+server.test.ts:
- Around line 4-43: The tests define mockBibliographyData but never inject
it—because the GET handler imports bibliographyData from $lib/data/bibliography
at module scope, tests exercise the real module; update the test to mock that
module: use vitest's vi.mock to replace $lib/data/bibliography with an object
exporting bibliographyData (use the mockBibliographyData array) before importing
or invoking the GET handler so the handler (GET) reads the mocked
bibliographyData instead of the real/generated data.

In `@web/vitest.config.ts`:
- Around line 1-9: The Vitest config lacks SvelteKit path alias resolution so
imports using SvelteKit’s $lib will fail; update the config returned by
defineConfig in web/vitest.config.ts to enable SvelteKit aliasing by either
importing and applying the Vite config from `@sveltejs/kit/vite` (or its plugin)
or by adding a resolve.alias entry that maps '$lib' to 'src/lib' (ensure the
change is applied inside the exported defineConfig({ test: {...}, resolve: {
alias: [...] } }) object so tests can resolve $lib imports).
🧹 Nitpick comments (2)
web/src/lib/api/__tests__/filter.test.ts (1)

103-117: siteId regex is duplicated — drift risk with the server handler.

This test defines its own regex /^[a-zA-Z0-9_.]+$/ rather than importing or sharing the pattern from the server handler. If the validation regex in +server.ts changes, this test will silently pass with stale rules. Consider extracting the regex into a shared constant.

web/src/routes/+page.ts (1)

6-6: Consider sharing DEFAULT_SITE_ID as a constant.

This value is also conceptually used in the API route handler's logic. Extracting it to a shared module would avoid drift.

- Change BibliographyItem.customInfo from single site to per-site map
- Update parseCustomInfo to return all sites' metadata (not just one)
- Support multiple sites via customInfo[siteId] structure
- Update filterBySiteId to properly filter by site association
- Transform API responses to extract site-specific customInfo
- Update page loader to use bibliography module directly
- Remove unused merge.ts (replaced by inline transformation)

    Enables proper multi-site bibliography API support.
- Change error response key from 'message' to 'error'
- Use json() with status option instead of error() for consistency
- Ensures external clients can reliably parse error payloads

Affected endpoints:
- GET /api/bibliography?siteId= (400 and 404 responses)
- Ensure bibliography.ts is generated before type checking
- Fixes CI failures when checking out fresh repository
- Generate ETag from response content using SHA-256 hash
- Enable conditional requests and cache validation
- Supports external site builds with proper cache control

Endpoints affected:
- GET /api/bibliography?siteId= (200 responses)
- Add comprehensive JSDoc for GET /api/bibliography endpoint
- Document response body structure with all fields
- Clarify single-site limitation and use cases
- Document error response format
- Add cache control and ETag behavior
- Explain security rationale for memo field exclusion
- Update filterBySiteId documentation for clarity

Helps external consumers understand API contract and limitations.
Tests cover:
- Missing or invalid siteId parameter validation (400)
- Non-existent siteId handling (404)
- Proper filtering by siteId (200)
- memo field exclusion for security
- ETag header generation
- Cache-Control header configuration
- Valid/invalid siteId format acceptance

Includes vitest setup with test and test:ui scripts.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/scripts/generate-data.ts (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Stale references to bibliography.json.

The doc comment (Line 2) and the console.log at Line 113 still reference bibliography.json, but the script now generates bibliography.ts.

Proposed fix
 /**
- * Build script to generate bibliography.json from bib and yaml files
+ * Build script to generate bibliography.ts from bib and yaml files
  * Run with: npx tsx scripts/generate-data.ts
  */

At Line 113:

-	console.log('Generating bibliography.json...');
+	console.log('Generating bibliography.ts...');
🧹 Nitpick comments (4)
web/src/lib/data/bibliography.ts (1)

1-60: Generated data file looks consistent with the type system.

This file appears to be output from generate-data.ts. If so, consider adding a generated-file header comment (e.g., // AUTO-GENERATED — do not edit manually) to prevent accidental hand-edits and to help tooling (linters, formatters) skip it.

web/src/routes/api/bibliography/+server.ts (2)

66-82: Double JSON serialization and redundant Content-Type header.

json(transformed) on Line 76 internally calls JSON.stringify again, meaning the response body is serialized twice (Line 72 for the ETag, and again inside json()). Also, SvelteKit's json() helper already sets Content-Type: application/json, making Line 80 redundant.

Proposed optimization
 	const responseBody = JSON.stringify(transformed);
 	const etag = `"${createHash('sha256').update(responseBody).digest('hex').slice(0, 16)}"`;

-	return json(transformed, {
+	return new Response(responseBody, {
 		headers: {
 			'ETag': etag,
-			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
-			'Content-Type': 'application/json'
+			'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
+			'Content-Type': 'application/json'
 		}
 	});

45-83: No If-None-Match handling despite ETag generation.

The endpoint computes and returns an ETag but never checks the incoming If-None-Match request header. Without that check, the ETag provides no bandwidth savings — the full response is always sent. Consider returning a 304 Not Modified when the client's ETag matches.

Proposed addition after ETag computation
 	const etag = `"${createHash('sha256').update(responseBody).digest('hex').slice(0, 16)}"`;

+	const ifNoneMatch = url.searchParams // wrong — use request headers
+	// Access request headers via the event:
+	// In SvelteKit, destructure `request` from the event:
+	// export const GET: RequestHandler = async ({ url, request }) => {
+	// const ifNoneMatch = request.headers.get('if-none-match');
+	// if (ifNoneMatch === etag) {
+	//     return new Response(null, { status: 304 });
+	// }

Specifically, update the handler signature and add the conditional:

-export const GET: RequestHandler = async ({ url }) => {
+export const GET: RequestHandler = async ({ url, request }) => {

Then, after computing the ETag:

+	if (request.headers.get('if-none-match') === etag) {
+		return new Response(null, { status: 304 });
+	}
web/src/routes/+page.ts (1)

6-6: Consider extracting DEFAULT_SITE_ID to a shared config.

This constant is hardcoded here. If other parts of the codebase (or tests) need to reference the default site, having it in a single shared location would prevent drift.

Add test suite for filterBySiteId function covering:
- Filtering entries by siteId
- Excluding entries without siteId metadata
- Handling different siteIds
- Non-existent siteId behavior
- Field preservation
- siteId format validation
- Remove web/src/lib/data/ from .gitignore
- Include generated bibliography.ts in repository
- Ensures TypeScript imports resolve during CI type checks
- Reduces CI runtime by avoiding regeneration step
- Update vitest dependency from ^1.6.0 to ^3.2.0
- Resolves compatibility issue with Vite 7.2.6
- Enables latest test runner features and improvements
- Add resolve.alias configuration for $lib → src/lib
- Enables tests to import using SvelteKit's \$lib alias
- Resolves module import failures during test execution
- Tests can now use same import paths as application code
…bility)

- Replace node:crypto with Web Crypto API's crypto.subtle.digest
- Generates SHA-256 hash using platform-native APIs
- Resolves 'crypto not found' error during Cloudflare Pages deployment
- Enables deployment to serverless/edge environments

The Web Crypto API is universally supported in all JavaScript runtimes
including Node.js, Deno, Cloudflare Workers, and browsers.
- Add request parameter to GET handler
- Check If-None-Match header and return 304 Not Modified when ETag matches
- Enables HTTP cache validation and bandwidth savings
- Clients can now use conditional requests for efficient data fetching

304 responses bypass response body transmission when data hasn't changed.
- Create web/src/lib/config/constants.ts for application-wide constants
- Move DEFAULT_SITE_ID from +page.ts to constants file
- Enables single source of truth for default site identifier
- Prevents drift if other modules need to reference default site

Future improvements can add more constants to this file.
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +80 to +83
const ifNoneMatch = request.headers.get('if-none-match');
if (ifNoneMatch === etag) {
return new Response(null, { status: 304 });
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 If-None-Match comparison doesn't handle multiple ETags per HTTP spec

The If-None-Match header comparison at web/src/routes/api/bibliography/+server.ts:81 uses strict string equality (ifNoneMatch === etag), but per RFC 7232 Section 3.2, the If-None-Match header can contain a comma-separated list of ETags (e.g., "abc", "def") or the wildcard *.

Root Cause and Impact

The HTTP spec defines If-None-Match as "*" / 1#entity-tag, meaning it can contain multiple ETags. The current code:

const ifNoneMatch = request.headers.get('if-none-match');
if (ifNoneMatch === etag) {
    return new Response(null, { status: 304 });
}

This fails when:

  1. A client sends multiple ETags: If-None-Match: "abc123", "def456" — the strict equality check against a single ETag like "abc123" will fail because the full header string doesn't match.
  2. A client sends the wildcard * — this should match any ETag but won't with strict equality.

Impact: Cache validation will fail in these cases, causing the server to return a full 200 response with the body instead of a 304 Not Modified. This wastes bandwidth but doesn't cause incorrect behavior. In practice, most browsers send a single ETag for simple GET requests, so the impact is limited.

Suggested change
const ifNoneMatch = request.headers.get('if-none-match');
if (ifNoneMatch === etag) {
return new Response(null, { status: 304 });
}
const ifNoneMatch = request.headers.get('if-none-match');
if (ifNoneMatch === '*' || ifNoneMatch?.split(',').some((tag) => tag.trim() === etag)) {
return new Response(null, { status: 304 });
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@Rindrics Rindrics merged commit 9b2b2a2 into main Feb 12, 2026
6 checks passed
@Rindrics Rindrics deleted the issue-24-api branch February 12, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant