@southwind-ai/database 0.1.3 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/database",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,7 +15,7 @@
15
15
  "typecheck": "tsc --noEmit --project tsconfig.json"
16
16
  },
17
17
  "dependencies": {
18
- "@southwind-ai/shared": "0.1.2",
18
+ "@southwind-ai/shared": "0.2.0",
19
19
  "drizzle-orm": "^0.45.2"
20
20
  },
21
21
  "exports": {
@@ -27,7 +27,14 @@ export const departments = pgTable(
27
27
  },
28
28
  (table) => [
29
29
  uniqueIndex("uk_departments_code").on(table.code),
30
+ uniqueIndex("uk_departments_single_root")
31
+ .on(sql`((1))`)
32
+ .where(sql`${table.parentId} is null and ${table.deletedAt} is null`),
30
33
  index("idx_departments_parent").on(table.parentId),
34
+ check(
35
+ "departments_parent_not_self",
36
+ sql`${table.parentId} is null or ${table.parentId} <> ${table.id}`,
37
+ ),
31
38
  ],
32
39
  );
33
40
 
@@ -25,9 +25,6 @@ export * from "./platform-settings.js";
25
25
  // @windy-module system.scheduler begin
26
26
  export * from "./scheduler.js";
27
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
28
  // @windy-module system.workflow begin
32
29
  export * from "./workflow.js";
33
30
  // @windy-module system.workflow end
@@ -1,5 +1,7 @@
1
1
  import {
2
+ boolean,
2
3
  index,
4
+ integer,
3
5
  pgTable,
4
6
  primaryKey,
5
7
  text,
@@ -9,6 +11,10 @@ import {
9
11
  varchar,
10
12
  } from "drizzle-orm/pg-core";
11
13
  import type { TargetRef } from "@southwind-ai/shared";
14
+ import type {
15
+ ExternalNotificationChannel,
16
+ NotificationDeliveryStatus,
17
+ } from "@southwind-ai/shared";
12
18
  import { auditColumns, idColumn, statusColumn } from "./common.js";
13
19
  import { users } from "./identity.js";
14
20
 
@@ -22,15 +28,32 @@ export const platformNotifications = pgTable(
22
28
  category: varchar("category", { length: 40 }).notNull(),
23
29
  status: statusColumn().notNull(),
24
30
  publishedAt: timestamp("published_at", { withTimezone: true }).notNull(),
31
+ scheduledFor: timestamp("scheduled_for", { withTimezone: true }),
25
32
  archivedAt: timestamp("archived_at", { withTimezone: true }),
26
33
  archivedBy: varchar("archived_by", { length: 64 }),
27
34
  target: jsonb("target").$type<TargetRef>(),
28
35
  recipientUserId: varchar("recipient_user_id", { length: 64 }),
36
+ recipientUserIds: jsonb("recipient_user_ids")
37
+ .$type<string[]>()
38
+ .notNull()
39
+ .default([]),
29
40
  recipientRoleCodes: jsonb("recipient_role_codes")
30
41
  .$type<string[]>()
31
42
  .notNull()
32
43
  .default([]),
44
+ recipientDepartmentIds: jsonb("recipient_department_ids")
45
+ .$type<string[]>()
46
+ .notNull()
47
+ .default([]),
48
+ includeChildDepartments: boolean("include_child_departments")
49
+ .notNull()
50
+ .default(false),
51
+ deliveryChannels: jsonb("delivery_channels")
52
+ .$type<ExternalNotificationChannel[]>()
53
+ .notNull()
54
+ .default([]),
33
55
  sourceKey: varchar("source_key", { length: 180 }),
56
+ revision: integer("revision").notNull().default(1),
34
57
  ...auditColumns,
35
58
  },
36
59
  (table) => [
@@ -42,6 +65,96 @@ export const platformNotifications = pgTable(
42
65
  ],
43
66
  );
44
67
 
68
+ export const notificationPreferences = pgTable("notification_preferences", {
69
+ userId: varchar("user_id", { length: 64 })
70
+ .primaryKey()
71
+ .references(() => users.id, { onDelete: "restrict" }),
72
+ enabledChannels: jsonb("enabled_channels")
73
+ .$type<ExternalNotificationChannel[]>()
74
+ .notNull()
75
+ .default([]),
76
+ disabledCategories: jsonb("disabled_categories")
77
+ .$type<string[]>()
78
+ .notNull()
79
+ .default([]),
80
+ updatedAt: timestamp("updated_at", { withTimezone: true })
81
+ .notNull()
82
+ .defaultNow(),
83
+ updatedBy: varchar("updated_by", { length: 64 }).notNull(),
84
+ });
85
+
86
+ export const notificationDeliveryOutbox = pgTable(
87
+ "notification_delivery_outbox",
88
+ {
89
+ id: idColumn().primaryKey(),
90
+ notificationId: varchar("notification_id", { length: 64 })
91
+ .notNull()
92
+ .references(() => platformNotifications.id, { onDelete: "restrict" }),
93
+ userId: varchar("user_id", { length: 64 })
94
+ .notNull()
95
+ .references(() => users.id, { onDelete: "restrict" }),
96
+ channel: varchar("channel", { length: 16 })
97
+ .$type<ExternalNotificationChannel>()
98
+ .notNull(),
99
+ status: varchar("status", { length: 16 })
100
+ .$type<NotificationDeliveryStatus>()
101
+ .notNull(),
102
+ subject: varchar("subject", { length: 120 }).notNull(),
103
+ content: text("content").notNull(),
104
+ attemptCount: integer("attempt_count").notNull().default(0),
105
+ nextAttemptAt: timestamp("next_attempt_at", { withTimezone: true })
106
+ .notNull()
107
+ .defaultNow(),
108
+ leaseToken: varchar("lease_token", { length: 64 }),
109
+ leaseExpiresAt: timestamp("lease_expires_at", { withTimezone: true }),
110
+ deliveredAt: timestamp("delivered_at", { withTimezone: true }),
111
+ providerMessageId: varchar("provider_message_id", { length: 180 }),
112
+ lastErrorCode: varchar("last_error_code", { length: 64 }),
113
+ createdAt: timestamp("created_at", { withTimezone: true })
114
+ .notNull()
115
+ .defaultNow(),
116
+ updatedAt: timestamp("updated_at", { withTimezone: true })
117
+ .notNull()
118
+ .defaultNow(),
119
+ },
120
+ (table) => [
121
+ uniqueIndex("uk_notification_delivery_target").on(
122
+ table.notificationId,
123
+ table.userId,
124
+ table.channel,
125
+ ),
126
+ index("idx_notification_delivery_claim").on(
127
+ table.status,
128
+ table.nextAttemptAt,
129
+ table.leaseExpiresAt,
130
+ ),
131
+ index("idx_notification_delivery_notification").on(table.notificationId),
132
+ ],
133
+ );
134
+
135
+ export const notificationRevisions = pgTable(
136
+ "notification_revisions",
137
+ {
138
+ notificationId: varchar("notification_id", { length: 64 })
139
+ .notNull()
140
+ .references(() => platformNotifications.id, { onDelete: "restrict" }),
141
+ revision: integer("revision").notNull(),
142
+ title: varchar("title", { length: 120 }).notNull(),
143
+ summary: varchar("summary", { length: 240 }).notNull(),
144
+ content: text("content").notNull(),
145
+ category: varchar("category", { length: 40 }).notNull(),
146
+ editedAt: timestamp("edited_at", { withTimezone: true }).notNull(),
147
+ editedBy: varchar("edited_by", { length: 64 }).notNull(),
148
+ },
149
+ (table) => [
150
+ primaryKey({ columns: [table.notificationId, table.revision] }),
151
+ index("idx_notification_revisions_time").on(
152
+ table.notificationId,
153
+ table.editedAt,
154
+ ),
155
+ ],
156
+ );
157
+
45
158
  export const notificationReads = pgTable(
46
159
  "notification_reads",
47
160
  {
@@ -58,3 +171,25 @@ export const notificationReads = pgTable(
58
171
  index("idx_notification_reads_user").on(table.userId, table.readAt),
59
172
  ],
60
173
  );
174
+
175
+ export const notificationFavorites = pgTable(
176
+ "notification_favorites",
177
+ {
178
+ notificationId: varchar("notification_id", { length: 64 })
179
+ .notNull()
180
+ .references(() => platformNotifications.id, { onDelete: "restrict" }),
181
+ userId: varchar("user_id", { length: 64 })
182
+ .notNull()
183
+ .references(() => users.id, { onDelete: "restrict" }),
184
+ favoritedAt: timestamp("favorited_at", { withTimezone: true })
185
+ .notNull()
186
+ .defaultNow(),
187
+ },
188
+ (table) => [
189
+ primaryKey({ columns: [table.notificationId, table.userId] }),
190
+ index("idx_notification_favorites_user").on(
191
+ table.userId,
192
+ table.favoritedAt,
193
+ ),
194
+ ],
195
+ );
@@ -19,11 +19,16 @@ import {
19
19
  // @windy-module system.settings begin
20
20
  import {
21
21
  boolean,
22
+ jsonb as settingsJsonb,
22
23
  pgTable as settingsTable,
23
24
  text,
24
25
  varchar as settingsVarchar,
25
26
  } from "drizzle-orm/pg-core";
26
27
  import { auditColumns } from "./common.js";
28
+ import type {
29
+ WatermarkContentField,
30
+ WatermarkDensity,
31
+ } from "@southwind-ai/shared";
27
32
  // @windy-module system.settings end
28
33
 
29
34
  // @windy-module system.settings begin
@@ -44,6 +49,14 @@ export const platformWatermarkPolicies = settingsTable(
44
49
  id: settingsVarchar("id", { length: 64 }).primaryKey(),
45
50
  enabled: boolean("enabled").notNull(),
46
51
  businessPagesEnabled: boolean("business_pages_enabled").notNull(),
52
+ density: settingsVarchar("density", { length: 16 })
53
+ .$type<WatermarkDensity>()
54
+ .default("medium")
55
+ .notNull(),
56
+ contentFields: settingsJsonb("content_fields")
57
+ .$type<WatermarkContentField[]>()
58
+ .default(["username", "displayName"])
59
+ .notNull(),
47
60
  customContent: settingsVarchar("custom_content", { length: 80 }).notNull(),
48
61
  ...auditColumns,
49
62
  },
@@ -1,23 +0,0 @@
1
- import { index, pgTable, text, varchar } from "drizzle-orm/pg-core";
2
- import { auditColumns, idColumn, statusColumn } from "./common.js";
3
-
4
- export const workOrders = pgTable(
5
- "work_orders",
6
- {
7
- id: idColumn().primaryKey(),
8
- title: varchar("title", { length: 120 }).notNull(),
9
- description: text("description").notNull(),
10
- priority: varchar("priority", { length: 24 }).notNull(),
11
- workflowStatus: varchar("workflow_status", { length: 32 }).notNull(),
12
- ownerId: varchar("owner_id", { length: 64 }).notNull(),
13
- departmentId: varchar("department_id", { length: 64 }),
14
- status: statusColumn().notNull(),
15
- ...auditColumns,
16
- },
17
- (table) => [
18
- index("idx_work_orders_owner").on(table.ownerId),
19
- index("idx_work_orders_department").on(table.departmentId),
20
- index("idx_work_orders_status").on(table.status),
21
- index("idx_work_orders_workflow").on(table.workflowStatus),
22
- ],
23
- );