Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Article Lifecycle Module — React + Node.js

Pulled this vertical slice out of the main SJP repo as a standalone code sample. This is the same article submission → review → publish pipeline as the Laravel slice, rebuilt with React, Node.js, and TypeScript. The rest of the system (editorial board, analytics, billing, etc.) lives in the main repo and isn't included here. Docs below are from the relevant wiki pages.


1. Overview

Handles the end-to-end article lifecycle:

Stage Actor What happens
Submission Author Creates article record, uploads manuscript (PDF/DOCX), associates with a journal
Review Reviewer Evaluates submission from queue, transitions status (approve / reject)
Publication Admin Publishes approved articles — registers DOI via CrossRef and indexes on Google Scholar

2. Tech Stack

Layer Technology Version
Runtime Node.js ≥ 18
API Express.js 4.x
Language TypeScript 5.7
ORM Prisma 6.x
Database PostgreSQL 14+
Auth JWT (access + refresh token rotation) jsonwebtoken
Validation Zod 3.x
File Uploads Multer 1.x
Frontend React 18.x
Bundler Vite 6.x
State Zustand 5.x
Routing React Router 6.x
Styling Tailwind CSS 3.x
Forms React Hook Form + Zod
Tests Jest + Supertest
Monorepo npm workspaces client/ + server/

3. Running This Module

# Clone & install
cd React-Node
npm install

# Database setup
cp .env.example .env        # edit DATABASE_URL + credentials
npx prisma migrate dev -w server
npx prisma db seed -w server

# Start dev servers (API :3001, client :5173)
npm run dev

Seeded accounts: admin@sjplatform.local / reviewer@sjplatform.local (password: password). New registrations default to AUTHOR role.

Tests: npm test -w server — runs with mocked Prisma, no PostgreSQL needed.

4. Module Structure

Backend (server/)

server/
├── prisma/
│   ├── schema.prisma                       # DB schema (users, articles, journals, reviews)
│   └── seed.ts                             # Admin + reviewer + journals
├── src/
│   ├── app.ts                              # Express app setup
│   ├── config/
│   │   ├── index.ts                        # Centralised config from env vars
│   │   └── database.ts                     # Prisma client singleton
│   ├── middleware/
│   │   ├── authenticate.ts                 # JWT verification → req.user
│   │   ├── authorize.ts                    # Role-based access (AUTHOR, REVIEWER, ADMIN)
│   │   ├── errorHandler.ts                 # Global error handler with Prisma error mapping
│   │   ├── rateLimiter.ts                  # express-rate-limit per IP
│   │   └── validate.ts                     # Zod schema validation middleware
│   ├── modules/
│   │   ├── articles/
│   │   │   ├── article.controller.ts       # Author CRUD
│   │   │   ├── article.service.ts          # Business logic + DB transactions
│   │   │   ├── article.resource.ts         # Response transformer (BigInt, dates, relations)
│   │   │   ├── article.schema.ts           # Zod validation schemas
│   │   │   └── article.routes.ts           # Router with multer upload middleware
│   │   ├── reviews/
│   │   │   ├── review.controller.ts        # Reviewer queue + status transitions
│   │   │   ├── review.service.ts           # Approve / reject with email notifications
│   │   │   ├── review.schema.ts            # Status update validation
│   │   │   └── review.routes.ts            # REVIEWER + ADMIN only
│   │   ├── publish/
│   │   │   ├── publish.controller.ts       # Admin publish action
│   │   │   ├── publish.service.ts          # DOI registration + Scholar indexing
│   │   │   └── publish.routes.ts           # ADMIN only
│   │   ├── auth/
│   │   │   ├── auth.controller.ts          # Register, login, logout, token refresh
│   │   │   ├── auth.service.ts             # bcrypt hashing, JWT signing, token rotation
│   │   │   ├── auth.schema.ts              # Zod schemas for auth inputs
│   │   │   └── auth.routes.ts
│   │   ├── users/
│   │   │   ├── user.controller.ts          # Profile CRUD + password change
│   │   │   ├── user.schema.ts
│   │   │   └── user.routes.ts
│   │   └── journals/
│   │       ├── journal.controller.ts       # Active journal listing
│   │       ├── journal.resource.ts
│   │       └── journal.routes.ts
│   ├── services/
│   │   ├── crossref.service.ts             # DOI deposit with retry
│   │   ├── scholar.service.ts              # Scholar indexing with retry
│   │   ├── email.service.ts                # Nodemailer transactional emails
│   │   └── storage.service.ts              # Multer config, file MIME validation
│   ├── utils/
│   │   ├── ApiError.ts                     # Typed HTTP error factory (400-422)
│   │   ├── jwt.ts                          # Sign/verify access + refresh tokens
│   │   ├── logger.ts                       # Winston structured logging
│   │   └── reference.ts                    # SJP-XX-XXXXX reference generator
│   └── types/index.ts                      # Shared TypeScript interfaces
└── tests/
    ├── setup.ts                            # Env vars for test environment
    ├── mocks/
    │   ├── prisma.ts                       # Full PrismaClient mock (jest.fn)
    │   └── storage.ts                      # Multer/fs mock for uploads
    ├── helpers/
    │   └── testUtils.ts                    # Factory functions + actingAs (JWT helper)
    ├── unit/
    │   ├── apiError.test.ts
    │   ├── reference.test.ts
    │   └── articleResource.test.ts
    └── feature/
        ├── auth.test.ts
        ├── articles.test.ts
        ├── review.test.ts
        ├── publish.test.ts
        └── profile.test.ts

Frontend (client/)

