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
| Resource | Features |
|---|---|
| Post | CRUD, soft delete, audit, draft → review → published workflow enforced through field-level permissions on status, slug generation |
| Tag | CRUD, admin-only create/delete |
| Comment | CRUD, belongs to Post (cascade delete) |
Relationships
- Post hasMany Comment (cascade delete)
- Comment belongsTo Post
- Post belongsToMany Tag (via PostTag junction)
Roles & Permissions
| Role | Capabilities |
|---|---|
| admin | Full access to all resources |
| editor | Can publish/unpublish posts, manage tags |
| author | Can create/edit own posts, cannot publish |
| reader | Read-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]