Skip to content

fix(media): store uploaded_at in milliseconds#892

Open
PlusA2M wants to merge 1 commit into
SonicJs-Org:mainfrom
PlusA2M:fix/media-uploaded-at-units
Open

fix(media): store uploaded_at in milliseconds#892
PlusA2M wants to merge 1 commit into
SonicJs-Org:mainfrom
PlusA2M:fix/media-uploaded-at-units

Conversation

@PlusA2M

@PlusA2M PlusA2M commented Jun 13, 2026

Copy link
Copy Markdown

Description

Media uploads stored uploaded_at in epoch seconds while the admin renderers
read it as epoch milliseconds, so new uploads displayed as 1/1970 and sorted
to the bottom. This standardizes the column on milliseconds (matching the content
tables) and migrates existing rows.

Fixes #889

Changes

  • Store uploaded_at as Date.now() (ms) at the three upload write sites
    (admin-media.ts POST /upload; api-media.ts POST /upload and
    POST /upload-multiple). The dead created_at literal in the same api-media
    object is bumped to ms too for consistency (it is not inserted).
  • Drop the compensating * 1000 in the two api-media.ts JSON readers (the data
    is now ms). The admin/template readers already assume ms, so they are unchanged.
  • Add migration 037_fix_media_uploaded_at_units.sql to normalize legacy
    seconds-valued rows: uploaded_at * 1000 WHERE uploaded_at < 1e11. The
    threshold (~1973 in ms) means already-ms rows are skipped and the migration is
    safe to re-run. Regenerated src/db/migrations-bundle.ts.
  • Add a regression test asserting uploads store a millisecond-magnitude value and
    that the API JSON returns a current-year ISO date.

⚠️ Note for the maintainer — migration approach

This PR takes the ms-standardization path (the clean long-term fix; matches
content.created_at/updated_at) with a data migration for existing rows.

The alternative is display-only: leave the column in seconds and multiply at
the ~5 admin read sites — no migration, but it keeps the unusual seconds
convention. I can switch to that approach if you'd prefer to avoid touching
existing data. (media.created_at/updated_at/deleted_at are also written in
seconds but are not currently displayed; I left those out of scope — let me know
if you'd like them normalized too.)

Testing

Unit Tests

  • Added/updated unit tests (media-uploaded-at-units.test.ts)
  • All unit tests passing (npm test — full suite green)

E2E Tests

  • Added/updated E2E tests
  • All E2E tests passing

Checklist

  • Code follows project conventions
  • Tests added/updated and passing
  • Type checking passes
  • No console errors or warnings
  • Documentation updated (if needed) — n/a

Media uploads stored `uploaded_at` as epoch seconds (Math.floor(Date.now()/1000))
while every admin renderer reads it as epoch milliseconds (new Date(uploaded_at)),
so new uploads displayed as 1/1970 and sorted below everything else. The rest of
core (content tables) uses Date.now() (ms).

Standardize on milliseconds: the three upload write sites now store Date.now(),
the two api-media JSON readers drop their compensating ×1000, and migration 037
normalizes existing seconds-valued rows (threshold 1e11 ≈ 1973, so already-ms
rows are skipped and the migration is safe to re-run).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: PlusA2M <plusa2m@gmail.com>
@PlusA2M PlusA2M requested a review from lane711 as a code owner June 13, 2026 16:30
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.

New media uploads display as 1/1970 and sort to the bottom (uploaded_at stored in seconds, read as ms)

1 participant