Journal backend: issue/volume lifecycle, masthead metadata, DOI/Scholar/Crossref compliance#26
Merged
Merged
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 ayearfield driving the volume rule). Exactly one open issue at all times — Vol 1 Issue 1 is created lazily if none exist.publishToJournalauto-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 vianextSynthicaDoi()(fixes the hardcoded2026.suffix).closeOpenIssue({editorial?, at?})stampspublishedAtand 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 witharticleCount+editorialGET /api/journal/issues/:volume/:issue→ issue record + full ToC (reusesissueContents)GET /api/journal/publications/:id→ now includescitation: {bibtex, ris, apa}(escaped)Director-only
POST /api/editor/director/issues/close,POST /api/editor/director/issues/moveGET /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 anapplication/xmlattachment. Validated well-formed via xml.dom.minidom.Google Scholar (
og.js)Share pages now emit
citation_title, onecitation_authorper 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-LDScholarlyArticleblock (<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, publicationcitation, 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 withJOURNAL_ISSN/JOURNAL_URLset to confirm ISSN/landing-URL propagation into meta, Scholar tags, and the Crossref deposit.Notes
notify.js/email.jsuntouched (owned by PR added discord notifications and resend emails #25);store.js/server.js/seed.jsedits are additive and localized.journalIssues— the same pre-existing gap aspreprints/proposals/etc. (it reloads from the seed baseline); the postgres provider persists it generically. Worth a follow-up owned by whoever ownsproviders/.🤖 Generated with Claude Code