Skip to content

Latest commit

 

History

History
207 lines (143 loc) · 7.92 KB

File metadata and controls

207 lines (143 loc) · 7.92 KB

ZineCore2 Implementer’s Guide

ZineCore2 is a Dublin Core–based metadata application profile for zines, designed to be practical for zinesters, community libraries, and union catalogs. This guide explains how to implement ZineCore2 in a typical stack:

  • Django (canonical catalog API)
  • Typesense (search index)
  • Nuxt / TypeScript (frontend)
  • JSON / JSON-LD for interchange

1. Core artifacts

ZineCore2 consists of four main artifacts:

  1. DCAP narrative (docs/zinecore2-profile.md)
    Human-readable definition of elements, definitions, and examples.

  2. DC TAP table (profiles/zinecore2-tap.csv)
    Machine-readable application profile: shape, properties, cardinality, and value constraints.

  3. JSON-LD context (contexts/zinecore2-context.jsonld)
    Maps JSON keys (e.g. title, series_title) to IRIs (e.g. dcterms:title, dcterms:isPartOf).

  4. JSON Schema (schemas/zinecore2.schema.json)
    Validates JSON records and drives code generation (TypeScript types, forms, etc.).

Everything else (Django models, serializers, Typesense schemas, UI forms) should be treated as implementation details that conform to these artifacts.

2. Backend: Django catalog

2.1. Model design

Create a Zine model that mirrors the ZineCore2 fields. For example:

class Zine(models.Model):
    id = models.CharField(primary_key=True, max_length=64)
    title = models.CharField(max_length=512)

    series_title = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    issue_designation = models.CharField(max_length=255, blank=True, null=True)
    edition_statement = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    alternative_title = ArrayField(models.CharField(max_length=255), blank=True, default=list)

    creator = ArrayField(models.CharField(max_length=255))
    contributor = ArrayField(models.CharField(max_length=255), blank=True, default=list)

    subject = ArrayField(models.CharField(max_length=255))
    genre = ArrayField(models.CharField(max_length=255))

    abstract = models.TextField(blank=True)
    table_of_contents = models.TextField(blank=True)
    public_notes = ArrayField(models.TextField(), blank=True, default=list)

    publisher = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    date = ArrayField(models.CharField(max_length=64))  # allow "Spring 2010", "c. 1996"

    physical_dimensions = models.CharField(max_length=255, blank=True)
    number_of_pages = models.CharField(max_length=64, blank=True)
    format = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    binding_features = ArrayField(models.CharField(max_length=255), blank=True, default=list)

    language = ArrayField(models.CharField(max_length=16))
    place_of_publication = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    coverage = ArrayField(models.CharField(max_length=255), blank=True, default=list)

    source = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    relation = ArrayField(models.CharField(max_length=255), blank=True, default=list)
    rights = ArrayField(models.CharField(max_length=255))
    identifier = ArrayField(models.CharField(max_length=255), blank=True, default=list)

You can also normalize creators, subjects, and genres into separate tables and use M2M relations if you want authority control.

2.2. Serializers and validation

Use DRF serializers that:

  • Match the ZineCore2 JSON naming and structure (arrays, nulls).
  • Optionally validate against the JSON Schema using jsonschema or ajv on the backend.
class ZineSerializer(serializers.ModelSerializer):
    class Meta:
        model = Zine
        fields = "__all__"

For stricter validation, you can plug the JSON Schema into a custom validator before saving.

2.3. Import/export

  • Import from CSV/MARC: map incoming fields to ZineCore2 keys via a crosswalk.
  • Export:
    • /api/zines/:id → canonical ZineCore2 JSON (schema-conformant).
    • Optionally /api/zines/:id.jsonld → same JSON plus @context and @id for Linked Data.

3. Search: Typesense index

Create a Typesense zines collection that indexes key ZineCore2 fields:

  • title (string)
  • creator (string[])
  • series_title (string[])
  • issue_designation (string)
  • subject (string[])
  • genre (string[])
  • date (string[])
  • language (string[])
  • place_of_publication (string[])
  • rights (string[])

Store the full ZineCore2 JSON either:

  • As a zine nested object/field (if supported), or
  • As your ground truth in Django and just send a subset into Typesense.

On reindex:

  • Fetch canonical JSON from Django.
  • Validate against zinecore2.schema.json.
  • Transform into a Typesense document and upsert.

4. Frontend: Nuxt + TypeScript

4.1. Types

Use the ZineCore2 TypeScript interface:

import type { ZineCore2Zine } from '~/types/zinecore2';

async function fetchZine(id: string): Promise<ZineCore2Zine> {
  const { data } = await useFetch(`/api/zines/${id}`);
  return data.value as ZineCore2Zine;
}

All components dealing with zine records can now be fully typed.

4.2. Forms

For create/edit forms:

  • Base the form schema on the JSON Schema:
    • Required fields: mark as required in UI.
    • Arrays: render tag/“chips” inputs.
    • subject, genre, rights: populate from your vocab picklists (Anchor-based subjects, ZineCore2 genres, rights statements).
  • Enforce simple constraints client-side:
    • language string pattern for ISO codes.
    • At least one creator, subject, genre, date, language, rights.

You can also generate a simple form schema from the JSON Schema with a library, or hand-write it.

4.3. Display patterns

Use a consistent pattern when rendering ZineCore2 data:

  • Title + series/issue:
    • Mutate Zine #3 (Mutate Zine, No. 3) when series_title and issue_designation present.
  • Subline:
    • Perzine · 2009 · English · Portland, OR, USA.
  • Badges:
    • rights as license/rights badges.
    • genre as chips.
    • subject as clickable filters.

5. Vocabularies and constraints

ZineCore2 leaves vocabularies open but RECOMMENDS:

  • Subjects: Anchor Archive–inspired subject thesaurus (flattened for your use).
  • Genres: Short controlled list (perzine, fanzine, art zine, etc.).
  • Rights: Finite list including “All rights reserved”, “Anti-copyright”, “Copyleft”, “Please copy”, and CC licenses.

In your system:

  • Store vocabularies in their own tables (or JSON files) and reference them in UI.
  • Use valueConstraint labels from the DC TAP (AnchorArchiveSubjects, ZineCore2Genres, etc.) as keys to wire these together.

6. Validation and conformance

To claim ZineCore2 conformance:

  1. Record structure

    • Every zine issue record MUST validate against zinecore2.schema.json.
  2. Required fields

    • Every record MUST include: title, creator, subject, genre, date, language, rights.
  3. Semantics

    • series_title and issue_designation SHOULD be used for serial zines.
    • Each issue SHOULD be a separate record; series-level discovery is built by grouping on series_title and issue_designation.
  4. Interchange

    • Systems exchanging zine metadata SHOULD use ZineCore2 JSON (optionally JSON‑LD with the provided context).

7. Implementation workflow summary

  1. Add ZineCore2 JSON Schema, TAP CSV, and JSON‑LD context to your repo.
  2. Align your Django Zine model and serializers with the ZineCore2 fields.
  3. Use the JSON Schema to validate data at ingest and before export.
  4. Index key fields into Typesense for search/browse.
  5. Use the TypeScript interface in Nuxt for type-safe API access and forms.
  6. Document in your README that your API/web app is ZineCore2-compliant.

ZineCore2 then becomes the stable contract between creators, zine libraries, and your software: everyone speaks the same shape, regardless of backend details.