yap-blog/drizzle/meta/0006_snapshot.json
matt 35fb33c5a7 Add granular author permissions, admin-tag persistence, own-password change
Each author account now carries five grantable permissions, editable on
the Users page: publish posts, unpublish posts, delete posts (all three
scoped to the author's own posts), create tags, and approve comments
(scoped to comments on the author's posts, without delete). A bare
author writes and edits their own drafts only. Permission checks gate
the status TRANSITION, so editing an already-published post never
requires the publish permission, and the editor's status dropdown only
offers what the account may do. Tags an author creates are granted to
them automatically, and "creating" an existing off-grant tag is
refused (it would be a self-grant loophole). Existing author accounts
keep publish+unpublish via migration backfill.

Tags the admin attaches outside an author's grants now survive the
author's edits: the form shows them checked-and-locked and the server
re-attaches them on every save.

Every account can change its own password on the new /admin/account
page (current password required); the username in the admin header
links there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 12:58:19 -04:00

1042 lines
26 KiB
JSON

{
"id": "cdae9fb6-cf03-47a9-a922-b6bd45ae9017",
"prevId": "32da6541-c2e9-4bd5-8747-d4ec83671204",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.comments": {
"name": "comments",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "comments_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"post_id": {
"name": "post_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"parent_id": {
"name": "parent_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"author_name": {
"name": "author_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"author_email": {
"name": "author_email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_public": {
"name": "email_public",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"body": {
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "comment_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"comments_post_id_status_idx": {
"name": "comments_post_id_status_idx",
"columns": [
{
"expression": "post_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "status",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"comments_parent_id_idx": {
"name": "comments_parent_id_idx",
"columns": [
{
"expression": "parent_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"comments_status_idx": {
"name": "comments_status_idx",
"columns": [
{
"expression": "status",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"comments_post_id_posts_id_fk": {
"name": "comments_post_id_posts_id_fk",
"tableFrom": "comments",
"tableTo": "posts",
"columnsFrom": [
"post_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"comments_parent_id_comments_id_fk": {
"name": "comments_parent_id_comments_id_fk",
"tableFrom": "comments",
"tableTo": "comments",
"columnsFrom": [
"parent_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.nav_items": {
"name": "nav_items",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "nav_items_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"label": {
"name": "label",
"type": "text",
"primaryKey": false,
"notNull": true
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"page_id": {
"name": "page_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {
"nav_items_page_id_pages_id_fk": {
"name": "nav_items_page_id_pages_id_fk",
"tableFrom": "nav_items",
"tableTo": "pages",
"columnsFrom": [
"page_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {
"nav_items_target_check": {
"name": "nav_items_target_check",
"value": "(\"nav_items\".\"url\" IS NULL) <> (\"nav_items\".\"page_id\" IS NULL)"
}
},
"isRLSEnabled": false
},
"public.pages": {
"name": "pages",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "pages_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"body": {
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"status": {
"name": "status",
"type": "content_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'draft'"
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"pages_slug_unique": {
"name": "pages_slug_unique",
"nullsNotDistinct": false,
"columns": [
"slug"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.post_tags": {
"name": "post_tags",
"schema": "",
"columns": {
"post_id": {
"name": "post_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"tag_id": {
"name": "tag_id",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"post_tags_tag_id_idx": {
"name": "post_tags_tag_id_idx",
"columns": [
{
"expression": "tag_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"post_tags_post_id_posts_id_fk": {
"name": "post_tags_post_id_posts_id_fk",
"tableFrom": "post_tags",
"tableTo": "posts",
"columnsFrom": [
"post_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"post_tags_tag_id_tags_id_fk": {
"name": "post_tags_tag_id_tags_id_fk",
"tableFrom": "post_tags",
"tableTo": "tags",
"columnsFrom": [
"tag_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"post_tags_post_id_tag_id_pk": {
"name": "post_tags_post_id_tag_id_pk",
"columns": [
"post_id",
"tag_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.posts": {
"name": "posts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "posts_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"body": {
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"author_name": {
"name": "author_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"author_id": {
"name": "author_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"featured_image_url": {
"name": "featured_image_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"featured_image_alt": {
"name": "featured_image_alt",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "content_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'draft'"
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"published_at": {
"name": "published_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"posts_status_published_at_idx": {
"name": "posts_status_published_at_idx",
"columns": [
{
"expression": "status",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "published_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"posts_author_id_users_id_fk": {
"name": "posts_author_id_users_id_fk",
"tableFrom": "posts",
"tableTo": "users",
"columnsFrom": [
"author_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"posts_slug_unique": {
"name": "posts_slug_unique",
"nullsNotDistinct": false,
"columns": [
"slug"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.settings": {
"name": "settings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true
},
"site_title": {
"name": "site_title",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'My Blog'"
},
"header_text": {
"name": "header_text",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"footer_text": {
"name": "footer_text",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"posts_per_page": {
"name": "posts_per_page",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 10
},
"excerpt_words": {
"name": "excerpt_words",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 40
},
"home_mode": {
"name": "home_mode",
"type": "home_mode",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'posts'"
},
"home_tag_id": {
"name": "home_tag_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"home_page_id": {
"name": "home_page_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"theme": {
"name": "theme",
"type": "theme",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'solarized-dark'"
},
"font": {
"name": "font",
"type": "font",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'geist'"
}
},
"indexes": {},
"foreignKeys": {
"settings_home_tag_id_tags_id_fk": {
"name": "settings_home_tag_id_tags_id_fk",
"tableFrom": "settings",
"tableTo": "tags",
"columnsFrom": [
"home_tag_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"settings_home_page_id_pages_id_fk": {
"name": "settings_home_page_id_pages_id_fk",
"tableFrom": "settings",
"tableTo": "pages",
"columnsFrom": [
"home_page_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {
"settings_single_row_check": {
"name": "settings_single_row_check",
"value": "\"settings\".\"id\" = 1"
},
"settings_posts_per_page_check": {
"name": "settings_posts_per_page_check",
"value": "\"settings\".\"posts_per_page\" BETWEEN 1 AND 50"
},
"settings_excerpt_words_check": {
"name": "settings_excerpt_words_check",
"value": "\"settings\".\"excerpt_words\" BETWEEN 5 AND 200"
}
},
"isRLSEnabled": false
},
"public.tags": {
"name": "tags",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "tags_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tags_name_unique": {
"name": "tags_name_unique",
"nullsNotDistinct": false,
"columns": [
"name"
]
},
"tags_slug_unique": {
"name": "tags_slug_unique",
"nullsNotDistinct": false,
"columns": [
"slug"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user_tags": {
"name": "user_tags",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"tag_id": {
"name": "tag_id",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"user_tags_tag_id_idx": {
"name": "user_tags_tag_id_idx",
"columns": [
{
"expression": "tag_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"user_tags_user_id_users_id_fk": {
"name": "user_tags_user_id_users_id_fk",
"tableFrom": "user_tags",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"user_tags_tag_id_tags_id_fk": {
"name": "user_tags_tag_id_tags_id_fk",
"tableFrom": "user_tags",
"tableTo": "tags",
"columnsFrom": [
"tag_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"user_tags_user_id_tag_id_pk": {
"name": "user_tags_user_id_tag_id_pk",
"columns": [
"user_id",
"tag_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"identity": {
"type": "always",
"name": "users_id_seq",
"schema": "public",
"increment": "1",
"startWith": "1",
"minValue": "1",
"maxValue": "2147483647",
"cache": "1",
"cycle": false
}
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "user_role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'author'"
},
"can_create_tags": {
"name": "can_create_tags",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"can_publish_posts": {
"name": "can_publish_posts",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"can_unpublish_posts": {
"name": "can_unpublish_posts",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"can_delete_posts": {
"name": "can_delete_posts",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"can_approve_comments": {
"name": "can_approve_comments",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.comment_status": {
"name": "comment_status",
"schema": "public",
"values": [
"pending",
"approved"
]
},
"public.content_status": {
"name": "content_status",
"schema": "public",
"values": [
"draft",
"published"
]
},
"public.font": {
"name": "font",
"schema": "public",
"values": [
"geist",
"inter",
"lora",
"merriweather",
"jetbrains-mono",
"source-serif",
"eb-garamond",
"playfair-display",
"open-sans",
"work-sans",
"atkinson-hyperlegible",
"space-grotesk"
]
},
"public.home_mode": {
"name": "home_mode",
"schema": "public",
"values": [
"posts",
"tag",
"page"
]
},
"public.theme": {
"name": "theme",
"schema": "public",
"values": [
"solarized-dark",
"solarized-light",
"dracula",
"nord",
"gruvbox-dark",
"mono",
"mono-dark",
"catppuccin-mocha",
"catppuccin-latte",
"tokyo-night",
"one-dark",
"rose-pine",
"everforest-dark",
"monokai",
"github-light"
]
},
"public.user_role": {
"name": "user_role",
"schema": "public",
"values": [
"admin",
"author"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}