Skip to content

Modernization, Storage Migration & UI/UX Improvements - #14

Open
MiskaWasTaken wants to merge 4 commits into
micromaomao:masterfrom
MiskaWasTaken:master
Open

Modernization, Storage Migration & UI/UX Improvements#14
MiskaWasTaken wants to merge 4 commits into
micromaomao:masterfrom
MiskaWasTaken:master

Conversation

@MiskaWasTaken

Copy link
Copy Markdown

The following message is written by AI and and was reviewed by me to ensure accuracy.
There was just so much that has been done, I could not accurately remember.
Rest assured everything has been reviewed.

See the EOF to learn how to migrate

image image

Overview

This PR contains the major modernization and maintenance work completed across SchSrch, including dependency upgrades, UI/UX improvements, performance optimizations, database/storage changes, search/indexing improvements, and the migration tooling required to move PDF storage from MongoDB to the filesystem.

The most significant backend change is the migration of PDF binaries from the legacy pastpaperpaperblobs MongoDB collection to filesystem-backed storage using pastpaperdocs.relativePath and SHA-256 integrity verification.

The changes have been tested against a restored production database dump and a production-like application environment, including migration, verification, indexing, search, PDF serving, and legacy blob cleanup.

What's Included

Dependency & Infrastructure Updates

  • Updated project dependencies to newer compatible versions.
  • Updated/modernized supporting packages where required by the current runtime.
  • Improved compatibility with the current Node.js/MongoDB environment.
  • Updated migration tooling and supporting storage code.
  • Improved handling of MongoDB BSON Binary objects when reconstructing legacy PDF blobs.

UI/UX Improvements

Various frontend improvements have been made throughout schsrch to improve usability and presentation.
Improvements include:

  • General UI cleanup and modernization.
  • Improved page layouts and spacing.
  • Improved navigation and interaction flows.
  • Better presentation of past-paper information.
  • Improved PDF viewing/opening experience.
  • Improved search/result presentation.
  • Improved responsive behavior.
  • General visual consistency improvements across the application.

These changes are intended to make the existing functionality easier to use without changing the core purpose of schsrch
Split-view paper/search experience
image

Improved PDF workflow

pastpaperdocs.relativePath
        ↓
storage abstraction
        ↓
filesystem PDF
        ↓
PDF viewer

Responsive layout
The UI was adjusted to better accommodate different viewport sizes, particularly around the split-view/search/PDF workflow.

Dark UI/Search result presentation
The application UI has been modernized around the existing dark presentation, including the search and document-viewing workflow.
image

Search improvements

The search system has been improved at both the Elasticsearch and application layers.

Elasticsearch document structure
The Elasticsearch mapping currently contains:

content   : text
docId     : keyword
page      : integer
paper     : keyword
subject   : keyword

This keeps Elasticsearch focused on searchable/indexable data instead of storing the actual PDF binaries.
A typical indexed document is:

{
  "docId": "...",
  "subject": "9701",
  "paper": "2",
  "content": "...extracted PDF text...",
  "page": 2
}

Phrase/relevance handling
Search ranking has been improved to better distinguish meaningful phrase matches from documents that merely contain the individual words somewhere in their text.

Metadata-aware search
Subject and paper information is available to Elasticsearch as keyword fields, allowing search and filtering to operate on structured metadata as well as full-text content.

Elasticsearch is independent of PDF storage
A critical architectural property of the migration is:

PDF binary
   │
   └── filesystem

Extracted text
   │
   ├── pastpaperindexes
   │
   └── Elasticsearch

Once text has been extracted, Elasticsearch does not need to reopen the PDF to perform searches.

Therefore, moving PDF binaries from MongoDB to the filesystem does not require Elasticsearch to be rebuilt merely because the storage backend changed.

pastpaperindexes and Elasticsearch architecture

The system now clearly separates the roles of the two indexing layers.

pastpaperindexes

This is the MongoDB persistence layer for extracted PDF text.

Each entry contains information such as:

docId
page
content

It acts as the durable source from which Elasticsearch can be rebuilt.

Elasticsearch

Elasticsearch provides the actual full-text search layer.

The relationship is:

PDF
 ↓
PDF text extraction
 ↓
pastpaperindexes
 ↓
Elasticsearch

Elasticsearch rebuild
reIndexElasticSearch.bin.js is the tool intended for rebuilding Elasticsearch.
It can rebuild Elasticsearch from the existing pastpaperindexes collection without needing to re-read the physical PDFs.
This is fundamentally different from doIndex.bin.js.

doIndex.bin.js clarified

doIndex.bin.js is an ingestion/importer, not an Elasticsearch rebuild tool.

Its workflow is approximately:

External PDF
    ↓
PDF parsing
    ↓
Text extraction
    ↓
Recognizer/layout generation
    ↓
pastpaperdocs
    ↓
pastpaperindexes
    ↓
Elasticsearch
    ↓
Filesystem storage

It accepts filesystem PDFs as input.

It does not depend on pastpaperpaperblobs.

It can therefore be used after the filesystem storage migration for future paper ingestion.

It also provides a way to reprocess PDFs when extracted/indexed data needs to be regenerated.

Backend performance improvements

Backend performance improvements
Backend data access was optimized to reduce repeated database queries inside iteration paths.

The problematic pattern is effectively:

for (const paper of papers) {
    await database.find(...);
}

which turns one logical request into potentially dozens or hundreds of database operations.
The relevant code paths were changed to reduce unnecessary repeated lookups and reuse already available data where possible.
This is particularly relevant for paper/search pages where many documents can be displayed at once.

MongoDB count API modernization
Deprecated MongoDB counting patterns were replaced with explicit document-count APIs such as:

countDocuments()

instead of relying on the older:

count()

This removes deprecated API usage and makes the intended counting behavior explicit.

Database access modernization
MongoDB/Mongoose configuration and access patterns were updated as part of the dependency/runtime modernization.

Static asset performance

Static resource delivery was optimized to reduce repeat downloads.

Long-lived caching

Static resources can be cached for longer periods where appropriate, reducing network traffic on subsequent visits.

PDF HTTP caching

Filesystem-backed PDF delivery can use HTTP cache validation mechanisms including:

ETag
Last-Modified

This allows clients to avoid retransmitting unchanged PDFs.

Font loading

Important fonts are preloaded to reduce the delay before the UI reaches its intended typography.

Deferred JavaScript

Non-critical JavaScript resources are loaded without unnecessarily blocking initial page rendering.

PDF storage migration

Previous architecture

The old architecture stored PDF binary fragments in:
pastpaperpaperblobs

The restored production database contained:

pastpaperdocs:       29,217
pastpaperpaperblobs: 32,638

The blob collection occupied approximately:

21,923,213,312 bytes
≈ 20.4 GiB

This is a significant amount of database storage dedicated to PDF binaries.

New filesystem storage architecture
The new architecture stores physical PDF files under STORAGE_ROOT.

MongoDB retains the document metadata and filesystem reference:

{
    "relativePath": "new_imports/9701/s05/9701_s05_5_0_ci.pdf",
    "fileHash": "..."
}

The binary itself is no longer required to live inside MongoDB.

Migration bug fixed: metadata was not unique

The original migration implementation used:

subject
time
type
paper
variant

as a filesystem signature.

That assumption is invalid.

Production data contained multiple MongoDB documents with the same metadata but different binary contents.

For example:

0620_s06_5_0_ci

had two distinct MongoDB documents.
One had:
9937c3ca808d5a84ad66098d4f17ff03a1edd05291b384a4481a8d5f74f3a36b
while the other had:
4acc63a0fbb74e2eb8aaa4cdc59ec68fa308651a5c368fa101ec8124f8adb549

Both cannot safely occupy the same physical file.

Collision-safe path generation

The new migration no longer treats metadata signatures as globally unique filesystem identifiers.

Path ownership is tracked using the document identity and calculated content hash.

If the canonical path is already occupied by a different document/content hash, a deterministic collision path is generated:

<filename>__<MongoDB document ID>.pdf

For example:

new_imports/0620/s06/0620_s06_5_0_ci.pdf

and:

new_imports/0620/s06/0620_s06_5_0_ci__588bfd02bf262205b65ccc5f.pdf

This ensures that both documents survive migration without overwriting each other.

The migration identified 8 path collisions in the production dataset.

All 8 were resolved without data loss.

SHA-256 integrity verification

The migration does not simply write the reconstructed PDF and assume everything worked.

For every migrated document:

MongoDB blob fragments
        ↓
reconstruct PDF buffer
        ↓
calculate SHA-256
        ↓
write PDF
        ↓
read PDF back
        ↓
calculate SHA-256 again
        ↓
compare
        ↓
update pastpaperdocs

The database is only updated with the new filesystem path/hash after the filesystem copy has been verified.

The invariant is:

expected blob SHA-256
        ==
actual filesystem SHA-256

Independent migration verification

migrate-storage.js --verify performs an independent verification pass.

It checks:

Database → filesystem

For every pastpaperdocs document:

relativePath exists
referenced file exists
file is readable
SHA-256 matches fileHash
Filesystem → database

The filesystem is also scanned so that unreferenced PDFs can be identified as orphans.

Collision verification

The verification process checks that multiple database documents are not incorrectly assigned to the same physical file.

This is deliberately separate from the migration's own bookkeeping so that the migration isn't simply grading its own homework.

Resume removed

The old --resume approach based on the original metadata-signature assumptions was intentionally not relied upon for the production migration.

The production migration was performed as a clean migration against the restored database.

The recommended production procedure is therefore:

backup
 ↓
dry run
 ↓
clean migration
 ↓
verification
 ↓
application smoke test
 ↓
cleanup

rather than depending on an ambiguous partially migrated state.

Dry-run validation

The migration can be first tested using:

MONGODB=<db> \
STORAGE_ROOT=<storage> \
node migrate-storage.js --dry-run

Legacy blob cleanup

The migration intentionally does not delete pastpaperpaperblobs automatically.

This is deliberate.

Migration and deletion are separate operations:

Migration
   ↓
Verification
   ↓
Application smoke test
   ↓
Explicit cleanup

The cleanup operation is:

node migrate-storage.js --cleanup-blobs --confirm-cleanup

The cleanup path:

Runs migration verification.
Refuses to delete blobs if blocking integrity failures exist.
Reports the number of legacy blob documents.
Reports estimated storage reclamation.
Requires --confirm-cleanup.
Drops pastpaperpaperblobs.
Verifies that the collection no longer exists.
Writes cleanup-report.json.

It leaves:

pastpaperdocs
pastpaperindexes
pastpaperfeedbacks
filesystem PDFs
Elasticsearch

untouched.

A --force mode was added for cases where an operator has explicitly reviewed and accepted known migration exceptions.
--force cannot be used by itself.
This combination is required:

--cleanup-blobs
--confirm-cleanup
--force

Without --confirm-cleanup, the operation aborts.

MIGRATION STEPS FOR PRODUCTION

  1. Backup
    mongodump --uri="<PRODUCTION_MONGODB_URI>"

Also ensure the filesystem destination is backed up or otherwise recoverable.

  1. Dry Run
MONGODB="<PRODUCTION_MONGODB_URI>" \
STORAGE_ROOT="/path/to/papers" \
node migrate-storage.js --dry-run

Do not continue if unexpected errors are reported. (~8 collisons may be experienced, this is okay continue)

  1. Migration
MONGODB="<PRODUCTION_MONGODB_URI>" \
STORAGE_ROOT="/path/to/papers" \
node migrate-storage.js

This does not delete pastpaperpaperblobs. It simply rebuilds the pdf to storage

  1. Verification
MONGODB="<PRODUCTION_MONGODB_URI>" \
STORAGE_ROOT="/path/to/papers" \
node migrate-storage.js --verify

Expected:

0 documents missing relativePath
0 missing/unreadable files
0 invalid hashes
0 unsafe path collisions

(or the 8 files)
Orphaned files should be reviewed separately.

  1. Elasticsearch & indexing migration
    As part of the production migration, Elasticsearch should be cleared and rebuilt after the filesystem migration.

This provides a clean index generated from the migrated PDF set and ensures that Elasticsearch is synchronized with the final pastpaperdocs/filesystem state.

Production indexing procedure
After the PDF migration has completed successfully:
curl -s 'http://127.0.0.1:9200/_cat/indices?v'
Delete the existing pastpaper index:
curl -X DELETE 'http://127.0.0.1:9200/pastpaper'
Verify its deleted
curl -s 'http://127.0.0.1:9200/_cat/indices?v'
The old pastpaper index should no longer be present.

Rebuild indexing from the migrated PDFs
Run doIndex.bin.js against the migrated filesystem storage:

MONGODB="<PRODUCTION_MONGODB_URI>" \
ES="<PRODUCTION_ES_HOST>" \
STORAGE_ROOT="<PRODUCTION_STORAGE_ROOT>" \
node doIndex.bin.js <PDF_DIRECTORY>

doIndex.bin.js processes the PDFs and rebuilds the application/indexing data from the actual filesystem papers.

Migrated filesystem PDFs
          │
          ▼
     doIndex.bin.js
          │
          ├──────────────► pastpaperdocs
          │
          ├──────────────► pastpaperindexes
          │
          └──────────────► Elasticsearch

Verify Index
curl -s 'http://127.0.0.1:9200/_cat/indices?v'
Then
curl -s 'http://127.0.0.1:9200/pastpaper/_count'
Verify that Elasticsearch contains indexed documents.

  1. Application smoke test
    Start SchSrch against the migrated database and verify:
✓ application starts
✓ MongoDB connection works
✓ Elasticsearch connection works
✓ search works
✓ search results open
✓ PDFs open
✓ PDF navigation works
✓ multiple subjects/papers work
  1. Legacy Blob Cleanup
    After successful migration, verification and application testing:
MONGODB="<PRODUCTION_MONGODB_URI>" \
STORAGE_ROOT="/path/to/papers" \
node migrate-storage.js \
  --cleanup-blobs \
  --confirm-cleanup
  --force

⚠️This permanently drops:
pastpaperpaperblobs

Summary of migration

1. BACKUP
   mongodump
        │
        ▼
2. DRY RUN
   migrate-storage.js --dry-run
        │
        ▼
3. MIGRATE PDFs
   migrate-storage.js
        │
        ▼
4. VERIFY FILE STORAGE
   migrate-storage.js --verify
        │
        ▼
5. CLEAR ELASTICSEARCH
   DELETE /pastpaper
        │
        ▼
6. REINDEX
   doIndex.bin.js <migrated PDF directory>
        │
        ▼
7. APPLICATION SMOKE TEST
   Search + PDF opening + navigation
        │
        ▼
8. DELETE LEGACY BLOBS
   migrate-storage.js --cleanup-blobs --confirm-cleanup --force
        │
        ▼
9. VERIFY
   pastpaperpaperblobs no longer exists

- Migrate PDF storage from MongoDB blobs to filesystem-backed storage
- Introduce storage abstraction layer and static file serving
- Add migration and verification utilities with dry-run and integrity checks
- Remove legacy GridFS/blob storage and simplify document retrieval
- Implement type-aware metadata extraction for specimen papers, examiner reports and grade thresholds
- Improve indexing pipeline with ahead-of-time metadata and directory generation
- Add fuzzy search, phrase boosting and metadata-aware Elasticsearch ranking
- Optimize backend by eliminating N+1 database queries and reducing Elasticsearch payload sizes
- Modernize frontend with responsive layout, dark mode, advanced filters and improved search UX
- Add split-view PDF mode and improve result presentation
- Improve caching, compression and static asset delivery
- Modernize MongoDB usage and replace deprecated APIs where safe
- Expand documentation with architecture, storage and indexing guides
- Improve test suite reliability and make database setup idempotent
- Add storage verification, migration auditing and performance tooling

This update significantly reduces MongoDB storage requirements,
improves search relevance and performance, modernizes the frontend,
and prepares the project for future PDF.js modernization while
preserving backward compatibility and existing functionality.
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