Skip to content

refactor(types): album/image snake_case fields → camelCase#466

Open
Zheaoli wants to merge 2 commits into
mainfrom
refactor/types-album-image-snake-camel
Open

refactor(types): album/image snake_case fields → camelCase#466
Zheaoli wants to merge 2 commits into
mainfrom
refactor/types-album-image-snake-camel

Conversation

@Zheaoli
Copy link
Copy Markdown
Collaborator

@Zheaoli Zheaoli commented May 19, 2026

Summary

PR-09 of the API refactor plan (docs/plans/2026-05-19-api-refactor-design.md). The largest PR of the series — renames four snake_case fields end-to-end across the API surface and frontend consumers:

  • album_valuealbumValue
  • image_nameimageName
  • image_sortingimageSorting
  • show_on_mainpageshowOnMainpage

The Prisma schema and DB columns stay snake_case; the rename happens at the server boundary. Frontends, the wire format, and types/index.ts now use camelCase exclusively for these four fields.

Server boundary

A new server/lib/model-transform.ts provides:

  • toAlbum(row) / toAlbumList(rows) — for Prisma findMany/findFirst results in server/db/query/albums.ts.
  • mapRawImageRow(row) / mapRawImageRows(rows) — for raw-SQL SELECT image.* results in server/db/query/{images,daily}.ts. Renames the four canonical fields without touching the rest of the payload.
  • toAlbumPrismaCreate / toAlbumPrismaUpdate — used by server/db/operate/albums.ts to consume AlbumType (camelCase) and emit Prisma's snake_case create/update input shape.
  • toImagePrismaCreate / toImagePrismaUpdate — same pattern for server/db/operate/images.ts.

SQL aliases were added in the following raw queries (PostgreSQL preserves case only inside double-quoted identifiers):

  • server/db/query/images.tsalbums.id AS \"albumValue\" in five queries, image.show_on_mainpage AS \"showOnMainpage\" in fetchMapImages, \"albums\".album_value AS \"albumValue\" in fetchImageByIdAndAuth, A.album_value AS \"albumValue\" in getRSSImages.
  • server/db/query/daily.tsalbums.album_value AS \"albumValue\" in fetchAlbumsWithDailyWeight.
  • server/tasks/service.tsimage.image_name AS \"imageName\" in fetchImagesBatchForScope.
  • server/db/query/dashboard.ts — already aliased before this PR; untouched.

server/db/query/albums.ts::fetchAlbumByRouter was also tightened to Promise<AlbumType | null> (it could return null but the type previously hid that). app/(theme)/[...album]/page.tsx was updated to accept the nullable.

Files (server)

  • types/index.ts
  • server/lib/model-transform.ts (new)
  • server/db/query/{albums,images,daily}.ts
  • server/db/operate/{albums,images}.ts
  • server/tasks/{service,metadata-refresh}.ts
  • hono/albums.ts
  • hono/open/download.ts

Files (frontend)

  • app/(theme)/[...album]/page.tsx
  • app/admin/settings/daily/page.tsx
  • app/rss.xml/route.ts
  • components/admin/album/{album-add-sheet,album-edit-sheet,album-list}.tsx
  • components/admin/list/{image-edit-sheet,image-view,list-props}.tsx
  • components/admin/tasks/tasks-page.tsx
  • components/admin/upload/{simple,livephoto,multiple}-file-upload.tsx
  • components/album/{preview-image,preview-image-exif}.tsx
  • components/layout/top-nav.tsx

Docs

  • CLAUDE.md — "Known Deviations" item 1 updated: the four PR-09 fields plus Exif.data_time (PR-08) and the Config settings shape (PR-07) are now marked done; remaining snake_case fields (e.g. preview_url, video_url, album_name, album_license, random_show, exposure_time, daily_weight) and the backup format are explicitly noted as out of scope.

Backup-format decision

The on-disk backup envelope (types/backup.ts, server/backup/format-adapter.ts, server/backup/prisma-repository.ts) keeps the existing snake_case field names.

Rationale:

  • It's a versioned serialization format (format: 'picimpact-backup', version: 1), not an API surface. Anyone consuming it does so explicitly via the import/export flow.
  • It mirrors Prisma's column-name shape, which is convenient for the repository's mapAlbumRecord / mapImageRecord mappers — they consume Prisma rows directly and emit backup records without any extra translation.
  • Most importantly, backups produced before this PR continue to import unchanged, with no version bump and no migration path. Switching to camelCase would have required either a V2 envelope or a migration shim in parseBackupEnvelope, both adding risk and surface area for a refactor PR.

The backup repository continues to read/write snake_case end-to-end. The API boundary (Hono handlers, server actions, frontends) sees camelCase. The two domains never overlap.

