Skip to content

Release model: move toward a proper API-backed data model #60

Description

@magent-cryptograss

Problem

Release pages are currently flat YAML blobs rendered by ReleaseContentHandler.php. There's no schema enforcement beyond valid YAML, no typed fields per release type, no object model, and no query methods beyond the custom releaselist API action.

Every new field requires touching the PHP renderer. Batch operations (enrichment, cleanup, migration) are reimplemented as standalone Python scripts in blue-railroad-import that parse YAML, mutate strings, and push edits through the MediaWiki API.

There are now three separate draft/staging systems that don't talk to each other:

  1. delivery-kid content drafts (/draft-content) — temporary file staging, 24h TTL
  2. PickiPedia ReleaseDrafts — wiki namespace pages with metadata
  3. Blue Railroad submissions — on-chain submissions with their own lifecycle

Each was invented independently with its own state management.

Vision

Releases should behave more like Django models:

  • Typed release kinds (album, blue-railroad, talk, video, etc.) with per-type required/optional fields
  • Proper validation on save
  • Query interface ("all releases where type=talk", "releases missing torrent metadata")
  • Methods (release.add_version(), release.enrich())
  • Unified draft/staging lifecycle

Decision: Django app (hybrid model)

The wiki page remains the human-editable interface. A Django app owns the data model, validation, and API. Edits sync bidirectionally.

Why Django over extending delivery-kid (FastAPI)

  • delivery-kid is about file staging and transcoding — different concern, different deploy cadence
  • Django's ORM, migrations, and admin are better suited for a data-modeling problem than FastAPI + hand-rolled SQLAlchemy
  • Django admin gives us a debugging/batch-ops interface for free
  • blue-railroad-import already does Release operations in Python — the model classes there (DraftType, RecordDraft, VideoDraft, etc.) are proto-Django models waiting to graduate

Why hybrid over pure-DB or pure-wiki

  • Wiki pages are the human interface people know. Editors shouldn't need to learn a new tool.
  • But wiki pages are a terrible database. No schema, no queries, no validation, no relations.
  • Hybrid: wiki is the view/editor layer, Django is the data layer. Best of both.

Architecture sketch

┌─────────────────────┐
│   Wiki page edit     │  (human or bot)
│   (ReleaseDraft or   │
│    Release namespace)│
└────────┬────────────┘
         │ ContentSaveComplete hook
         ▼
┌─────────────────────┐
│  PHP hook → HTTP     │  POST to Django API with parsed YAML
│  (PickiPediaReleases │
│   extension)         │
└────────┬────────────┘
         ▼
┌─────────────────────┐
│  Django app          │  Validates, stores, indexes
│  - Release model     │  Returns errors if validation fails
│  - ReleaseDraft model│  (hook can reject the edit)
│  - REST API          │
│  - Admin interface   │
└────────┬────────────┘
         │
         ▼
┌─────────────────────┐
│  PostgreSQL          │  magenta_memory DB or separate DB
│  (on hunter)         │
└─────────────────────┘

Reverse sync (Django → wiki)

When Django modifies a Release (e.g., bot enrichment, batch update):

  • Django calls the MediaWiki API to update the wiki page
  • Same pattern blue-railroad-import uses today, but from Django instead of standalone scripts

What moves out of PHP

Currently in PHP Moves to Django
YAML parsing + validation Model with typed fields
Draft type dispatch (record/video/other) Model subclasses or type field with per-type validation
releaselist API action REST API with filtering/search
Status derivation (is finalized? has Release page?) Model state field with transitions
Blockheight estimation Utility function (already in Python)

What stays in PHP

Stays in PHP Why
Wiki page rendering (ContentHandler) MW integration — but renders from Django API, not raw YAML
Special pages (DeliverRecord, etc.) MW integration — but POST to Django instead of building YAML
HMAC token generation Auth layer, MW session-aware
Hook wiring MW event system

What this replaces in blue-railroad-import

