@southwind-ai/database 0.1.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.
@@ -0,0 +1,169 @@
1
+ import {
2
+ check,
3
+ index,
4
+ integer,
5
+ jsonb,
6
+ pgTable,
7
+ primaryKey,
8
+ text,
9
+ timestamp,
10
+ uniqueIndex,
11
+ varchar,
12
+ } from "drizzle-orm/pg-core";
13
+ import { sql } from "drizzle-orm";
14
+ import { auditColumns, idColumn, statusColumn } from "./common.js";
15
+ import type { CredentialState } from "@southwind-ai/shared";
16
+
17
+ export const departments = pgTable(
18
+ "departments",
19
+ {
20
+ id: idColumn().primaryKey(),
21
+ parentId: varchar("parent_id", { length: 64 }),
22
+ name: varchar("name", { length: 120 }).notNull(),
23
+ code: varchar("code", { length: 80 }).notNull(),
24
+ order: integer("order").notNull().default(0),
25
+ status: statusColumn().notNull(),
26
+ ...auditColumns,
27
+ },
28
+ (table) => [
29
+ uniqueIndex("uk_departments_code").on(table.code),
30
+ index("idx_departments_parent").on(table.parentId),
31
+ ],
32
+ );
33
+
34
+ export const users = pgTable(
35
+ "users",
36
+ {
37
+ id: idColumn().primaryKey(),
38
+ username: varchar("username", { length: 80 }).notNull(),
39
+ displayName: varchar("display_name", { length: 120 }).notNull(),
40
+ passwordHash: text("password_hash"),
41
+ credentialState: varchar("credential_state", { length: 40 })
42
+ .$type<CredentialState>()
43
+ .notNull()
44
+ .default("active"),
45
+ passwordChangedAt: timestamp("password_changed_at", {
46
+ withTimezone: true,
47
+ }),
48
+ email: varchar("email", { length: 160 }),
49
+ phone: varchar("phone", { length: 32 }),
50
+ departmentId: varchar("department_id", { length: 64 }),
51
+ roleIds: jsonb("role_ids").$type<string[]>(),
52
+ postIds: jsonb("post_ids").$type<string[]>(),
53
+ status: statusColumn().notNull(),
54
+ ...auditColumns,
55
+ },
56
+ (table) => [
57
+ uniqueIndex("uk_users_username").on(table.username),
58
+ uniqueIndex("uk_users_phone").on(table.phone),
59
+ index("idx_users_department").on(table.departmentId),
60
+ index("idx_users_status").on(table.status),
61
+ check(
62
+ "users_credential_state_check",
63
+ sql`${table.credentialState} in ('active', 'must-change-password')`,
64
+ ),
65
+ ],
66
+ );
67
+
68
+ export const roles = pgTable(
69
+ "roles",
70
+ {
71
+ id: idColumn().primaryKey(),
72
+ code: varchar("code", { length: 80 }).notNull(),
73
+ name: varchar("name", { length: 120 }).notNull(),
74
+ dataScope: varchar("data_scope", { length: 40 }).notNull(),
75
+ permissionKeys: jsonb("permission_keys").$type<string[]>(),
76
+ menuKeys: jsonb("menu_keys").$type<string[]>(),
77
+ departmentIds: jsonb("department_ids").$type<string[]>(),
78
+ status: statusColumn().notNull(),
79
+ ...auditColumns,
80
+ },
81
+ (table) => [
82
+ uniqueIndex("uk_roles_code").on(table.code),
83
+ index("idx_roles_status").on(table.status),
84
+ ],
85
+ );
86
+
87
+ export const userRoles = pgTable(
88
+ "user_roles",
89
+ {
90
+ userId: varchar("user_id", { length: 64 }).notNull(),
91
+ roleId: varchar("role_id", { length: 64 }).notNull(),
92
+ },
93
+ (table) => [primaryKey({ columns: [table.userId, table.roleId] })],
94
+ );
95
+
96
+ export const rolePermissions = pgTable(
97
+ "role_permissions",
98
+ {
99
+ roleId: varchar("role_id", { length: 64 }).notNull(),
100
+ permissionKey: varchar("permission_key", { length: 160 }).notNull(),
101
+ },
102
+ (table) => [primaryKey({ columns: [table.roleId, table.permissionKey] })],
103
+ );
104
+
105
+ export const serverSessions = pgTable(
106
+ "server_sessions",
107
+ {
108
+ id: idColumn().primaryKey(),
109
+ userId: varchar("user_id", { length: 64 }).notNull(),
110
+ tokenHash: varchar("token_hash", { length: 128 }).notNull(),
111
+ status: statusColumn().notNull().default("active"),
112
+ issuedAt: timestamp("issued_at", { withTimezone: true })
113
+ .notNull()
114
+ .defaultNow(),
115
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
116
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
117
+ lastSeenAt: timestamp("last_seen_at", { withTimezone: true }),
118
+ ip: varchar("ip", { length: 80 }),
119
+ userAgent: text("user_agent"),
120
+ },
121
+ (table) => [
122
+ uniqueIndex("uk_server_sessions_token_hash").on(table.tokenHash),
123
+ index("idx_server_sessions_user").on(table.userId),
124
+ index("idx_server_sessions_status").on(table.status),
125
+ index("idx_server_sessions_expires").on(table.expiresAt),
126
+ ],
127
+ );
128
+
129
+ export const menus = pgTable(
130
+ "menus",
131
+ {
132
+ id: idColumn().primaryKey(),
133
+ key: varchar("key", { length: 160 }).notNull(),
134
+ title: varchar("title", { length: 120 }).notNull(),
135
+ type: varchar("type", { length: 32 }).notNull(),
136
+ order: integer("order").notNull().default(0),
137
+ visible: varchar("visible", { length: 32 }).notNull().default("visible"),
138
+ path: varchar("path", { length: 240 }),
139
+ component: varchar("component", { length: 240 }),
140
+ icon: varchar("icon", { length: 80 }),
141
+ permissionKey: varchar("permission_key", { length: 160 }),
142
+ featureKey: varchar("feature_key", { length: 160 }),
143
+ parentKey: varchar("parent_key", { length: 160 }),
144
+ status: statusColumn().notNull(),
145
+ ...auditColumns,
146
+ },
147
+ (table) => [
148
+ uniqueIndex("uk_menus_key").on(table.key),
149
+ index("idx_menus_parent").on(table.parentKey),
150
+ index("idx_menus_status").on(table.status),
151
+ ],
152
+ );
153
+
154
+ export const dictionaries = pgTable(
155
+ "dictionaries",
156
+ {
157
+ id: idColumn().primaryKey(),
158
+ code: varchar("code", { length: 100 }).notNull(),
159
+ name: varchar("name", { length: 120 }).notNull(),
160
+ description: text("description"),
161
+ items: jsonb("items").$type<Array<Record<string, unknown>>>().notNull(),
162
+ status: statusColumn().notNull(),
163
+ ...auditColumns,
164
+ },
165
+ (table) => [
166
+ uniqueIndex("uk_dictionaries_code").on(table.code),
167
+ index("idx_dictionaries_status").on(table.status),
168
+ ],
169
+ );
@@ -0,0 +1,33 @@
1
+ // @windy-module system.bulk-data begin
2
+ export * from "./bulk-data.js";
3
+ // @windy-module system.bulk-data end
4
+ // @windy-module system.scheduler begin
5
+ export * from "./durable-jobs.js";
6
+ // @windy-module system.scheduler end
7
+ // @windy-module system.storage begin
8
+ export * from "./file-storage.js";
9
+ // @windy-module system.storage end
10
+ export * from "./governance.js";
11
+ export type { GovernedExportScopeSnapshot } from "./common.js";
12
+ // @windy-module system.data-governance begin
13
+ export * from "./governed-exports.js";
14
+ // @windy-module system.data-governance end
15
+ export * from "./identity.js";
16
+ // @windy-module system.license begin
17
+ export * from "./license-legacy.js";
18
+ export * from "./license-v1.js";
19
+ // @windy-module system.license end
20
+ export * from "./migration-history.js";
21
+ // @windy-module system.notification begin
22
+ export * from "./notifications.js";
23
+ // @windy-module system.notification end
24
+ export * from "./platform-settings.js";
25
+ // @windy-module system.scheduler begin
26
+ export * from "./scheduler.js";
27
+ // @windy-module system.scheduler end
28
+ // @windy-module work-order begin
29
+ export * from "./work-orders.js";
30
+ // @windy-module work-order end
31
+ // @windy-module system.workflow begin
32
+ export * from "./workflow.js";
33
+ // @windy-module system.workflow end
@@ -0,0 +1,93 @@
1
+ import {
2
+ index,
3
+ jsonb,
4
+ pgTable,
5
+ text,
6
+ timestamp,
7
+ uniqueIndex,
8
+ varchar,
9
+ } from "drizzle-orm/pg-core";
10
+ import { auditColumns, idColumn, statusColumn } from "./common.js";
11
+
12
+ /** 原型数据保留区;禁止将这些行解释为正式 V1 License。 */
13
+ export const legacyLicenseProjects = pgTable(
14
+ "legacy_license_projects",
15
+ {
16
+ id: idColumn().primaryKey(),
17
+ code: varchar("code", { length: 80 }).notNull(),
18
+ name: varchar("name", { length: 160 }).notNull(),
19
+ customerName: varchar("customer_name", { length: 160 }).notNull(),
20
+ contactName: varchar("contact_name", { length: 120 }),
21
+ notes: text("notes"),
22
+ status: statusColumn().notNull(),
23
+ ...auditColumns,
24
+ },
25
+ (table) => [
26
+ uniqueIndex("uk_legacy_license_projects_code").on(table.code),
27
+ index("idx_legacy_license_projects_status").on(table.status),
28
+ ],
29
+ );
30
+
31
+ export const legacyLicenseIssueRecords = pgTable(
32
+ "legacy_license_issue_records",
33
+ {
34
+ id: idColumn().primaryKey(),
35
+ projectId: varchar("project_id", { length: 64 }).notNull(),
36
+ operatorId: varchar("operator_id", { length: 64 }).notNull(),
37
+ subject: varchar("subject", { length: 160 }).notNull(),
38
+ machineCode: varchar("machine_code", { length: 256 }).notNull(),
39
+ edition: varchar("edition", { length: 40 }).notNull(),
40
+ publicKeyId: varchar("public_key_id", { length: 160 }).notNull(),
41
+ licenseId: varchar("license_id", { length: 64 }).notNull(),
42
+ limits: jsonb("limits").$type<Record<string, unknown>>().notNull(),
43
+ modules: jsonb("modules").$type<Record<string, unknown>[]>().notNull(),
44
+ claims: jsonb("claims").$type<Record<string, unknown>>().notNull(),
45
+ signature: text("signature").notNull(),
46
+ signedPayload: text("signed_payload"),
47
+ algorithm: varchar("algorithm", { length: 40 })
48
+ .notNull()
49
+ .default("ED25519"),
50
+ payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
51
+ status: statusColumn().notNull(),
52
+ issuedAt: timestamp("issued_at", { withTimezone: true }).notNull(),
53
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
54
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
55
+ revokedBy: varchar("revoked_by", { length: 64 }),
56
+ revokeReason: text("revoke_reason"),
57
+ },
58
+ (table) => [
59
+ uniqueIndex("uk_legacy_license_issue_license_id").on(table.licenseId),
60
+ index("idx_legacy_license_issue_project").on(table.projectId),
61
+ index("idx_legacy_license_issue_operator").on(table.operatorId),
62
+ index("idx_legacy_license_issue_machine").on(table.machineCode),
63
+ index("idx_legacy_license_issue_key").on(table.publicKeyId),
64
+ index("idx_legacy_license_issue_status").on(table.status),
65
+ index("idx_legacy_license_issue_issued").on(table.issuedAt),
66
+ ],
67
+ );
68
+
69
+ export const legacyLicenses = pgTable(
70
+ "legacy_licenses",
71
+ {
72
+ id: idColumn().primaryKey(),
73
+ licenseKey: varchar("license_key", { length: 160 }).notNull(),
74
+ subject: varchar("subject", { length: 160 }).notNull(),
75
+ machineCode: varchar("machine_code", { length: 256 }).notNull(),
76
+ publicKeyId: varchar("public_key_id", { length: 160 }).notNull(),
77
+ edition: varchar("edition", { length: 40 }).notNull().default("enterprise"),
78
+ modules: jsonb("modules").$type<string[]>().notNull().default([]),
79
+ payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
80
+ signedPayload: text("signed_payload"),
81
+ signature: text("signature").notNull(),
82
+ status: statusColumn().notNull(),
83
+ issuedAt: timestamp("issued_at", { withTimezone: true }).notNull(),
84
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
85
+ revokedAt: timestamp("revoked_at", { withTimezone: true }),
86
+ ...auditColumns,
87
+ },
88
+ (table) => [
89
+ index("idx_legacy_licenses_machine").on(table.machineCode),
90
+ index("idx_legacy_licenses_key").on(table.licenseKey),
91
+ index("idx_legacy_licenses_status").on(table.status),
92
+ ],
93
+ );
@@ -0,0 +1,246 @@
1
+ import type { LicenseV1Claims, LicenseVersionKey } from "@southwind-ai/shared";
2
+ import { sql } from "drizzle-orm";
3
+ import {
4
+ check,
5
+ index,
6
+ integer,
7
+ jsonb,
8
+ pgTable,
9
+ text,
10
+ timestamp,
11
+ uniqueIndex,
12
+ varchar,
13
+ } from "drizzle-orm/pg-core";
14
+ import { idColumn, statusColumn } from "./common.js";
15
+
16
+ const archiveColumns = {
17
+ status: statusColumn().notNull().default("active"),
18
+ createdAt: timestamp("created_at", { withTimezone: true })
19
+ .notNull()
20
+ .defaultNow(),
21
+ createdBy: varchar("created_by", { length: 64 }).notNull(),
22
+ updatedAt: timestamp("updated_at", { withTimezone: true })
23
+ .notNull()
24
+ .defaultNow(),
25
+ updatedBy: varchar("updated_by", { length: 64 }).notNull(),
26
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
27
+ archivedBy: varchar("archived_by", { length: 64 }),
28
+ };
29
+
30
+ export const licenseCustomers = pgTable(
31
+ "license_customers",
32
+ {
33
+ id: idColumn().primaryKey(),
34
+ code: varchar("code", { length: 80 }).notNull(),
35
+ name: varchar("name", { length: 160 }).notNull(),
36
+ ...archiveColumns,
37
+ },
38
+ (table) => [
39
+ uniqueIndex("uk_license_customers_code").on(table.code),
40
+ index("idx_license_customers_status").on(table.status),
41
+ ],
42
+ );
43
+
44
+ export const licenseProjects = pgTable(
45
+ "license_projects",
46
+ {
47
+ id: idColumn().primaryKey(),
48
+ customerId: varchar("customer_id", { length: 64 })
49
+ .notNull()
50
+ .references(() => licenseCustomers.id, { onDelete: "restrict" }),
51
+ code: varchar("code", { length: 80 }).notNull(),
52
+ name: varchar("name", { length: 160 }).notNull(),
53
+ ...archiveColumns,
54
+ },
55
+ (table) => [
56
+ uniqueIndex("uk_license_projects_customer_code").on(
57
+ table.customerId,
58
+ table.code,
59
+ ),
60
+ index("idx_license_projects_customer").on(table.customerId),
61
+ index("idx_license_projects_status").on(table.status),
62
+ ],
63
+ );
64
+
65
+ export const licenseVersions = pgTable(
66
+ "license_versions",
67
+ {
68
+ id: idColumn().primaryKey(),
69
+ projectId: varchar("project_id", { length: 64 })
70
+ .notNull()
71
+ .references(() => licenseProjects.id, { onDelete: "restrict" }),
72
+ key: varchar("key", { length: 80 }).notNull(),
73
+ name: varchar("name", { length: 120 }).notNull(),
74
+ description: text("description"),
75
+ ...archiveColumns,
76
+ },
77
+ (table) => [
78
+ uniqueIndex("uk_license_versions_project_key").on(
79
+ table.projectId,
80
+ table.key,
81
+ ),
82
+ index("idx_license_versions_project").on(table.projectId),
83
+ index("idx_license_versions_status").on(table.status),
84
+ ],
85
+ );
86
+
87
+ export const licenseContractVersions = pgTable(
88
+ "license_contract_versions",
89
+ {
90
+ id: idColumn().primaryKey(),
91
+ projectId: varchar("project_id", { length: 64 })
92
+ .notNull()
93
+ .references(() => licenseProjects.id, { onDelete: "restrict" }),
94
+ contractNumber: varchar("contract_number", { length: 120 }).notNull(),
95
+ version: varchar("version", { length: 40 }).notNull(),
96
+ licenseVersionId: varchar("license_version_id", { length: 64 })
97
+ .notNull()
98
+ .references(() => licenseVersions.id, { onDelete: "restrict" }),
99
+ ...archiveColumns,
100
+ },
101
+ (table) => [
102
+ uniqueIndex("uk_license_contract_project_number_version").on(
103
+ table.projectId,
104
+ table.contractNumber,
105
+ table.version,
106
+ ),
107
+ index("idx_license_contract_project").on(table.projectId),
108
+ index("idx_license_contract_status").on(table.status),
109
+ ],
110
+ );
111
+
112
+ export const licenseNodes = pgTable(
113
+ "license_nodes",
114
+ {
115
+ id: idColumn().primaryKey(),
116
+ projectId: varchar("project_id", { length: 64 })
117
+ .notNull()
118
+ .references(() => licenseProjects.id, { onDelete: "restrict" }),
119
+ code: varchar("code", { length: 80 }).notNull(),
120
+ name: varchar("name", { length: 160 }).notNull(),
121
+ purpose: varchar("purpose", { length: 160 }),
122
+ machineCode: varchar("machine_code", { length: 256 }).notNull(),
123
+ ...archiveColumns,
124
+ },
125
+ (table) => [
126
+ uniqueIndex("uk_license_nodes_project_code").on(
127
+ table.projectId,
128
+ table.code,
129
+ ),
130
+ uniqueIndex("uk_license_nodes_machine").on(table.machineCode),
131
+ index("idx_license_nodes_project").on(table.projectId),
132
+ index("idx_license_nodes_status").on(table.status),
133
+ ],
134
+ );
135
+
136
+ export const licenses = pgTable(
137
+ "licenses",
138
+ {
139
+ id: idColumn().primaryKey(),
140
+ formatVersion: integer("format_version").notNull().default(1),
141
+ productCode: varchar("product_code", { length: 120 }).notNull(),
142
+ customerId: varchar("customer_id", { length: 64 })
143
+ .notNull()
144
+ .references(() => licenseCustomers.id, { onDelete: "restrict" }),
145
+ projectId: varchar("project_id", { length: 64 })
146
+ .notNull()
147
+ .references(() => licenseProjects.id, { onDelete: "restrict" }),
148
+ contractVersionId: varchar("contract_version_id", { length: 64 })
149
+ .notNull()
150
+ .references(() => licenseContractVersions.id, { onDelete: "restrict" }),
151
+ nodeId: varchar("node_id", { length: 64 })
152
+ .notNull()
153
+ .references(() => licenseNodes.id, { onDelete: "restrict" }),
154
+ machineCode: varchar("machine_code", { length: 256 }).notNull(),
155
+ licenseVersionKey: varchar("license_version_key", {
156
+ length: 80,
157
+ })
158
+ .$type<LicenseVersionKey>()
159
+ .notNull(),
160
+ licenseVersionName: varchar("license_version_name", {
161
+ length: 120,
162
+ }).notNull(),
163
+ claims: jsonb("claims").$type<LicenseV1Claims>().notNull(),
164
+ signedPayload: text("signed_payload").notNull(),
165
+ payloadDigest: varchar("payload_digest", { length: 128 }).notNull(),
166
+ publicKeyId: varchar("public_key_id", { length: 160 }).notNull(),
167
+ targetSoftwareVersion: varchar("target_software_version", { length: 80 }),
168
+ algorithm: varchar("algorithm", { length: 40 })
169
+ .notNull()
170
+ .default("ED25519"),
171
+ signature: text("signature").notNull(),
172
+ issuedAt: timestamp("issued_at", { withTimezone: true }).notNull(),
173
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
174
+ issuedBy: varchar("issued_by", { length: 64 }).notNull(),
175
+ createdAt: timestamp("created_at", { withTimezone: true })
176
+ .notNull()
177
+ .defaultNow(),
178
+ },
179
+ (table) => [
180
+ check("ck_licenses_format_v1", sql`${table.formatVersion} = 1`),
181
+ check(
182
+ "ck_licenses_expiry_after_issue",
183
+ sql`${table.expiresAt} > ${table.issuedAt}`,
184
+ ),
185
+ index("idx_license_v1_customer").on(table.customerId),
186
+ index("idx_license_v1_project").on(table.projectId),
187
+ index("idx_license_v1_contract").on(table.contractVersionId),
188
+ index("idx_license_v1_node").on(table.nodeId),
189
+ index("idx_license_v1_machine_issued").on(
190
+ table.machineCode,
191
+ table.issuedAt,
192
+ ),
193
+ index("idx_license_v1_key").on(table.publicKeyId),
194
+ index("idx_license_v1_expires").on(table.expiresAt),
195
+ ],
196
+ );
197
+
198
+ export const licenseReplacements = pgTable(
199
+ "license_replacements",
200
+ {
201
+ id: idColumn().primaryKey(),
202
+ replacedLicenseId: varchar("replaced_license_id", { length: 64 })
203
+ .notNull()
204
+ .references(() => licenses.id, { onDelete: "restrict" }),
205
+ replacementLicenseId: varchar("replacement_license_id", { length: 64 })
206
+ .notNull()
207
+ .references(() => licenses.id, { onDelete: "restrict" }),
208
+ reason: text("reason"),
209
+ createdAt: timestamp("created_at", { withTimezone: true })
210
+ .notNull()
211
+ .defaultNow(),
212
+ createdBy: varchar("created_by", { length: 64 }).notNull(),
213
+ },
214
+ (table) => [
215
+ uniqueIndex("uk_license_replacements_old").on(table.replacedLicenseId),
216
+ uniqueIndex("uk_license_replacements_new").on(table.replacementLicenseId),
217
+ check(
218
+ "ck_license_replacements_distinct",
219
+ sql`${table.replacedLicenseId} <> ${table.replacementLicenseId}`,
220
+ ),
221
+ ],
222
+ );
223
+
224
+ export const licenseEvents = pgTable(
225
+ "license_events",
226
+ {
227
+ id: idColumn().primaryKey(),
228
+ licenseId: varchar("license_id", { length: 64 })
229
+ .notNull()
230
+ .references(() => licenses.id, { onDelete: "restrict" }),
231
+ type: varchar("type", { length: 80 }).notNull(),
232
+ actorId: varchar("actor_id", { length: 64 }).notNull(),
233
+ occurredAt: timestamp("occurred_at", { withTimezone: true })
234
+ .notNull()
235
+ .defaultNow(),
236
+ payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
237
+ },
238
+ (table) => [
239
+ index("idx_license_events_license_time").on(
240
+ table.licenseId,
241
+ table.occurredAt,
242
+ ),
243
+ index("idx_license_events_type_time").on(table.type, table.occurredAt),
244
+ index("idx_license_events_actor_time").on(table.actorId, table.occurredAt),
245
+ ],
246
+ );
@@ -0,0 +1,30 @@
1
+ import {
2
+ index,
3
+ jsonb,
4
+ pgTable,
5
+ text,
6
+ timestamp,
7
+ varchar,
8
+ } from "drizzle-orm/pg-core";
9
+
10
+ export const windyMigrationHistory = pgTable(
11
+ "windy_migration_history",
12
+ {
13
+ id: varchar("id", { length: 160 }).primaryKey(),
14
+ module: varchar("module", { length: 96 }).notNull(),
15
+ direction: varchar("direction", { length: 16 }).notNull(),
16
+ status: varchar("status", { length: 16 }).notNull(),
17
+ checksum: varchar("checksum", { length: 128 }),
18
+ statements: jsonb("statements").$type<string[]>(),
19
+ error: text("error"),
20
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull(),
21
+ finishedAt: timestamp("finished_at", { withTimezone: true }).notNull(),
22
+ createdAt: timestamp("created_at", { withTimezone: true })
23
+ .notNull()
24
+ .defaultNow(),
25
+ },
26
+ (table) => [
27
+ index("idx_windy_migration_history_module").on(table.module),
28
+ index("idx_windy_migration_history_status").on(table.status),
29
+ ],
30
+ );
@@ -0,0 +1,60 @@
1
+ import {
2
+ index,
3
+ pgTable,
4
+ primaryKey,
5
+ text,
6
+ timestamp,
7
+ jsonb,
8
+ uniqueIndex,
9
+ varchar,
10
+ } from "drizzle-orm/pg-core";
11
+ import type { TargetRef } from "@southwind-ai/shared";
12
+ import { auditColumns, idColumn, statusColumn } from "./common.js";
13
+ import { users } from "./identity.js";
14
+
15
+ export const platformNotifications = pgTable(
16
+ "platform_notifications",
17
+ {
18
+ id: idColumn().primaryKey(),
19
+ title: varchar("title", { length: 120 }).notNull(),
20
+ summary: varchar("summary", { length: 240 }).notNull(),
21
+ content: text("content").notNull(),
22
+ category: varchar("category", { length: 40 }).notNull(),
23
+ status: statusColumn().notNull(),
24
+ publishedAt: timestamp("published_at", { withTimezone: true }).notNull(),
25
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
26
+ archivedBy: varchar("archived_by", { length: 64 }),
27
+ target: jsonb("target").$type<TargetRef>(),
28
+ recipientUserId: varchar("recipient_user_id", { length: 64 }),
29
+ recipientRoleCodes: jsonb("recipient_role_codes")
30
+ .$type<string[]>()
31
+ .notNull()
32
+ .default([]),
33
+ sourceKey: varchar("source_key", { length: 180 }),
34
+ ...auditColumns,
35
+ },
36
+ (table) => [
37
+ index("idx_platform_notifications_status_published").on(
38
+ table.status,
39
+ table.publishedAt,
40
+ ),
41
+ uniqueIndex("uk_platform_notifications_source_key").on(table.sourceKey),
42
+ ],
43
+ );
44
+
45
+ export const notificationReads = pgTable(
46
+ "notification_reads",
47
+ {
48
+ notificationId: varchar("notification_id", { length: 64 })
49
+ .notNull()
50
+ .references(() => platformNotifications.id, { onDelete: "restrict" }),
51
+ userId: varchar("user_id", { length: 64 })
52
+ .notNull()
53
+ .references(() => users.id, { onDelete: "restrict" }),
54
+ readAt: timestamp("read_at", { withTimezone: true }).notNull().defaultNow(),
55
+ },
56
+ (table) => [
57
+ primaryKey({ columns: [table.notificationId, table.userId] }),
58
+ index("idx_notification_reads_user").on(table.userId, table.readAt),
59
+ ],
60
+ );