Skip to content

feat: Publish Porfolio API - #65

Open
nandenjin wants to merge 17 commits into
masterfrom
server-app
Open

feat: Publish Porfolio API#65
nandenjin wants to merge 17 commits into
masterfrom
server-app

Conversation

@nandenjin

Copy link
Copy Markdown
Owner

No description provided.

@nandenjin nandenjin self-assigned this Mar 22, 2026
@nandenjin nandenjin added the enhancement New feature or request label Mar 22, 2026
Copilot AI review requested due to automatic review settings March 22, 2026 07:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR publishes a portfolio content API by embedding JSON-LD into existing markdown content, generating a SQLite DB + static asset bundle at build time, and serving that data via a new Node/Hono server (with Docker + CI wiring).

Changes:

  • Replace markdown frontmatter with per-page JSON-LD <script type="application/ld+json">…</script> blocks across works/news/events/profile.
  • Add .server workspace: build pipeline to parse markdown + extract JSON-LD, generate portfolio.db, copy assets, and serve an API (Hono + better-sqlite3).
  • Add Dockerfile, GitHub Actions workflow, and devcontainer updates to build/typecheck the server.

Reviewed changes

Copilot reviewed 131 out of 133 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
works/ut-collection-perspective/index.md Remove frontmatter; add JSON-LD metadata script
works/ushio-makabe/index.md Remove frontmatter; add JSON-LD metadata script
works/ushio-kameyama/index.md Remove frontmatter; add JSON-LD metadata script
works/twinkle/index.md Remove frontmatter; add JSON-LD metadata script
works/tsukulive19/index.md Remove frontmatter; add JSON-LD metadata script
works/tkbneu18/index.md Remove frontmatter; add JSON-LD metadata script
works/the-lost-thing/index.md Remove frontmatter; add JSON-LD metadata script
works/taurus/index.md Remove frontmatter; add JSON-LD metadata script
works/sympathy/index.md Remove frontmatter; add JSON-LD metadata script
works/suzuna/index.md Remove frontmatter; add JSON-LD metadata script
works/study/index.md Remove frontmatter; add JSON-LD metadata script
works/ssss/index.md Remove frontmatter; add JSON-LD metadata script
works/sohosai17/index.md Remove frontmatter; add JSON-LD metadata script
works/skip24/index.md Remove frontmatter; add JSON-LD metadata script
works/sgym-card/index.md Remove frontmatter; add JSON-LD metadata script
works/ri/index.md Remove frontmatter; add JSON-LD metadata script
works/residents/index.md Remove frontmatter; add JSON-LD metadata script
works/neuws18/index.md Remove frontmatter; add JSON-LD metadata script
works/neuws17/index.md Remove frontmatter; add JSON-LD metadata script
works/mastival/index.md Remove frontmatter; add JSON-LD metadata script
works/layers/index.md Remove frontmatter; add JSON-LD metadata script
works/laika/index.md Remove frontmatter; add JSON-LD metadata script
works/kasuga_ac/index.md Remove frontmatter; add JSON-LD metadata script
works/kamine-proto/index.md Remove frontmatter; add JSON-LD metadata script
works/kamine-baby/index.md Remove frontmatter; add JSON-LD metadata script
works/himap/index.md Remove frontmatter; add JSON-LD metadata script
works/hannan-guitar/index.md Remove frontmatter; add JSON-LD metadata script
works/ham19web/index.md Remove frontmatter; add JSON-LD metadata script
works/fyb/index.md Remove frontmatter; add JSON-LD metadata script
works/encounter/index.md Remove frontmatter; add JSON-LD metadata script
works/design-pattern/index.md Remove frontmatter; add JSON-LD metadata script
works/carillon/index.md Remove frontmatter; add JSON-LD metadata script
works/caf-kanno/index.md Remove frontmatter; add JSON-LD metadata script
works/battle-collection/index.md Remove frontmatter; add JSON-LD metadata script
works/ariabella22/index.md Remove frontmatter; add JSON-LD metadata script
profile/index.md Add JSON-LD Person schema script
package.json Declare .server as an npm workspace
news/251203_information-design/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/250704_tmaf25/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/250426_kamine/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/241007_mast-ad-lt-2024/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/230822_ushio-rpng-catalog/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/220912_gfest-guide-video/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/211128_caf21_award/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/211101_caf21/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/211028_at21/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/210728_tmaf21/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/210325_graduation/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/210324_tsukulive/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/210324_ham20/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/201207_isca/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/201108_tsukulive-note/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/200527_fyb/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/200228_kdcc/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/200124_making-notes/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/191101_tsukulive/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/190501_the-lost-thing/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/190501_ham19/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/190201_mastival/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/190121_sobo-award/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/181229_sobo-magazine/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/181007_the-lost-thing-on-cup/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/180901_t-tasu/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
news/180701_the-lost-thing/index.md Remove frontmatter; add JSON-LD NewsArticle schema script
events/tsukuba24/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/tmaf25/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/tmaf21/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/t-tasu18/index.md Replace content with JSON-LD ExhibitionEvent schema script
events/residents21/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/pond23/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/perspective/index.md Remove frontmatter; add JSON-LD Event schema script
events/neo24/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/mastival/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/makabe/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/kmym21/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/kamine-neo25/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/kamine-expoc25/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/ham20/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/ham19/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/caf21/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/caf-ushio/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
events/asterism/index.md Remove frontmatter; add JSON-LD ExhibitionEvent schema script
Dockerfile Add container build for the API server
.server/tsconfig.json Add TypeScript config for the server workspace
.server/src/types/content.ts Add content + JSON-LD TypeScript types
.server/src/types/api.ts Add API helper types (pagination, ProblemDetails)
.server/src/shared/response.ts Add list pagination header helper
.server/src/shared/error.ts Add RFC7807-style error responses
.server/src/shared/db-client.ts Add SQLite client helper
.server/src/models/works/schema.ts Define works table schema
.server/src/models/works/router.ts Add works API routes
.server/src/models/works/queries.ts Add works DB queries
.server/src/models/works/index.ts Export works model wiring
.server/src/models/profile/schema.ts Define profile table schema
.server/src/models/profile/router.ts Add profile router (model-level)
.server/src/models/profile/queries.ts Add profile DB query
.server/src/models/profile/index.ts Export profile model wiring
.server/src/models/news/schema.ts Define news table schema
.server/src/models/news/router.ts Add news API routes
.server/src/models/news/queries.ts Add news DB queries
.server/src/models/news/index.ts Export news model wiring
.server/src/models/events/schema.ts Define events table schema
.server/src/models/events/router.ts Add events API routes
.server/src/models/events/queries.ts Add events DB queries
.server/src/models/events/index.ts Export events model wiring
.server/src/index.ts Add Hono server entrypoint, routes, static, error handling
.server/package.json Add server dependencies and build scripts
.server/build/normalize-paths.ts Add HTML image path normalization helpers
.server/build/models/works/parser.ts Add works markdown + JSON-LD parser
.server/build/models/profile/parser.ts Add profile markdown + JSON-LD parser
.server/build/models/news/parser.ts Add news markdown + JSON-LD parser
.server/build/models/events/parser.ts Add events markdown + JSON-LD parser
.server/build/jsonld.ts Add JSON-LD extraction + validation utilities
.server/build/index.ts Add build pipeline entrypoint (parse → normalize → db → assets)
.server/build/db-populate.ts Add SQLite schema creation + insertion
.server/build/copy-assets.ts Add asset copy step into dist/static
.server/.gitignore Ignore server build artifacts
.nvmrc Pin Node major version
.gitignore Ignore .claude dir
.github/workflows/server.yaml Add CI workflow for server typecheck/build
.dockerignore Add Docker build context filtering
.devcontainer/devcontainer.json Enable Docker-in-Docker feature; add GH Actions extension