client/
├── src/
│   ├── App.tsx                             # Route definitions with guards
│   ├── main.tsx                            # React entry point
│   ├── api/
│   │   ├── client.ts                       # Axios instance with JWT interceptor
│   │   ├── auth.api.ts
│   │   ├── articles.api.ts
│   │   ├── reviews.api.ts
│   │   ├── publish.api.ts
│   │   └── users.api.ts
│   ├── components/
│   │   ├── guards/                         # ProtectedRoute, GuestRoute, RoleGuard
│   │   ├── layout/                         # AppLayout, Header, Sidebar
│   │   └── ui/                             # Button, Card, Input, Modal, Pagination, etc.
│   ├── pages/
│   │   ├── auth/                           # Login, Register, ForgotPassword, ResetPassword
│   │   ├── articles/                       # List, Create, Edit, Detail
│   │   ├── reviews/                        # Dashboard, ReviewArticle
│   │   ├── publish/                        # PublishPage (admin)
│   │   └── profile/                        # ProfilePage
│   ├── stores/
│   │   └── authStore.ts                    # Zustand store with token persistence
│   ├── lib/
│   │   ├── utils.ts                        # cn(), formatDate(), statusColor()
│   │   └── validation.ts                   # Shared Zod schemas
│   └── types/index.ts                      # Shared TypeScript interfaces
├── tailwind.config.js
├── vite.config.ts
└── tsconfig.json

5. Module Configuration

Settings in server/src/config/index.ts, driven by environment variables:

Key Default Description
journal.maxFileSizeKb 10240 Manuscript upload limit (KB)
journal.allowedExtensions ['pdf','doc','docx'] Permitted file types
journal.allowedMimeTypes [...] MIME type validation
journal.referenceFormat 'SJP-%02d-%05d' Article reference ID pattern (journal_id, article_id)
journal.statuses ['submitted','under_review','approved','rejected','published'] Lifecycle states
journal.reviewableStatuses ['submitted','under_review'] Which statuses appear in reviewer queue
jwt.accessExpiry 15m Access token lifetime
jwt.refreshExpiry 7d Refresh token lifetime (rotated on use)

External service credentials (CROSSREF_*, SCHOLAR_*, SMTP_*) are set via .env — see .env.example.

6. API Endpoints

JWT-authenticated, rate-limited.

Auth

Method URI Description
POST /api/auth/register Create account (defaults to AUTHOR role)
POST /api/auth/login Returns access + refresh tokens
POST /api/auth/logout Invalidates refresh token
POST /api/auth/refresh Rotates refresh token, returns new access token

Articles (authenticated)

Method URI Description
GET /api/articles Paginated list, filterable with ?status=
POST /api/articles Create with manuscript upload (multipart/form-data)
GET /api/articles/:id Detail with journal, user, reviews
PUT /api/articles/:id Update metadata + file (only when SUBMITTED)
DELETE /api/articles/:id Delete (not if APPROVED/PUBLISHED)

Reviews (REVIEWER / ADMIN)

Method URI Description
GET /api/reviews Paginated review queue
GET /api/reviews/:id Article detail for review
PATCH /api/reviews/:id/status Approve or reject with notes

Publish (ADMIN only)

Method URI Description
GET /api/publish Approved articles ready to publish
POST /api/publish/:id Register DOI + index on Scholar

Users (authenticated)

Method URI Description
GET /api/users/me Current user profile
PATCH /api/users/me Update name / email
PUT /api/users/me/password Change password
DELETE /api/users/me Delete account

Journals

Method URI Description
GET /api/journals Active journals list

7. Tests

Test Suite Tests Covers
apiError.test.ts 7 All HTTP error factory methods (400–415)
reference.test.ts 4 Reference ID formatting, zero-padding
articleResource.test.ts 9 Response transformer, BigInt, dates, relations
auth.test.ts 7 Register, login, logout, validation, password mismatch
articles.test.ts 16 CRUD, auth guards, ownership, status restrictions, admin override
review.test.ts 11 Role authorization, queue, approve/reject, invalid status
publish.test.ts 9 Admin-only access, publish flow, DOI warnings, 404
profile.test.ts 9 View/update profile, email conflict, password change, account deletion

72 total tests. All run with mocked Prisma (no database required). Run with:

npm test -w server            # all tests
npx jest --verbose -w server  # verbose output
npx jest --coverage -w server # with coverage report

8. Auth Flow

┌──────────┐   POST /auth/login    ┌──────────┐
│  Client  │ ──────────────────▸   │  Server  │
│          │ ◂──────────────────   │          │
│          │   { accessToken,      │          │
│          │     refreshToken }    │          │
│          │                       │          │
│  Axios   │   Authorization:      │  JWT     │
│  inter-  │   Bearer <access>     │  verify  │
│  ceptor  │ ──────────────────▸   │          │
│          │                       │          │
│  401?    │   POST /auth/refresh  │  Rotate  │
│  auto    │ ──────────────────▸   │  refresh │
│  retry   │ ◂──────────────────   │  token   │
└──────────┘                       └──────────┘
  • Access tokens expire in 15 minutes
  • Refresh tokens expire in 7 days, single-use (rotated on each refresh)
  • Axios interceptor automatically refreshes on 401 and retries the original request

9. Notes

  • Publish handles external API failures gracefully — CrossRef and Scholar fail independently, so partial success is possible (warnings returned in response)
  • File upload validates both extension and MIME type server-side
  • BigInt fileSize from PostgreSQL is automatically serialised to number in API responses
  • Frontend uses Zustand with persist middleware for token storage across page reloads
  • Monorepo managed via npm workspaces — single npm install at root handles both packages
  • See CONTRIBUTING.md for branching/commit conventions

About

Scientific Journal Platform - React + Node.js vertical slice

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages