Archiving Events
When a gallery has done its job, archiving it produces a single .zip of every photo + the feedback data, deletes the originals from the active storage area, and renders the public gallery URL inactive. Archives stay on disk (or in S3) until you explicitly delete them.
When archives happen
Three triggers, all run the same archive routine:
- Manual — admin clicks Archive Event on the Event Details page.
- Bulk manual — admin selects multiple events on the Events table and clicks Archive Selected.
- Auto on expiry — the expiration checker (a cron job inside the backend) sees that
expires_athas passed and archives the event automatically.
The auto path also fires:
- A 7-day-out warning email to the customer (one-time, deduplicated via
email_queue.email_type='expiration_warning'). - An expired email to the customer + admin on the day of expiry.
- A
event.expiredwebhook before the archive runs, then aevent.archivedwebhook after — both contain the canonical event subject.
What the archive contains
Inside events/archived/{slug}.zip:
{slug}/
├── photos/
│ ├── DSC_0001.jpg ← originals at full resolution
│ ├── DSC_0002.jpg
│ └── ...
└── feedback/
├── feedback_data.json ← raw rows from the feedback table
├── feedback_data.csv ← same data, spreadsheet-friendly
└── feedback_summary.json ← aggregate counts (likes/ratings/comments per photo)The feedback files are only included if the event has feedback enabled and at least one feedback row exists. Compression: zlib level 9.
EXIF data on the photos is preserved in the archive (originals unchanged). GPS is intact in the archive too — only the displayed thumbnails strip GPS.
What’s deleted from the active area
After the archive zip is written and verified:
- Originals from
events/active/{slug}/— deleted - Thumbnails matching
photos.thumbnail_path— deleted - Hero photo files for the event — deleted
- Watermark variant files (if any were cached) — deleted
The photos rows stay in the database with is_archived=true set on the parent event. This is intentional: the photo IDs are referenced by feedback rows, activity logs, etc., and the row metadata (filename, size, captured_at) is useful for archive browsing.
What’s left at the URL
Visitors who hit /gallery/{slug} (or the share-link URL) after archive get the Gallery Not Found page — a CMS-customizable block in the admin’s CMS Pages section. Default copy: “This gallery is no longer available.”
If the event was simply expired (not archived yet — the few minutes between event.expired and event.archived firing), they get the Gallery Expired interstitial with a clock icon and the expiry date.
Client-access URLs to archived events also show the expired state — the special-access flow is gone too.
The Archives admin page
/admin/archives lists every archived event with:
- Event name, original event date, archive date
- Photo count, archive size on disk
- Download button (re-download the zip)
- Restore button (see below)
- Delete button (permanently removes the zip + the database row)
Restoring
The Restore button on the Archives page reverses the archive:
- Downloads the zip from storage.
- Extracts the photos back to
events/active/{slug}/. - Sets
is_archived=falseandis_active=trueon the event. - Regenerates thumbnails for every photo.
This is an O(n) operation in number of photos and runs in the foreground — for galleries with hundreds of photos, expect 1–5 minutes. The admin UI shows a spinner.
After restore the share link works again. Feedback rows are still there from before the archive, so likes/ratings/comments are preserved end-to-end.
Restore re-extracts to local disk. If your storage backend is S3, the photos are restored to local fs (your storage path) — not back into S3. You can re-archive after to push back to wherever your backups live.
Webhook payload
The event.archived webhook fires once per archive operation. Payload follows the canonical event subject, with one additional field:
{
"type": "event.archived",
"data": {
"event": {
"id": 42,
"slug": "wedding-smith",
"event_name": "Smith Wedding",
"...": "...",
"archive_path": "events/archived/wedding-smith.zip"
}
}
}For S3 backends, archive_path is the s3://bucket/key form.
See Webhooks for the full payload reference.
Where the code lives
- Trigger + manifest:
backend/src/services/archiveService.js - Auto-expiry cascade:
backend/src/services/expirationChecker.js - Cleanup of orphaned files:
backend/src/utils/cleanupTempUploads.js