The release_draft.py module — DraftType, RecordDraft, VideoDraft, OtherDraft, BlueRailroadDraft, build_release_from_draft(), process_release_drafts() — all become Django model methods. The bot loop calls Django's API instead of parsing wiki YAML directly.

Implementation notes for later

Django models (rough sketch)

class Release(models.Model):
    cid = models.CharField(max_length=128, unique=True, db_index=True)
    release_type = models.CharField(choices=RELEASE_TYPES)  # record, video, other, blue-railroad
    title = models.CharField(max_length=512)
    description = models.TextField(blank=True)
    blockheight = models.PositiveBigIntegerField(null=True)  # content time
    upload_blockheight = models.PositiveBigIntegerField(null=True)  # upload time
    commit = models.CharField(max_length=64, blank=True)  # maybelle-config build
    pinned_on = ArrayField(models.CharField(max_length=64))
    created_at = models.DateTimeField(auto_now_add=True)

    # Type-specific fields (nullable, relevant per release_type)
    artist = models.CharField(max_length=256, blank=True)
    venue = models.CharField(max_length=256, blank=True)
    performers = ArrayField(models.CharField(max_length=128), default=list)
    file_type = models.CharField(max_length=64, blank=True)
    torrent_url = models.URLField(blank=True)
    webseed_url = models.URLField(blank=True)
    submission_id = models.PositiveIntegerField(null=True)  # Blue Railroad


class ReleaseDraft(models.Model):
    draft_id = models.UUIDField(unique=True)
    draft_type = models.CharField(choices=DRAFT_TYPES)
    source = models.CharField(max_length=64)  # special-deliver-video, blue-railroad-bot, etc.
    uploader = models.CharField(max_length=128)
    commit = models.CharField(max_length=64, blank=True)
    blockheight = models.PositiveBigIntegerField(null=True)
    upload_blockheight = models.PositiveBigIntegerField(null=True)
    state = models.CharField(choices=DRAFT_STATES)  # draft, transcoding, finalized, error
    release = models.ForeignKey(Release, null=True, on_delete=models.SET_NULL)
    metadata = models.JSONField(default=dict)  # type-specific content blob
    created_at = models.DateTimeField(auto_now_add=True)

Migration path

  1. Build the Django app with models + REST API + admin
  2. Write a management command that imports all existing Release and ReleaseDraft wiki pages into the DB (one-time migration)
  3. Add the ContentSaveComplete hook in PHP that syncs wiki edits → Django
  4. Switch the PHP ContentHandler to render from Django API responses
  5. Switch the Deliver page JS to POST structured data to Django instead of building YAML
  6. blue-railroad-import bot calls Django API instead of wiki API for Release operations
  7. delivery-kid finalize callback notifies Django instead of (or in addition to) creating wiki edits directly

Where it lives

Options:

  • New repo (cryptograss/pickipedia-api or cryptograss/release-service) — cleanest separation
  • Inside maybelle-config alongside delivery-kid — colocated with the other Python services
  • Inside pickipedia repo as a django/ directory — keeps wiki + API together

Leaning toward a new repo. It's a distinct service with its own deploy, DB, and dependencies.

Deploy

  • Runs on hunter alongside delivery-kid and the wiki
  • Nginx reverse-proxies /api/releases/ to Django (gunicorn/uvicorn)
  • Shares the PostgreSQL instance (separate database or schema)
  • Managed by maybelle-config deploy scripts

Context

This came up while cleaning up Release pages (CIDv0/v1 duplicates, unlabeled test uploads, missing metadata) and while porting the Coconut.co transcoding integration. The lack of structure makes problems hard to prevent and tedious to fix. The three parallel draft systems are a symptom of not having a unified Release lifecycle.

As of block ~22520000 (March 2026), the PHP extension handles: 3 Special pages (DeliverRecord, DeliverOtherContent, DeliverVideo), ReleaseDraft content type with 5 draft type handlers, Release content type, HMAC auth for upload/finalize, and hand-built YAML in 4 JS modules via quoteYamlValue(). All of this strains under the lack of a real data model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions