← All seeds
Blog ADL v0.3.1

Blog Platform

An approachable starting point: posts with a draft → review → published workflow, many-to-many tags, and cascading comments.

Resources
Post, Tag, Comment
Roles
admin, editor, author, reader
Features (4)
soft_deleteauditm2m_relationshipspermissions

A complete blog platform with posts, tags, and comments.

Resources

ResourceFeatures
PostCRUD, soft delete, audit, draft → review → published workflow enforced through field-level permissions on status, slug generation
TagCRUD, admin-only create/delete
CommentCRUD, belongs to Post (cascade delete)

Relationships

  • Post hasMany Comment (cascade delete)
  • Comment belongsTo Post
  • Post belongsToMany Tag (via PostTag junction)

Roles & Permissions

RoleCapabilities
adminFull access to all resources
editorCan publish/unpublish posts, manage tags
authorCan create/edit own posts, cannot publish
readerRead-only access to published content, can create comments

Seed Data

5 posts (2 published, 1 review, 2 draft), 4 tags, users: admin, alice (author), bob (author), carol (reader), diana (editor).

Lua Scripts

  • scripts/search.lua: Full-text search across post titles, bodies, and excerpts

Domain definition

View raw (300 lines)
Show domain.yaml
# pocs/poc-01-blog/adl-blog.yaml
#
# Blog Platform — ADL Domain Definition
# ADL version: 0.3.1
#
# Resources:  Post, Tag, Comment
# Relations:  Post hasMany Comment (cascade delete)
#             Comment belongsTo Post
#             Post belongsToMany Tag (via PostTag junction)
#
# ADL features exercised:
#   Phase 1      — CRUD generation, REST route mapping, RBAC permissions
#   Phase 6      — hasMany, belongsTo, belongsToMany, ?include=
#   Phase 7      — filter, sort, paginate, search, count
#   Phase 8      — Rich types: markdown, slug, datetime, uuid
#   Phase 9      — $merge (Timestamp, SoftDelete objects), $ref enums, properties
#   Phase 10     — UUID primary keys, custom indexes on FK fields
#   Phase 11     — (reserved for POC-02; no state machine here)
#   Phase 12     — Soft delete on Post + Comment, audit trail on Post
#   Phase 14     — Field-level permissions (status write restricted to editor/admin)

domain:
  name: blog
  version: "1.0.0"
  description: >
    Blog platform demonstrating the full ADL v0.3.1 feature set.
    Posts, Tags, and Comments with full RBAC, soft delete, audit trails,
    rich enums, relationships, and query engine capabilities.

  api: {}

  # ─── Shared objects ────────────────────────────────────────────
  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

    SoftDelete:
      deletedAt: { type: datetime }

  # ─── Shared enums ──────────────────────────────────────────────
  enums:
    PostStatus:
      values:
        draft:
          label: "Draft"
          description: "Work in progress, not visible to public"
          color: "#94a3b8"
        review:
          label: "In Review"
          description: "Submitted for editorial review"
          color: "#f59e0b"
        published:
          label: "Published"
          description: "Live and publicly visible"
          color: "#10b981"
        archived:
          label: "Archived"
          description: "Removed from public listing, kept for record"
          color: "#64748b"
          final: true

  # ─── Shared properties ─────────────────────────────────────────
  properties:
    slug:
      type: slug
      unique: true
      maxLength: 255

  # ─── Resources ─────────────────────────────────────────────────
  resources:

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Post                                                        ║
    # ╚══════════════════════════════════════════════════════════════╝
    Post:
      description: >
        A blog post authored by a registered user. Posts progress through
        a status lifecycle (draft → review → published → archived).
        Soft-deleted posts are hidden from the default list scope but
        remain in the database and can be restored. All changes are
        tracked in the audit log.

      object:
        id:
          type: uuid
          primaryKey: true

        title:
          type: string
          required: true
          minLength: 3
          maxLength: 200

        slug:
          $ref: '#/properties/slug'
          from: title

        excerpt:
          type: text
          maxLength: 500

        body:
          type: markdown

        status:
          $ref: '#/enums/PostStatus'
          default: draft

        # Author is stored as username string for POC simplicity.
        # Production would use type: uuid with a FK to the auth domain.
        authorId:
          type: string
          required: true
          immutable: true
          maxLength: 100

        authorName:
          type: string
          maxLength: 200

        publishedAt:
          type: datetime

        $merge:
          - { $ref: '#/objects/Timestamp' }
          - { $ref: '#/objects/SoftDelete' }

      relationships:
        comments:
          type: hasMany
          target: Comment
          foreignKey: postId
          onDelete: cascade

        tags:
          type: belongsToMany
          target: Tag
          through: PostTag
          foreignKey: postId
          otherKey: tagId

      indexes:
        - fields: [status]
          description: "Filter posts by status"
        - fields: [authorId]
          description: "List posts by author"
        - fields: [publishedAt]
          description: "Sort and range-query by publish date"
        - fields: [status, publishedAt]
          description: "Composite: published posts by date"

      features:
        softDelete: true
        audit: true
        search: true
        export: true
        import: true

      audit:
        track: [title, status, body, excerpt, publishedAt]
        exclude: [updatedAt, deletedAt]

      validation:
        publishedAtRequiredWhenPublished:
          rule: "status != 'published' || publishedAt != null"
          message: "A publish date is required when setting status to published"

      permissions:
        list:   ["*"]
        read:   ["*"]
        create: [authenticated]
        update: [owner, editor, admin]
        delete: [admin]

        fields:
          # Only editors and admins can change post status
          status:
            read:  ["*"]
            write: [editor, admin]

          # Only editors and admins can set the publish date
          publishedAt:
            read:  ["*"]
            write: [editor, admin]

        transitions:
          # (No state machine in POC-01 — status changes go through field-level
          # permissions. POC-02 introduces full state machine transitions.)
  

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Tag                                                         ║
    # ╚══════════════════════════════════════════════════════════════╝
    Tag:
      description: >
        A label that can be applied to many posts via the PostTag
        junction table. Tags are admin-managed vocabulary; regular
        users can read but not create or modify them.

      object:
        id:
          type: uuid
          primaryKey: true

        name:
          type: string
          required: true
          unique: true
          minLength: 1
          maxLength: 50

        slug:
          $ref: '#/properties/slug'
          from: name

        color:
          type: string
          maxLength: 7
          pattern: '^#[0-9a-fA-F]{6}$'
          description: "Hex color code for UI display, e.g. #3b82f6"

        description:
          type: text
          maxLength: 300

        $merge:
          - { $ref: '#/objects/Timestamp' }

      permissions:
        list:   ["*"]
        read:   ["*"]
        create: [admin]
        update: [admin]
        delete: [admin]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Comment                                                     ║
    # ╚══════════════════════════════════════════════════════════════╝
    Comment:
      description: >
        A comment left by any authenticated user on a specific post.
        Comments are soft-deleted (never hard-deleted) to preserve
        discussion context. The postId and authorId are immutable
        after creation.

      object:
        id:
          type: uuid
          primaryKey: true

        body:
          type: text
          required: true
          minLength: 1
          maxLength: 5000

        postId:
          type: uuid
          required: true
          immutable: true

        authorId:
          type: string
          required: true
          immutable: true
          maxLength: 100

        authorName:
          type: string
          maxLength: 200

        $merge:
          - { $ref: '#/objects/Timestamp' }
          - { $ref: '#/objects/SoftDelete' }

      relationships:
        post:
          type: belongsTo
          target: Post
          foreignKey: postId
          onDelete: cascade

      indexes:
        - fields: [postId]
          description: "List all comments for a post"
        - fields: [authorId]
          description: "List all comments by an author"

      features:
        softDelete: true
        export: true
        import: true

      permissions:
        list:   ["*"]
        read:   ["*"]
        create: [authenticated]
        update: [owner, admin]
        delete: [owner, admin]

Download bundle