Files
logbuch/src/data/schema/posts.ts
T
Matthias G 2fd71fd406 Number entries on publish, delete media, read the cover
- drafts stay unnumbered, the number is handed out when an entry goes live
- DELETE /api/v1/media/:id and a delete control in the media library
- cover_media_id in the draft read, coverMediaId and coverUrl in the archive read
2026-08-01 12:25:01 +02:00

75 lines
3.6 KiB
TypeScript

import { sql } from 'drizzle-orm'
import { index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, unique, uuid } from 'drizzle-orm/pg-core'
import { projects } from './brands'
import { media } from './media'
import { postTypes } from './post-types'
export const postAudience = pgEnum('post_audience', ['internal', 'customer', 'public'])
export const postStatus = pgEnum('post_status', ['draft', 'review', 'scheduled', 'published', 'archived'])
export const postLocale = pgEnum('post_locale', ['de', 'en'])
export const issueStatus = pgEnum('issue_status', ['draft', 'scheduled', 'published'])
export const issues = pgTable('issue', {
id: uuid('id').primaryKey().defaultRandom(),
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
title: text('title').notNull(),
periodFrom: timestamp('period_from', { withTimezone: true }),
periodTo: timestamp('period_to', { withTimezone: true }),
status: issueStatus('status').notNull().default('draft'),
publishAt: timestamp('publish_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export const posts = pgTable('post', {
id: uuid('id').primaryKey().defaultRandom(),
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
issueId: uuid('issue_id').references(() => issues.id, { onDelete: 'set null' }),
number: integer('number'),
slug: text('slug').notNull(),
title: text('title').notNull(),
teaser: text('teaser'),
typeId: uuid('type_id')
.notNull()
.default(sql`post_type_default()`)
.references(() => postTypes.id, { onDelete: 'restrict' }),
audience: postAudience('audience').notNull().default('internal'),
status: postStatus('status').notNull().default('draft'),
locale: postLocale('locale').notNull().default('de'),
translationGroup: uuid('translation_group'),
publishAt: timestamp('publish_at', { withTimezone: true }),
authorName: text('author_name'),
coverMediaId: uuid('cover_media_id').references(() => media.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
}, table => [
unique('post_project_slug').on(table.projectId, table.slug),
unique('post_project_number').on(table.projectId, table.number),
index('post_publish_at').on(table.publishAt),
index('post_project_status').on(table.projectId, table.status),
])
export const blocks = pgTable('block', {
id: uuid('id').primaryKey().defaultRandom(),
postId: uuid('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
sort: integer('sort').notNull().default(0),
type: text('type').notNull(),
data: jsonb('data').$type<Record<string, unknown>>().notNull().default({}),
}, table => [index('block_post_sort').on(table.postId, table.sort)])
export const tags = pgTable('tag', {
id: uuid('id').primaryKey().defaultRandom(),
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
slug: text('slug').notNull(),
label: text('label').notNull(),
}, table => [unique('tag_project_slug').on(table.projectId, table.slug)])
export const postTags = pgTable('post_tag', {
postId: uuid('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
tagId: uuid('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
}, table => [primaryKey({ columns: [table.postId, table.tagId] })])
export type Issue = typeof issues.$inferSelect
export type Post = typeof posts.$inferSelect
export type Block = typeof blocks.$inferSelect
export type Tag = typeof tags.$inferSelect