@manablox/db 0.2.0 → 0.4.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-BLAkQMJT.d.ts +877 -0
- package/dist/index-DrNMGM9N.d.ts +5193 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +60 -0
- package/dist/repositories-pz4NeWaF.js +1928 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/schema-Dm3RcBst.js +648 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +2 -0
- package/dist/testing.d.ts +77 -0
- package/dist/testing.js +217 -0
- 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 +18 -10
- package/drizzle.config.ts +0 -11
- package/src/bootstrap.ts +0 -13
- package/src/cli/create-db.ts +0 -30
- package/src/cli/migrate.ts +0 -17
- package/src/client.ts +0 -44
- package/src/columns.ts +0 -39
- package/src/errors.ts +0 -50
- package/src/index.ts +0 -19
- package/src/migrate.ts +0 -21
- package/src/pagination.ts +0 -52
- package/src/query.ts +0 -213
- package/src/repositories/asset-usage.ts +0 -166
- package/src/repositories/asset.ts +0 -181
- package/src/repositories/content-type.ts +0 -116
- package/src/repositories/content.ts +0 -811
- package/src/repositories/index.ts +0 -40
- package/src/repositories/menu.ts +0 -235
- package/src/repositories/role.ts +0 -85
- package/src/repositories/space.ts +0 -83
- package/src/repositories/user.ts +0 -280
- package/src/repositories/webhook.ts +0 -46
- package/src/repositories/workflow.ts +0 -306
- package/src/schema/assets.ts +0 -108
- package/src/schema/auth.ts +0 -166
- package/src/schema/content-types.ts +0 -31
- package/src/schema/content.ts +0 -133
- package/src/schema/index.ts +0 -38
- package/src/schema/menus.ts +0 -61
- package/src/schema/relations.ts +0 -64
- package/src/schema/spaces.ts +0 -20
- package/src/schema/webhooks.ts +0 -46
- package/src/schema/workflows.ts +0 -92
- package/src/testing-fixtures.ts +0 -139
- package/src/testing.ts +0 -105
- package/test/asset-usage.test.ts +0 -101
- package/test/menu.test.ts +0 -126
- package/test/publish.test.ts +0 -130
- package/test/query.test.ts +0 -170
- package/test/role.test.ts +0 -81
- package/test/tree.test.ts +0 -188
- package/test/user.test.ts +0 -126
- package/test/webhook.test.ts +0 -48
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -10
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { it as DatabaseHandle, t as Repositories } from "./index-BLAkQMJT.js";
|
|
2
|
+
import { ContentTypeDefinition, ContentTypeRegistry } from "@manablox/core";
|
|
3
|
+
//#region src/testing-fixtures.d.ts
|
|
4
|
+
export declare const stringField: import("@manablox/core").FieldTypeDefinition<Record<string, unknown>, string>;
|
|
5
|
+
export declare const numberField: import("@manablox/core").FieldTypeDefinition<Record<string, unknown>, number>;
|
|
6
|
+
export interface RepositoryTestContext {
|
|
7
|
+
handle: DatabaseHandle;
|
|
8
|
+
/** Every statement sent to Postgres, so a test can assert what a call cost. */
|
|
9
|
+
queries: string[];
|
|
10
|
+
repos: Repositories;
|
|
11
|
+
registry: ContentTypeRegistry;
|
|
12
|
+
types: {
|
|
13
|
+
page: ContentTypeDefinition;
|
|
14
|
+
folder: ContentTypeDefinition;
|
|
15
|
+
};
|
|
16
|
+
spaceId: string;
|
|
17
|
+
close: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** Spins up an isolated database per suite, migrated from the real migration files. */
|
|
20
|
+
export declare function createRepositoryContext(name: string): Promise<RepositoryTestContext>;
|
|
21
|
+
/** Convenience creator so tests read as tree shapes rather than field soup. */
|
|
22
|
+
export declare function makeNode(ctx: RepositoryTestContext, options: {
|
|
23
|
+
title: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
parentId?: string | null;
|
|
26
|
+
type?: 'page' | 'folder';
|
|
27
|
+
fields?: Record<string, unknown>;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
createdBy: string | null;
|
|
31
|
+
fields: Record<string, unknown>;
|
|
32
|
+
id: string;
|
|
33
|
+
locale: string;
|
|
34
|
+
localizationId: string;
|
|
35
|
+
parentId: string | null;
|
|
36
|
+
path: string;
|
|
37
|
+
permalink: string | null;
|
|
38
|
+
permalinkPath: string;
|
|
39
|
+
permalinkSegment: string | null;
|
|
40
|
+
position: number;
|
|
41
|
+
publishedAt: Date | null;
|
|
42
|
+
search: string | null;
|
|
43
|
+
searchText: string;
|
|
44
|
+
slug: string;
|
|
45
|
+
spaceId: string;
|
|
46
|
+
status: import("@manablox/core").ContentStatus;
|
|
47
|
+
title: string;
|
|
48
|
+
typeId: string;
|
|
49
|
+
updatedAt: Date;
|
|
50
|
+
updatedBy: string | null;
|
|
51
|
+
version: number;
|
|
52
|
+
}>;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/testing.d.ts
|
|
55
|
+
/**
|
|
56
|
+
* Only ever connected to in order to `create database`: every test file works in its own
|
|
57
|
+
* fresh database and drops it again, so this points at a Postgres *server*, not at data
|
|
58
|
+
* the tests touch. That is why falling back to `DATABASE_URL` is safe — it means the
|
|
59
|
+
* suite runs wherever the app itself runs (a container, CI, the host) without a second
|
|
60
|
+
* variable that has to be kept in step. `TEST_DATABASE_URL` still overrides it.
|
|
61
|
+
*/
|
|
62
|
+
export declare const TEST_ADMIN_URL: string;
|
|
63
|
+
export declare function withDatabase(url: string, name: string): string;
|
|
64
|
+
export interface TestDatabase {
|
|
65
|
+
name: string;
|
|
66
|
+
url: string;
|
|
67
|
+
/** Drops the database. Safe to call once every connection to it is closed. */
|
|
68
|
+
drop: () => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A fresh, migrated database for one test suite, cloned from the template.
|
|
72
|
+
*
|
|
73
|
+
* `prefix` names the suite in `pg_database`, which is what you grep for when a run was
|
|
74
|
+
* interrupted and left a database behind.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createTestDatabase(prefix: string): Promise<TestDatabase>;
|
|
77
|
+
//#endregion
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { b as applyBootstrapSql, t as createRepositories, y as createDatabase } from "./repositories-pz4NeWaF.js";
|
|
2
|
+
import postgres from "postgres";
|
|
3
|
+
import { ContentTypeRegistry, FieldTypeRegistry, defineContentType, defineFieldType } from "@manablox/core";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
9
|
+
//#region src/testing-fixtures.ts
|
|
10
|
+
/**
|
|
11
|
+
* Fixtures for a repository-level test: two plain field types, a `page` and a `folder`
|
|
12
|
+
* content type, a migrated database with one space, and a node maker. Shared through
|
|
13
|
+
* `@manablox/db/testing` so a suite in another package can start from the same world.
|
|
14
|
+
*/
|
|
15
|
+
const anySchema = () => ({ "~standard": {
|
|
16
|
+
version: 1,
|
|
17
|
+
vendor: "test",
|
|
18
|
+
validate: (value) => ({ value })
|
|
19
|
+
} });
|
|
20
|
+
const stringField = defineFieldType({
|
|
21
|
+
name: "string",
|
|
22
|
+
label: "Text",
|
|
23
|
+
settingsSchema: anySchema(),
|
|
24
|
+
valueSchema: () => anySchema(),
|
|
25
|
+
defaultValue: () => "",
|
|
26
|
+
storage: {
|
|
27
|
+
kind: "jsonb",
|
|
28
|
+
index: "btree"
|
|
29
|
+
},
|
|
30
|
+
filters: [
|
|
31
|
+
"eq",
|
|
32
|
+
"neq",
|
|
33
|
+
"contains",
|
|
34
|
+
"startsWith",
|
|
35
|
+
"in",
|
|
36
|
+
"isNull",
|
|
37
|
+
"isNotNull"
|
|
38
|
+
],
|
|
39
|
+
graphql: { type: {
|
|
40
|
+
kind: "scalar",
|
|
41
|
+
name: "String"
|
|
42
|
+
} },
|
|
43
|
+
search: (value) => typeof value === "string" ? value : null,
|
|
44
|
+
admin: { input: "string" }
|
|
45
|
+
});
|
|
46
|
+
const numberField = defineFieldType({
|
|
47
|
+
name: "number",
|
|
48
|
+
label: "Number",
|
|
49
|
+
settingsSchema: anySchema(),
|
|
50
|
+
valueSchema: () => anySchema(),
|
|
51
|
+
defaultValue: () => 0,
|
|
52
|
+
storage: {
|
|
53
|
+
kind: "jsonb",
|
|
54
|
+
index: "btree"
|
|
55
|
+
},
|
|
56
|
+
filters: [
|
|
57
|
+
"eq",
|
|
58
|
+
"lt",
|
|
59
|
+
"lte",
|
|
60
|
+
"gt",
|
|
61
|
+
"gte"
|
|
62
|
+
],
|
|
63
|
+
graphql: { type: {
|
|
64
|
+
kind: "scalar",
|
|
65
|
+
name: "Float"
|
|
66
|
+
} },
|
|
67
|
+
admin: { input: "number" }
|
|
68
|
+
});
|
|
69
|
+
/** Spins up an isolated database per suite, migrated from the real migration files. */
|
|
70
|
+
async function createRepositoryContext(name) {
|
|
71
|
+
const database = await createTestDatabase(name);
|
|
72
|
+
const queries = [];
|
|
73
|
+
const handle = createDatabase({
|
|
74
|
+
url: database.url,
|
|
75
|
+
max: 4
|
|
76
|
+
}, { onQuery: (query) => queries.push(query) });
|
|
77
|
+
const fieldTypes = new FieldTypeRegistry();
|
|
78
|
+
fieldTypes.register(stringField);
|
|
79
|
+
fieldTypes.register(numberField);
|
|
80
|
+
const page = defineContentType({
|
|
81
|
+
name: "page",
|
|
82
|
+
fields: [{
|
|
83
|
+
name: "body",
|
|
84
|
+
type: "string"
|
|
85
|
+
}, {
|
|
86
|
+
name: "weight",
|
|
87
|
+
type: "number"
|
|
88
|
+
}]
|
|
89
|
+
});
|
|
90
|
+
const folder = defineContentType({
|
|
91
|
+
name: "folder",
|
|
92
|
+
hasSlug: false,
|
|
93
|
+
fields: [{
|
|
94
|
+
name: "note",
|
|
95
|
+
type: "string"
|
|
96
|
+
}]
|
|
97
|
+
});
|
|
98
|
+
const registry = new ContentTypeRegistry(fieldTypes);
|
|
99
|
+
registry.setAll([page, folder]);
|
|
100
|
+
registry.validate();
|
|
101
|
+
const repos = createRepositories(handle.db, registry);
|
|
102
|
+
const space = await repos.spaces.create({
|
|
103
|
+
name: "Test",
|
|
104
|
+
machineName: "test",
|
|
105
|
+
url: "http://localhost:3002"
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
handle,
|
|
109
|
+
queries,
|
|
110
|
+
repos,
|
|
111
|
+
registry,
|
|
112
|
+
types: {
|
|
113
|
+
page,
|
|
114
|
+
folder
|
|
115
|
+
},
|
|
116
|
+
spaceId: space.id,
|
|
117
|
+
close: async () => {
|
|
118
|
+
await handle.close();
|
|
119
|
+
await database.drop();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Convenience creator so tests read as tree shapes rather than field soup. */
|
|
124
|
+
async function makeNode(ctx, options) {
|
|
125
|
+
const type = ctx.types[options.type ?? "page"];
|
|
126
|
+
const searchText = Object.values(options.fields ?? {}).filter((value) => typeof value === "string").join(" ");
|
|
127
|
+
return ctx.repos.content.create({
|
|
128
|
+
searchText,
|
|
129
|
+
spaceId: ctx.spaceId,
|
|
130
|
+
typeId: type.id,
|
|
131
|
+
locale: "en",
|
|
132
|
+
parentId: options.parentId ?? null,
|
|
133
|
+
title: options.title,
|
|
134
|
+
slug: options.slug,
|
|
135
|
+
fields: options.fields ?? {},
|
|
136
|
+
hasSlug: type.hasSlug
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/testing.ts
|
|
141
|
+
const MIGRATIONS = resolve(dirname(fileURLToPath(import.meta.url)), "../migrations");
|
|
142
|
+
/**
|
|
143
|
+
* Only ever connected to in order to `create database`: every test file works in its own
|
|
144
|
+
* fresh database and drops it again, so this points at a Postgres *server*, not at data
|
|
145
|
+
* the tests touch. That is why falling back to `DATABASE_URL` is safe — it means the
|
|
146
|
+
* suite runs wherever the app itself runs (a container, CI, the host) without a second
|
|
147
|
+
* variable that has to be kept in step. `TEST_DATABASE_URL` still overrides it.
|
|
148
|
+
*/
|
|
149
|
+
const TEST_ADMIN_URL = process.env.TEST_DATABASE_URL ?? process.env.DATABASE_URL ?? "postgres://manablox:manablox@localhost:5432/manablox";
|
|
150
|
+
/** A fingerprint of the migration files, so a schema change gets a fresh template. */
|
|
151
|
+
function migrationsFingerprint() {
|
|
152
|
+
const hash = createHash("sha1");
|
|
153
|
+
for (const file of readdirSync(MIGRATIONS).sort()) if (file.endsWith(".sql")) hash.update(readFileSync(join(MIGRATIONS, file)));
|
|
154
|
+
return hash.digest("hex").slice(0, 10);
|
|
155
|
+
}
|
|
156
|
+
const TEMPLATE = `manablox_test_template_${migrationsFingerprint()}`;
|
|
157
|
+
/**
|
|
158
|
+
* Makes sure the migrated template exists, once per server.
|
|
159
|
+
*
|
|
160
|
+
* Migrating takes a second or two; `create database … template …` takes milliseconds,
|
|
161
|
+
* so every suite clones the template instead of migrating from scratch. Suites run in
|
|
162
|
+
* parallel across processes, so the check-and-create is serialised on a session-level
|
|
163
|
+
* advisory lock — the second process finds the template already there.
|
|
164
|
+
*/
|
|
165
|
+
async function ensureTemplate(admin) {
|
|
166
|
+
await admin.unsafe(`select pg_advisory_lock(hashtext('${TEMPLATE}'))`);
|
|
167
|
+
try {
|
|
168
|
+
const [row] = await admin.unsafe(`select 1 as found from pg_database where datname = '${TEMPLATE}'`);
|
|
169
|
+
if (row) return;
|
|
170
|
+
await admin.unsafe(`create database "${TEMPLATE}"`);
|
|
171
|
+
const handle = createDatabase({
|
|
172
|
+
url: withDatabase(TEST_ADMIN_URL, TEMPLATE),
|
|
173
|
+
max: 1
|
|
174
|
+
});
|
|
175
|
+
try {
|
|
176
|
+
await applyBootstrapSql(handle.sql);
|
|
177
|
+
await migrate(handle.db, { migrationsFolder: MIGRATIONS });
|
|
178
|
+
} finally {
|
|
179
|
+
await handle.close();
|
|
180
|
+
}
|
|
181
|
+
} finally {
|
|
182
|
+
await admin.unsafe(`select pg_advisory_unlock(hashtext('${TEMPLATE}'))`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function withDatabase(url, name) {
|
|
186
|
+
return url.replace(/\/[^/?]*(\?.*)?$/, `/${name}$1`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A fresh, migrated database for one test suite, cloned from the template.
|
|
190
|
+
*
|
|
191
|
+
* `prefix` names the suite in `pg_database`, which is what you grep for when a run was
|
|
192
|
+
* interrupted and left a database behind.
|
|
193
|
+
*/
|
|
194
|
+
async function createTestDatabase(prefix) {
|
|
195
|
+
const name = `manablox_test_${prefix}_${Date.now().toString(36)}_${process.pid.toString(36)}`;
|
|
196
|
+
const admin = postgres(TEST_ADMIN_URL, { max: 1 });
|
|
197
|
+
try {
|
|
198
|
+
await ensureTemplate(admin);
|
|
199
|
+
await admin.unsafe(`create database "${name}" template "${TEMPLATE}"`);
|
|
200
|
+
} finally {
|
|
201
|
+
await admin.end();
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
name,
|
|
205
|
+
url: withDatabase(TEST_ADMIN_URL, name),
|
|
206
|
+
drop: async () => {
|
|
207
|
+
const cleanup = postgres(TEST_ADMIN_URL, { max: 1 });
|
|
208
|
+
try {
|
|
209
|
+
await cleanup.unsafe(`drop database if exists "${name}" with (force)`);
|
|
210
|
+
} finally {
|
|
211
|
+
await cleanup.end();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { TEST_ADMIN_URL, createRepositoryContext, createTestDatabase, makeNode, numberField, stringField, withDatabase };
|
|
@@ -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");
|