Skip to content

Journal backend: issue/volume lifecycle, masthead metadata, DOI/Scholar/Crossref compliance#26

Merged
duckyquang merged 1 commit into
mainfrom
feat/journal-issues-backend
Jul 3, 2026
Merged

Journal backend: issue/volume lifecycle, masthead metadata, DOI/Scholar/Crossref compliance#26
duckyquang merged 1 commit into
mainfrom
feat/journal-issues-backend

Conversation

@duckyquang

Copy link
Copy Markdown
Member

Unit 1 — backend-issues

Backend foundation for the standalone Synthica Journal site (synthica-org/journal reads this API), per ISSN and Crossref (DOI) requirements.

Issue/volume lifecycle (store.js)

  • db.journalIssues: first-class {volume, issue, status: 'open'|'closed', publishedAt, editorial} records (plus a year field driving the volume rule). Exactly one open issue at all times — Vol 1 Issue 1 is created lazily if none exist.
  • publishToJournal auto-assigns the open issue's volume/issue when the Director doesn't pass them (manual override still honored); the publish result includes {doi, volume, issue}. The default DOI is now minted via nextSynthicaDoi() (fixes the hardcoded 2026. suffix).
  • closeOpenIssue({editorial?, at?}) stamps publishedAt and opens the next issue: issue+1 within the same UTC calendar year, a new volume at Issue 1 once the year rolls over.
  • moveArticleToIssue({publicationId, volume, issue}) — target issue must exist.

Public endpoints (no auth, server.js)

  • GET /api/journal/meta{title, issn (JOURNAL_ISSN || 'pending'), publisher, frequency, journalUrl (JOURNAL_URL || GitHub Pages default), dashboardUrl (FRONTEND_URL || https://app.synthica.org), currentVolume, currentIssue}
  • GET /api/journal/issues → all issues newest first with articleCount + editorial
  • GET /api/journal/issues/:volume/:issue → issue record + full ToC (reuses issueContents)
  • GET /api/journal/publications/:id → now includes citation: {bibtex, ris, apa} (escaped)

Director-only

  • POST /api/editor/director/issues/close, POST /api/editor/director/issues/move
  • GET /api/editor/director/crossref.xml?volume=&issue= → Crossref 5.3.1 journal deposit XML (doi_batch → journal → journal_issue → journal_article: full_title, ISSN when set, contributors given/surname, publication_date, pages, CC BY in <ai:program>, doi_data with landing URL ${JOURNAL_URL}/articles/<slug>.html), served as an application/xml attachment. Validated well-formed via xml.dom.minidom.

Google Scholar (og.js)

Share pages now emit citation_title, one citation_author per author, citation_publication_date, citation_journal_title, citation_issn (when set), citation_volume/issue/firstpage/lastpage (pages 'A-B' parsed, en-dash tolerant), citation_pdf_url, citation_doi, plus a JSON-LD ScholarlyArticle block (< escaped so titles can't break out of the script tag).

Seed

Vol 1 Issues 1–2 closed in 2025 (one seeded paper each: Vol1/I1 and Vol1/I2) + open Vol 2 Issue 1 — ToCs are non-empty out of the box.

Tests

backend/test/issues.test.js (18 cases): open-issue auto-assignment, manual override, close → next issue, UTC year → volume rollover, move validation, meta/issues/detail shapes, Crossref XML (doi_batch + every article DOI + slug landing URLs + ISSN gating), bibtex/ris/apa content + escaping, Scholar tags + JSON-LD. All 25 suites / 201 tests pass.

E2E (verified on :4021)

/api/journal/meta, /api/journal/issues, /api/journal/issues/1/1, publication citation, share-page Scholar tags, director login → crossref.xml (headers + body), issues/move (incl. 404 on unknown target), issues/close (stamps date, opens Vol 2 Issue 2), and a second boot with JOURNAL_ISSN/JOURNAL_URL set to confirm ISSN/landing-URL propagation into meta, Scholar tags, and the Crossref deposit.

Notes

  • notify.js/email.js untouched (owned by PR added discord notifications and resend emails #25); store.js/server.js/seed.js edits are additive and localized.
  • The sheets provider doesn't persist journalIssues — the same pre-existing gap as preprints/proposals/etc. (it reloads from the seed baseline); the postgres provider persists it generically. Worth a follow-up owned by whoever owns providers/.

🤖 Generated with Claude Code

…f compliance

Issue/volume lifecycle:
- db.journalIssues as first-class records ({volume, issue, status, year,
  publishedAt, editorial}) with exactly one open issue at all times
  (Vol 1 Issue 1 created lazily when none exist)
- publishToJournal auto-assigns the open issue's volume/issue (manual
  override still honored); default DOI now minted via nextSynthicaDoi()
  instead of a hardcoded 2026 suffix
- closeOpenIssue() stamps publishedAt and opens the next issue (issue+1
  within the same UTC calendar year, new volume at Issue 1 after rollover)
- moveArticleToIssue() re-files a publication into an existing issue

Public API (no auth):
- GET /api/journal/meta — masthead (title, ISSN via JOURNAL_ISSN,
  publisher, frequency, journalUrl via JOURNAL_URL, dashboardUrl,
  current volume/issue)
- GET /api/journal/issues — all issues newest first with articleCount
- GET /api/journal/issues/:volume/:issue — issue record + full ToC
- GET /api/journal/publications/:id now includes citation
  {bibtex, ris, apa} (properly escaped)

Director-only:
- POST /api/editor/director/issues/close (optional editorial + at)
- POST /api/editor/director/issues/move {publicationId, volume, issue}
- GET /api/editor/director/crossref.xml?volume=&issue= — Crossref 5.3.1
  journal deposit XML (journal → journal_issue → journal_article with
  contributors given/surname, publication_date, pages, CC BY license in
  <ai:program>, doi_data with landing URL /articles/<slug>.html),
  served as an application/xml attachment

Scholar/SEO:
- share pages now emit Google Scholar citation_* tags (title, one author
  per tag, publication date, journal title, ISSN when set, volume/issue,
  first/last page, pdf_url, doi) plus a JSON-LD ScholarlyArticle block

Seed: Vol 1 Issues 1–2 closed in 2025 (one seeded paper each) + open
Vol 2 Issue 1, so ToCs are non-empty out of the box.

Tests: backend/test/issues.test.js (18 cases) — auto-assignment,
close/rollover, move validation, meta/issue shapes, Crossref XML,
citation formats, Scholar tags. All 25 suites / 201 tests pass.

Note: the sheets provider doesn't persist journalIssues (same pre-existing
gap as preprints/proposals); the postgres provider persists it generically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview, Comment Jul 2, 2026 7:26pm

@duckyquang
duckyquang merged commit 316ebfe into main Jul 3, 2026
4 checks passed
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