Test plan

  • pnpm run lint — 0 errors (warnings unchanged from main).
  • pnpm exec tsc --noEmit — 64 errors (2 fewer than the baseline-with-.next-types count of 66; the two removed errors were a pre-existing unsoundness in fetchAlbumByRouter and a generated model-transform constraint that was incidentally fixed).
  • pnpm run build — succeeds end-to-end; sitemap / RSS / route validators clean.
  • Log in to admin and view the gallery (default + simple + polaroid themes).
  • View an album page — confirms the album_value→albumValue alias in fetchClientImagesListByAlbum and the random-show shuffle works.
  • View /rss.xml — confirms item.albumValue reaches the feed item URL builder.
  • Upload an image (simple, livephoto, multiple flows) — confirms imageName in the POST body reaches Prisma as image_name.
  • Edit an album (name, route, license, image sorting, random show) — confirms toAlbumPrismaUpdate writes correctly and the relation table's album_value is updated when the route changes.
  • Edit an image, toggle "show on homepage" — confirms showOnMainpage round-trips.
  • Run a daily refresh — confirms fetchAlbumsWithDailyWeight returns camelCase albumValue to the settings page.
  • Export a backup and re-import it — confirms the backup format is unchanged and still accepted by parseBackupEnvelope.

🤖 Generated with Claude Code

Renames four canonical fields end-to-end across the API surface and
frontend consumers, completing item 1 of the API refactor plan's
snake_case data-model leak for the Album/Image types:

  - album_value      → albumValue
  - image_name       → imageName
  - image_sorting    → imageSorting
  - show_on_mainpage → showOnMainpage

The database schema is intentionally untouched. Prisma still maps the
underlying snake_case columns; the rename happens at the server
boundary so frontends consume camelCase without knowing the on-disk
naming.

Server boundary
---------------
- `server/lib/model-transform.ts` (new) exposes `toAlbum` / `toAlbumList`
  for Prisma `findMany`/`findFirst` results, `mapRawImageRow(s)` for
  raw-SQL `SELECT image.*` rows, and `toAlbumPrismaCreate` /
  `toAlbumPrismaUpdate` / `toImagePrismaCreate` / `toImagePrismaUpdate`
  for write paths.
- Raw SQL queries in `server/db/query/{images,daily}.ts` add `AS
  "albumValue"` / `AS "showOnMainpage"` aliases where columns are
  selected explicitly, and route `SELECT image.*` results through the
  raw-row mapper. `server/db/query/dashboard.ts` already aliased; left
  intact.
- `server/db/query/albums.ts` flips `fetchAlbumByRouter` to
  `Promise<AlbumType | null>` to reflect the actual return type
  (previously unsound).
- `server/db/operate/{albums,images}.ts` consume the camelCase input
  shape and delegate snake_case mapping to the new helpers.
- `server/tasks/service.ts` aliases `image.image_name AS "imageName"` to
  match the renamed `MetadataRefreshImage.imageName` field.

Hono handlers
-------------
- `hono/albums.ts` validates `album.albumValue.charAt(0)`.
- `hono/open/download.ts` reads `imageData.imageName` when deriving the
  download filename.

Backup format decision
----------------------
The on-disk backup envelope (`types/backup.ts`, `server/backup/*`) is a
versioned serialization format, not an API surface. It mirrors Prisma's
snake_case column names by design. Keeping the backup format snake_case
means existing backups produced before this PR import cleanly without a
version bump or migration path. The repository's `mapAlbumRecord` /
`mapImageRecord` helpers continue to read Prisma rows and emit
snake_case backup records — no boundary mapping needed. See item 1 of
the "Known Deviations" snapshot in `CLAUDE.md` for the remaining
snake_case fields that are out of scope for this PR.

Frontend
--------
Component-level property access is renamed throughout
`components/admin/album/*`, `components/admin/list/*`,
`components/admin/upload/*`, `components/admin/tasks/tasks-page.tsx`,
`components/album/*`, `components/layout/top-nav.tsx`, and the
`tasks-page.tsx` `AlbumOption = Pick<AlbumType, 'id' | 'name' |
'albumValue'>` alias. RSS feed (`app/rss.xml/route.ts`) and the daily
settings page (`app/admin/settings/daily/page.tsx`) follow suit.

Scope discipline
----------------
Other snake_case fields surfaced through the public types — `preview_url`,
`video_url`, `album_name`, `album_license`, `random_show`,
`exposure_time`, `f_number`, `daily_weight`, and the `Config` row shape
consumed by storage client builders — are intentionally not touched.
They are tracked separately and will be addressed in follow-up PRs.

See docs/plans/2026-05-19-api-refactor-design.md PR-09.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
picimpact Ready Ready Preview, Comment May 22, 2026 8:24am

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