Comment thread .server/src/models/profile/queries.ts Outdated
Comment thread .dockerignore
Comment thread .server/src/index.ts
Comment thread .server/src/models/works/router.ts
Comment thread .server/src/models/news/router.ts Outdated
Comment thread .github/workflows/server.yaml
Comment on lines +25 to +38
const insertWork = db.prepare(`
INSERT INTO works (id, jsonld, body_html)
VALUES (?, ?, ?)
`)

const insertEvent = db.prepare(`
INSERT INTO events (id, jsonld, body_html)
VALUES (?, ?, ?)
`)

const insertNews = db.prepare(`
INSERT INTO news (id, jsonld, body_html)
VALUES (?, ?, ?)
`)

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

The tables define sortable columns (works.release, events.session_start, news.release, etc.) and the query code orders by them, but populateDatabase() only inserts (id, jsonld, body_html). This leaves those columns NULL for every row, breaking ordering and making the indexes ineffective. Either populate the columns from JSON-LD during build, or update the list queries to sort based on JSON fields (e.g., json_extract(jsonld, ...)).

Copilot uses AI. Check for mistakes.
Comment thread .server/src/models/events/router.ts Outdated
Comment thread .server/build/normalize-paths.ts Outdated
Comment thread .server/build/copy-assets.ts Outdated
nandenjin and others added 9 commits March 22, 2026 16:56
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 133 out of 135 changed files in this pull request and generated 4 comments.

