@manablox/db 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-Cyf_N5K3.d.ts → index-BLAkQMJT.d.ts} +223 -4
- package/dist/{index-rZ24t-Ln.d.ts → index-DrNMGM9N.d.ts} +858 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/{repositories-DYjzuuF6.js → repositories-pz4NeWaF.js} +407 -12
- package/dist/{schema-Bb4p16Yz.js → schema-Dm3RcBst.js} +111 -2
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +2 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/migrations/0009_audit-log.sql +40 -0
- package/migrations/0010_notifications-approvals.sql +45 -0
- package/migrations/meta/0009_snapshot.json +3192 -0
- package/migrations/meta/0010_snapshot.json +3574 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { relations, sql } from "drizzle-orm";
|
|
3
|
-
import { boolean, customType, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
3
|
+
import { bigserial, boolean, customType, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
4
4
|
//#region src/columns.ts
|
|
5
5
|
/**
|
|
6
6
|
* `ltree` — the materialised ancestor path of a content node. A subtree move is one
|
|
@@ -193,6 +193,44 @@ const assetUsages = pgTable("asset_usages", {
|
|
|
193
193
|
index("asset_usages_content_idx").on(table.contentId)
|
|
194
194
|
]);
|
|
195
195
|
//#endregion
|
|
196
|
+
//#region src/schema/audit.ts
|
|
197
|
+
/**
|
|
198
|
+
* The audit log: one row per action, appended and never changed.
|
|
199
|
+
*
|
|
200
|
+
* Nothing here references another table. A space, a user or a document may be deleted
|
|
201
|
+
* long after the row was written, and the row must still say who did what: the actor
|
|
202
|
+
* and the target are recorded by id *and* by label, as they were at the time. A trigger
|
|
203
|
+
* (see the migration) refuses every `update` and `delete`, and each row carries the
|
|
204
|
+
* SHA-256 of its content chained to the previous row's, so a gap or an edit shows up
|
|
205
|
+
* when the chain is verified.
|
|
206
|
+
*/
|
|
207
|
+
const auditEntries = pgTable("audit_entries", {
|
|
208
|
+
id: uuid().primaryKey().defaultRandom(),
|
|
209
|
+
/** The order of the chain; assigned by the database, gapless within a transaction. */
|
|
210
|
+
seq: bigserial({ mode: "number" }).notNull(),
|
|
211
|
+
at: timestamp({ withTimezone: true }).notNull().defaultNow(),
|
|
212
|
+
/** Null for an instance-wide action: an account, a key, creating a space. */
|
|
213
|
+
spaceId: uuid(),
|
|
214
|
+
actorKind: text().$type().notNull(),
|
|
215
|
+
actorId: text(),
|
|
216
|
+
actorLabel: text().notNull(),
|
|
217
|
+
actorDetail: jsonb().$type(),
|
|
218
|
+
action: text().$type().notNull(),
|
|
219
|
+
targetKind: text().$type().notNull(),
|
|
220
|
+
targetId: text(),
|
|
221
|
+
targetLabel: text(),
|
|
222
|
+
changes: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
|
|
223
|
+
meta: jsonb().$type(),
|
|
224
|
+
prevHash: text(),
|
|
225
|
+
hash: text().notNull()
|
|
226
|
+
}, (table) => [
|
|
227
|
+
index("audit_entries_seq_idx").on(table.seq),
|
|
228
|
+
index("audit_entries_space_at_idx").on(table.spaceId, table.at),
|
|
229
|
+
index("audit_entries_target_idx").on(table.targetKind, table.targetId),
|
|
230
|
+
index("audit_entries_actor_idx").on(table.actorKind, table.actorId),
|
|
231
|
+
index("audit_entries_action_idx").on(table.action)
|
|
232
|
+
]);
|
|
233
|
+
//#endregion
|
|
196
234
|
//#region src/schema/auth.ts
|
|
197
235
|
const users = pgTable("users", {
|
|
198
236
|
id: uuid().primaryKey().defaultRandom(),
|
|
@@ -204,6 +242,11 @@ const users = pgTable("users", {
|
|
|
204
242
|
role: text().notNull().default("editor"),
|
|
205
243
|
banned: boolean().notNull().default(false),
|
|
206
244
|
banReason: text(),
|
|
245
|
+
/**
|
|
246
|
+
* Per notification kind, the channels this person switched on or off. Only what
|
|
247
|
+
* differs from the catalogue's defaults is stored; see `resolveNotificationPreferences`.
|
|
248
|
+
*/
|
|
249
|
+
notificationPreferences: jsonb().$type().notNull().default(sql`'{}'::jsonb`),
|
|
207
250
|
createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
|
|
208
251
|
updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
|
|
209
252
|
}, (table) => [uniqueIndex("users_email_key").on(table.email)]);
|
|
@@ -309,6 +352,7 @@ const contentTypes = pgTable("content_types", {
|
|
|
309
352
|
isPublishable: boolean().notNull().default(true),
|
|
310
353
|
isVisibleInTree: boolean().notNull().default(true),
|
|
311
354
|
canBeVisibleInMenu: boolean().notNull().default(true),
|
|
355
|
+
requiresApproval: boolean().notNull().default(false),
|
|
312
356
|
fields: jsonb().$type().notNull().default(sql`'[]'::jsonb`),
|
|
313
357
|
createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
|
|
314
358
|
updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow()
|
|
@@ -349,6 +393,68 @@ const menuItems = pgTable("menu_items", {
|
|
|
349
393
|
url: text()
|
|
350
394
|
}, (table) => [index("menu_items_menu_idx").on(table.menuId, table.parentId, table.position), index("menu_items_localization_idx").on(table.localizationId)]);
|
|
351
395
|
//#endregion
|
|
396
|
+
//#region src/schema/notifications.ts
|
|
397
|
+
/**
|
|
398
|
+
* One notification, for one person. A single event that concerns five people is five
|
|
399
|
+
* rows, so each can be read, kept or dropped on its own; the fan-out is the service's.
|
|
400
|
+
*
|
|
401
|
+
* The row is the in-app copy. Email and push are sent at the same time and leave no
|
|
402
|
+
* row of their own: a notification the person turned off in the admin but kept by
|
|
403
|
+
* email is a mail, not a row here.
|
|
404
|
+
*/
|
|
405
|
+
const notifications = pgTable("notifications", {
|
|
406
|
+
id: uuid().primaryKey().defaultRandom(),
|
|
407
|
+
userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
408
|
+
/** Null for something that concerns the instance rather than a space. */
|
|
409
|
+
spaceId: uuid().references(() => spaces.id, { onDelete: "cascade" }),
|
|
410
|
+
kind: text().$type().notNull(),
|
|
411
|
+
title: text().notNull(),
|
|
412
|
+
body: text().notNull().default(""),
|
|
413
|
+
/** Where to go in the admin, as a path: `/content/<id>`. */
|
|
414
|
+
url: text(),
|
|
415
|
+
/** What it is about, so the inbox can group and a target's later fate can be shown. */
|
|
416
|
+
targetKind: text(),
|
|
417
|
+
targetId: text(),
|
|
418
|
+
/**
|
|
419
|
+
* Who caused it; `null` for the system. Text rather than a uuid, like the audit
|
|
420
|
+
* log's actor: it is a snapshot of whoever acted, not a reference.
|
|
421
|
+
*/
|
|
422
|
+
actorId: text(),
|
|
423
|
+
actorLabel: text(),
|
|
424
|
+
meta: jsonb().$type(),
|
|
425
|
+
readAt: timestamp({ withTimezone: true }),
|
|
426
|
+
createdAt: timestamp({ withTimezone: true }).notNull().defaultNow()
|
|
427
|
+
}, (table) => [
|
|
428
|
+
index("notifications_user_created_idx").on(table.userId, table.createdAt),
|
|
429
|
+
index("notifications_user_unread_idx").on(table.userId, table.readAt),
|
|
430
|
+
index("notifications_target_idx").on(table.targetKind, table.targetId)
|
|
431
|
+
]);
|
|
432
|
+
/**
|
|
433
|
+
* A request to publish a document on the author's behalf, and what became of it. One
|
|
434
|
+
* document may have several over its life (sent back, resubmitted), but at most one
|
|
435
|
+
* pending at a time; the service keeps that rule, the table keeps the history.
|
|
436
|
+
*
|
|
437
|
+
* Cascades with the document: a deleted draft has nothing left to approve.
|
|
438
|
+
*/
|
|
439
|
+
const contentApprovals = pgTable("content_approvals", {
|
|
440
|
+
id: uuid().primaryKey().defaultRandom(),
|
|
441
|
+
spaceId: uuid().notNull().references(() => spaces.id, { onDelete: "cascade" }),
|
|
442
|
+
contentId: uuid().notNull().references(() => contents.id, { onDelete: "cascade" }),
|
|
443
|
+
typeId: uuid().notNull(),
|
|
444
|
+
status: text().$type().notNull().default("pending"),
|
|
445
|
+
/** Who asked; kept even once the account is gone, so the history stays readable. */
|
|
446
|
+
requestedBy: uuid(),
|
|
447
|
+
requestedByLabel: text().notNull().default(""),
|
|
448
|
+
requestNote: text(),
|
|
449
|
+
/** The document's version when it was submitted, so a reviewer sees what changed since. */
|
|
450
|
+
contentVersion: integer(),
|
|
451
|
+
requestedAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
|
|
452
|
+
decidedBy: uuid(),
|
|
453
|
+
decidedByLabel: text(),
|
|
454
|
+
decisionNote: text(),
|
|
455
|
+
decidedAt: timestamp({ withTimezone: true })
|
|
456
|
+
}, (table) => [index("content_approvals_content_idx").on(table.contentId, table.requestedAt), index("content_approvals_space_status_idx").on(table.spaceId, table.status, table.requestedAt)]);
|
|
457
|
+
//#endregion
|
|
352
458
|
//#region src/schema/relations.ts
|
|
353
459
|
const spacesRelations = relations(spaces, ({ many }) => ({
|
|
354
460
|
contents: many(contents),
|
|
@@ -510,6 +616,8 @@ var schema_exports = /* @__PURE__ */ __exportAll({
|
|
|
510
616
|
assetVariantsRelations: () => assetVariantsRelations,
|
|
511
617
|
assets: () => assets,
|
|
512
618
|
assetsRelations: () => assetsRelations,
|
|
619
|
+
auditEntries: () => auditEntries,
|
|
620
|
+
contentApprovals: () => contentApprovals,
|
|
513
621
|
contentTypes: () => contentTypes,
|
|
514
622
|
contentVersions: () => contentVersions,
|
|
515
623
|
contents: () => contents,
|
|
@@ -520,6 +628,7 @@ var schema_exports = /* @__PURE__ */ __exportAll({
|
|
|
520
628
|
menuItemsRelations: () => menuItemsRelations,
|
|
521
629
|
menus: () => menus,
|
|
522
630
|
menusRelations: () => menusRelations,
|
|
631
|
+
notifications: () => notifications,
|
|
523
632
|
publishedContents: () => publishedContents,
|
|
524
633
|
pushSubscriptions: () => pushSubscriptions,
|
|
525
634
|
roles: () => roles,
|
|
@@ -536,4 +645,4 @@ var schema_exports = /* @__PURE__ */ __exportAll({
|
|
|
536
645
|
workflows: () => workflows
|
|
537
646
|
});
|
|
538
647
|
//#endregion
|
|
539
|
-
export {
|
|
648
|
+
export { assetUsages as A, ltree as B, apikeys as C, users as D, sessions as E, publishedContents as F, pathDepth as H, spaces as I, buildPath as L, assets as M, contentVersions as N, verifications as O, contents as P, idToLabel as R, accounts as S, roles as T, tsvector as U, parsePath as V, contentApprovals as _, webhookDeliveries as a, menus as b, assetVariantsRelations as c, membershipsRelations as d, menuItemsRelations as f, usersRelations as g, spacesRelations as h, workflows as i, assetVariants as j, auditEntries as k, assetsRelations as l, rolesRelations as m, pushSubscriptions as n, webhooks as o, menusRelations as p, workflowRuns as r, assetUsagesRelations as s, schema_exports as t, contentsRelations as u, notifications as v, memberships as w, contentTypes as x, menuItems as y, labelToId as z };
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { AssetRow, AssetUsageRow, AssetVariantRow, ContentInsert, ContentRow, ContentTypeRow, ContentVersionRow, MembershipRow, MenuItemRow, MenuRow, PushSubscriptionRow, RoleRow, SpaceRow, UserRow, WorkflowRow, WorkflowRunRow, accounts, apikeys, assetUsages, assetUsagesRelations, assetVariants, assetVariantsRelations, assets, assetsRelations, contentTypes, contentVersions, contents, contentsRelations, memberships, membershipsRelations, menuItems, menuItemsRelations, menus, menusRelations, publishedContents, pushSubscriptions, roles, rolesRelations, sessions, spaces, spacesRelations, users, usersRelations, verifications, webhookDeliveries, webhooks, workflowRuns, workflows };
|
|
1
|
+
import { $ as assetUsages, A as contentsRelations, B as menus, C as workflows, D as assetUsagesRelations, E as spaces, F as spacesRelations, G as accounts, H as contentVersions, I as usersRelations, J as roles, K as apikeys, L as contentApprovals, M as menuItemsRelations, N as menusRelations, O as assetVariantsRelations, P as rolesRelations, Q as auditEntries, R as notifications, S as workflowRuns, T as webhooks, U as contents, V as contentTypes, W as publishedContents, X as users, Y as sessions, Z as verifications, _ as UserRow, a as ContentApprovalRow, c as ContentTypeRow, d as MenuItemRow, et as assetVariants, f as MenuRow, g as SpaceRow, h as RoleRow, i as AuditEntryRow, j as membershipsRelations, k as assetsRelations, l as ContentVersionRow, m as PushSubscriptionRow, n as AssetUsageRow, o as ContentInsert, p as NotificationRow, q as memberships, r as AssetVariantRow, s as ContentRow, t as AssetRow, tt as assets, u as MembershipRow, v as WorkflowRow, w as webhookDeliveries, x as pushSubscriptions, y as WorkflowRunRow, z as menuItems } from "./index-DrNMGM9N.js";
|
|
2
|
+
export { AssetRow, AssetUsageRow, AssetVariantRow, AuditEntryRow, ContentApprovalRow, ContentInsert, ContentRow, ContentTypeRow, ContentVersionRow, MembershipRow, MenuItemRow, MenuRow, NotificationRow, PushSubscriptionRow, RoleRow, SpaceRow, UserRow, WorkflowRow, WorkflowRunRow, accounts, apikeys, assetUsages, assetUsagesRelations, assetVariants, assetVariantsRelations, assets, assetsRelations, auditEntries, contentApprovals, contentTypes, contentVersions, contents, contentsRelations, memberships, membershipsRelations, menuItems, menuItemsRelations, menus, menusRelations, notifications, publishedContents, pushSubscriptions, roles, rolesRelations, sessions, spaces, spacesRelations, users, usersRelations, verifications, webhookDeliveries, webhooks, workflowRuns, workflows };
|
package/dist/schema.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { accounts, apikeys, assetUsages, assetUsagesRelations, assetVariants, assetVariantsRelations, assets, assetsRelations, contentTypes, contentVersions, contents, contentsRelations, memberships, membershipsRelations, menuItems, menuItemsRelations, menus, menusRelations, publishedContents, pushSubscriptions, roles, rolesRelations, sessions, spaces, spacesRelations, users, usersRelations, verifications, webhookDeliveries, webhooks, workflowRuns, workflows };
|
|
1
|
+
import { A as assetUsages, C as apikeys, D as users, E as sessions, F as publishedContents, I as spaces, M as assets, N as contentVersions, O as verifications, P as contents, S as accounts, T as roles, _ as contentApprovals, a as webhookDeliveries, b as menus, c as assetVariantsRelations, d as membershipsRelations, f as menuItemsRelations, g as usersRelations, h as spacesRelations, i as workflows, j as assetVariants, k as auditEntries, l as assetsRelations, m as rolesRelations, n as pushSubscriptions, o as webhooks, p as menusRelations, r as workflowRuns, s as assetUsagesRelations, u as contentsRelations, v as notifications, w as memberships, x as contentTypes, y as menuItems } from "./schema-Dm3RcBst.js";
|
|
2
|
+
export { accounts, apikeys, assetUsages, assetUsagesRelations, assetVariants, assetVariantsRelations, assets, assetsRelations, auditEntries, contentApprovals, contentTypes, contentVersions, contents, contentsRelations, memberships, membershipsRelations, menuItems, menuItemsRelations, menus, menusRelations, notifications, publishedContents, pushSubscriptions, roles, rolesRelations, sessions, spaces, spacesRelations, users, usersRelations, verifications, webhookDeliveries, webhooks, workflowRuns, workflows };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { it as DatabaseHandle, t as Repositories } from "./index-BLAkQMJT.js";
|
|
2
2
|
import { ContentTypeDefinition, ContentTypeRegistry } from "@manablox/core";
|
|
3
3
|
//#region src/testing-fixtures.d.ts
|
|
4
4
|
export declare const stringField: import("@manablox/core").FieldTypeDefinition<Record<string, unknown>, string>;
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as applyBootstrapSql, t as createRepositories, y as createDatabase } from "./repositories-pz4NeWaF.js";
|
|
2
2
|
import postgres from "postgres";
|
|
3
3
|
import { ContentTypeRegistry, FieldTypeRegistry, defineContentType, defineFieldType } from "@manablox/core";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
CREATE TABLE "audit_entries" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"seq" bigserial NOT NULL,
|
|
4
|
+
"at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
5
|
+
"space_id" uuid,
|
|
6
|
+
"actor_kind" text NOT NULL,
|
|
7
|
+
"actor_id" text,
|
|
8
|
+
"actor_label" text NOT NULL,
|
|
9
|
+
"actor_detail" jsonb,
|
|
10
|
+
"action" text NOT NULL,
|
|
11
|
+
"target_kind" text NOT NULL,
|
|
12
|
+
"target_id" text,
|
|
13
|
+
"target_label" text,
|
|
14
|
+
"changes" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
15
|
+
"meta" jsonb,
|
|
16
|
+
"prev_hash" text,
|
|
17
|
+
"hash" text NOT NULL
|
|
18
|
+
);
|
|
19
|
+
--> statement-breakpoint
|
|
20
|
+
CREATE INDEX "audit_entries_seq_idx" ON "audit_entries" USING btree ("seq");--> statement-breakpoint
|
|
21
|
+
CREATE INDEX "audit_entries_space_at_idx" ON "audit_entries" USING btree ("space_id","at");--> statement-breakpoint
|
|
22
|
+
CREATE INDEX "audit_entries_target_idx" ON "audit_entries" USING btree ("target_kind","target_id");--> statement-breakpoint
|
|
23
|
+
CREATE INDEX "audit_entries_actor_idx" ON "audit_entries" USING btree ("actor_kind","actor_id");--> statement-breakpoint
|
|
24
|
+
CREATE INDEX "audit_entries_action_idx" ON "audit_entries" USING btree ("action");--> statement-breakpoint
|
|
25
|
+
-- The log is append-only. Postgres has no "insert-only" grant short of a second role,
|
|
26
|
+
-- so a trigger refuses every update and delete instead; dropping it is a deliberate,
|
|
27
|
+
-- visible act rather than a stray statement. Not tracked by drizzle-kit: `pnpm
|
|
28
|
+
-- db:generate` neither sees nor removes it.
|
|
29
|
+
CREATE FUNCTION audit_entries_immutable() RETURNS trigger AS $$
|
|
30
|
+
BEGIN
|
|
31
|
+
RAISE EXCEPTION 'audit_entries is append-only: % refused', TG_OP
|
|
32
|
+
USING ERRCODE = 'insufficient_privilege';
|
|
33
|
+
END;
|
|
34
|
+
$$ LANGUAGE plpgsql;--> statement-breakpoint
|
|
35
|
+
CREATE TRIGGER audit_entries_immutable
|
|
36
|
+
BEFORE UPDATE OR DELETE ON "audit_entries"
|
|
37
|
+
FOR EACH ROW EXECUTE FUNCTION audit_entries_immutable();--> statement-breakpoint
|
|
38
|
+
CREATE TRIGGER audit_entries_no_truncate
|
|
39
|
+
BEFORE TRUNCATE ON "audit_entries"
|
|
40
|
+
FOR EACH STATEMENT EXECUTE FUNCTION audit_entries_immutable();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
CREATE TABLE "content_approvals" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"space_id" uuid NOT NULL,
|
|
4
|
+
"content_id" uuid NOT NULL,
|
|
5
|
+
"type_id" uuid NOT NULL,
|
|
6
|
+
"status" text DEFAULT 'pending' NOT NULL,
|
|
7
|
+
"requested_by" uuid,
|
|
8
|
+
"requested_by_label" text DEFAULT '' NOT NULL,
|
|
9
|
+
"request_note" text,
|
|
10
|
+
"content_version" integer,
|
|
11
|
+
"requested_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
12
|
+
"decided_by" uuid,
|
|
13
|
+
"decided_by_label" text,
|
|
14
|
+
"decision_note" text,
|
|
15
|
+
"decided_at" timestamp with time zone
|
|
16
|
+
);
|
|
17
|
+
--> statement-breakpoint
|
|
18
|
+
CREATE TABLE "notifications" (
|
|
19
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
20
|
+
"user_id" uuid NOT NULL,
|
|
21
|
+
"space_id" uuid,
|
|
22
|
+
"kind" text NOT NULL,
|
|
23
|
+
"title" text NOT NULL,
|
|
24
|
+
"body" text DEFAULT '' NOT NULL,
|
|
25
|
+
"url" text,
|
|
26
|
+
"target_kind" text,
|
|
27
|
+
"target_id" text,
|
|
28
|
+
"actor_id" text,
|
|
29
|
+
"actor_label" text,
|
|
30
|
+
"meta" jsonb,
|
|
31
|
+
"read_at" timestamp with time zone,
|
|
32
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
33
|
+
);
|
|
34
|
+
--> statement-breakpoint
|
|
35
|
+
ALTER TABLE "users" ADD COLUMN "notification_preferences" jsonb DEFAULT '{}'::jsonb NOT NULL;--> statement-breakpoint
|
|
36
|
+
ALTER TABLE "content_types" ADD COLUMN "requires_approval" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
|
37
|
+
ALTER TABLE "content_approvals" ADD CONSTRAINT "content_approvals_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
38
|
+
ALTER TABLE "content_approvals" ADD CONSTRAINT "content_approvals_content_id_contents_id_fk" FOREIGN KEY ("content_id") REFERENCES "public"."contents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
39
|
+
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
40
|
+
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
41
|
+
CREATE INDEX "content_approvals_content_idx" ON "content_approvals" USING btree ("content_id","requested_at");--> statement-breakpoint
|
|
42
|
+
CREATE INDEX "content_approvals_space_status_idx" ON "content_approvals" USING btree ("space_id","status","requested_at");--> statement-breakpoint
|
|
43
|
+
CREATE INDEX "notifications_user_created_idx" ON "notifications" USING btree ("user_id","created_at");--> statement-breakpoint
|
|
44
|
+
CREATE INDEX "notifications_user_unread_idx" ON "notifications" USING btree ("user_id","read_at");--> statement-breakpoint
|
|
45
|
+
CREATE INDEX "notifications_target_idx" ON "notifications" USING btree ("target_kind","target_id");
|