Comment on lines +11 to +32
router.get("events", (c) => {
const { limit, offset } = parsePagination((name) => c.req.query(name))

const result = getEvents({ limit, offset })
const lang = c.req.query("lang")
const origin = new URL(c.req.url).origin

setListHeaders(c, result.total, limit, offset)
return c.json(
result.data.map((item) =>
toApiJsonLd(item.jsonld, {
lang,
origin,
includeBodyHtml: false,
}),
),
)
})

// GET /events/:id
router.get("events/:id", (c) => {
const id = c.req.param("id")

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

These routes are registered as "events" / "events/:id" (no leading /). This can prevent the endpoints from matching requests when mounted. Use /events and /events/:id to ensure correct routing.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +32
router.get("news", (c) => {
const { limit, offset } = parsePagination((name) => c.req.query(name))

const result = getNews({ limit, offset })
const lang = c.req.query("lang")
const origin = new URL(c.req.url).origin

setListHeaders(c, result.total, limit, offset)
return c.json(
result.data.map((item) =>
toApiJsonLd(item.jsonld, {
lang,
origin,
includeBodyHtml: false,
}),
),
)
})

// GET /news/:id
router.get("news/:id", (c) => {
const id = c.req.param("id")

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

These routes are registered as "news" / "news/:id" (no leading /). This can cause route matching issues when mounted. Use /news and /news/:id so the endpoints are reachable.

Copilot uses AI. Check for mistakes.
.replace(
/<img([^>]*)\ssrc="(?!https?:\/\/)(?!\/)([^"]+)"/g,
(match, attrs, src) => {
return `<img${attrs} src="/${contentType}/${id}/${src}"`

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

normalizeImagePathsInHtml prepends /${contentType}/${id}/ to any relative <img src> value. For content like works/encounter/index.md where images are referenced as encounter/encounter_2.jpg but stored directly under works/encounter/encounter_2.jpg, this produces duplicated paths (e.g. /static/works/encounter/encounter/encounter_2.jpg) and breaks image URLs in body_html. Consider resolving relative paths against the content-type root (e.g. /${contentType}/${src}) or normalizing away a leading ${id}/ when present, then letting the later rewrite add /static.

Suggested change
return `<img${attrs} src="/${contentType}/${id}/${src}"`
let normalizedSrc = src as string
const idPrefix = `${id}/`
if (normalizedSrc.startsWith(idPrefix)) {
normalizedSrc = normalizedSrc.slice(idPrefix.length)
}
return `<img${attrs} src="/${contentType}/${id}/${normalizedSrc}"`

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +32
router.get("works", (c) => {
const { limit, offset } = parsePagination((name) => c.req.query(name))

const result = getWorks({ limit, offset })
const lang = c.req.query("lang")
const origin = new URL(c.req.url).origin

setListHeaders(c, result.total, limit, offset)
return c.json(
result.data.map((item) =>
toApiJsonLd(item.jsonld, {
lang,
origin,
includeBodyHtml: false,
}),
),
)
})

// GET /works/:id
router.get("works/:id", (c) => {
const id = c.req.param("id")

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

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

These routes are registered as "works" / "works/:id" (no leading /). In Hono, route paths are typically absolute (e.g. /works). As written, this can result in the endpoints not matching requests (especially when mounted via app.route("/", worksRouter)). Use /works and /works/:id to ensure the routes are reachable.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants