@maudecode/kanvil 0.0.0-stage → 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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/apps/server/.env.example +41 -0
  4. package/apps/server/dist/auth-BscXb1NQ.mjs +2666 -0
  5. package/apps/server/dist/config-Qxr_7oqZ.mjs +575 -0
  6. package/apps/server/dist/configure.mjs +23 -0
  7. package/apps/server/dist/index.mjs +1754 -0
  8. package/apps/server/dist/migrate-C9f4ozB4.mjs +17 -0
  9. package/apps/server/dist/migrate.mjs +12 -0
  10. package/apps/server/dist/recover.mjs +52 -0
  11. package/apps/server/drizzle/0000_foundation.sql +437 -0
  12. package/apps/server/drizzle/0001_claim_identity.sql +1 -0
  13. package/apps/server/drizzle/0002_transactional_attachments.sql +6 -0
  14. package/apps/server/drizzle/0003_durable_claim_fence.sql +4 -0
  15. package/apps/server/drizzle/0004_lumpy_archangel.sql +6 -0
  16. package/apps/server/drizzle/0005_unique_toxin.sql +28 -0
  17. package/apps/server/drizzle/0006_instance_services.sql +48 -0
  18. package/apps/server/drizzle/0007_optional_broker.sql +6 -0
  19. package/apps/server/drizzle/0008_workspaces.sql +37 -0
  20. package/apps/server/drizzle/0009_project_icons.sql +6 -0
  21. package/apps/server/drizzle/0010_project_labels.sql +14 -0
  22. package/apps/server/drizzle/0011_due_dates.sql +1 -0
  23. package/apps/server/drizzle/0012_label_colors.sql +7 -0
  24. package/apps/server/drizzle/meta/0000_snapshot.json +3210 -0
  25. package/apps/server/drizzle/meta/0001_snapshot.json +3216 -0
  26. package/apps/server/drizzle/meta/0002_snapshot.json +3255 -0
  27. package/apps/server/drizzle/meta/0003_snapshot.json +3262 -0
  28. package/apps/server/drizzle/meta/0004_snapshot.json +3302 -0
  29. package/apps/server/drizzle/meta/0005_snapshot.json +3518 -0
  30. package/apps/server/drizzle/meta/0006_snapshot.json +3842 -0
  31. package/apps/server/drizzle/meta/0007_snapshot.json +3874 -0
  32. package/apps/server/drizzle/meta/0008_snapshot.json +4051 -0
  33. package/apps/server/drizzle/meta/0009_snapshot.json +4105 -0
  34. package/apps/server/drizzle/meta/0010_snapshot.json +4174 -0
  35. package/apps/server/drizzle/meta/0011_snapshot.json +4180 -0
  36. package/apps/server/drizzle/meta/0012_snapshot.json +4226 -0
  37. package/apps/server/drizzle/meta/_journal.json +97 -0
  38. package/apps/web/dist/apple-touch-icon.png +0 -0
  39. package/apps/web/dist/assets/CompositeItem-D9KvUNiq.js +1 -0
  40. package/apps/web/dist/assets/admin-settings-B0u2-aAK.js +1 -0
  41. package/apps/web/dist/assets/camera-Cv5wKkKn.js +1 -0
  42. package/apps/web/dist/assets/date-picker-lhXxKIlN.js +1 -0
  43. package/apps/web/dist/assets/ellipsis-CN74O_EP.js +1 -0
  44. package/apps/web/dist/assets/forms-456UbfIC.js +92 -0
  45. package/apps/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  46. package/apps/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  47. package/apps/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  48. package/apps/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  49. package/apps/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  50. package/apps/web/dist/assets/index-CVzq9BMC.css +2 -0
  51. package/apps/web/dist/assets/index-Dt8wYIVA.js +51 -0
  52. package/apps/web/dist/assets/instance-BCNvOvYa.js +1 -0
  53. package/apps/web/dist/assets/list-order-1UoOT5nR.js +1 -0
  54. package/apps/web/dist/assets/lucide-U995NW8E.js +1 -0
  55. package/apps/web/dist/assets/member-directory-C81ugis6.js +1 -0
  56. package/apps/web/dist/assets/popover-BWk65ByY.js +1 -0
  57. package/apps/web/dist/assets/project-settings-DnpVfVf5.js +1 -0
  58. package/apps/web/dist/assets/rotate-ccw-BLeWpuu1.js +1 -0
  59. package/apps/web/dist/assets/settings-DUXuOgWn.js +1 -0
  60. package/apps/web/dist/assets/settings-layout-xR4hrfte.js +1 -0
  61. package/apps/web/dist/assets/table-BQbZG4BK.js +1 -0
  62. package/apps/web/dist/assets/ticket-detail-B9M_eaVu.js +2 -0
  63. package/apps/web/dist/assets/ticket-editor-Chg1lDuU.js +1 -0
  64. package/apps/web/dist/assets/ticket-list-C8500tXj.js +5 -0
  65. package/apps/web/dist/assets/ticket-tags-cbwQ93Gt.js +1 -0
  66. package/apps/web/dist/assets/upload-5Wg5ou_Z.js +1 -0
  67. package/apps/web/dist/assets/workspace-settings-Dte44Kkx.js +1 -0
  68. package/apps/web/dist/brand/icon-192.png +0 -0
  69. package/apps/web/dist/brand/icon-512.png +0 -0
  70. package/apps/web/dist/brand/mark-blue.svg +1 -0
  71. package/apps/web/dist/brand/mark-white.svg +1 -0
  72. package/apps/web/dist/brand/mark.svg +1 -0
  73. package/apps/web/dist/brand/maskable-512.png +0 -0
  74. package/apps/web/dist/brand/social.png +0 -0
  75. package/apps/web/dist/favicon.ico +0 -0
  76. package/apps/web/dist/favicon.svg +1 -0
  77. package/apps/web/dist/fonts/OFL.txt +93 -0
  78. package/apps/web/dist/fonts/ibm-plex-sans-latin.woff2 +0 -0
  79. package/apps/web/dist/icon.svg +1 -0
  80. package/apps/web/dist/index.html +31 -0
  81. package/apps/web/dist/site.webmanifest +29 -0
  82. package/apps/web/dist/third-party-icons.txt +64 -0
  83. package/apps/web/dist/third-party-themes.txt +548 -0
  84. package/bin/kanvil.mjs +27 -0
  85. package/compose.yml +66 -0
  86. package/docs/api.md +87 -0
  87. package/docs/branding.md +13 -0
  88. package/docs/design.md +37 -0
  89. package/docs/infrastructure.md +94 -0
  90. package/docs/mobile.md +79 -0
  91. package/docs/plan.md +111 -0
  92. package/docs/releasing.md +18 -0
  93. package/docs/self-hosting.md +204 -0
  94. package/docs/themes.md +38 -0
  95. package/docs/verification.md +83 -0
  96. package/docs/workspaces.md +21 -0
  97. package/package.json +45 -4
@@ -0,0 +1,575 @@
1
+ import pg from "pg";
2
+ import { drizzle } from "drizzle-orm/node-postgres";
3
+ import { relations } from "drizzle-orm";
4
+ import { boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
5
+ import { z } from "zod";
6
+ //#region \0rolldown/runtime.js
7
+ var __defProp = Object.defineProperty;
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
+ return target;
16
+ };
17
+ //#endregion
18
+ //#region src/auth-schema.ts
19
+ var auth_schema_exports = /* @__PURE__ */ __exportAll({
20
+ account: () => account,
21
+ accountRelations: () => accountRelations,
22
+ apikey: () => apikey,
23
+ jwks: () => jwks,
24
+ oauthAccessToken: () => oauthAccessToken,
25
+ oauthAccessTokenRelations: () => oauthAccessTokenRelations,
26
+ oauthClient: () => oauthClient,
27
+ oauthClientAssertion: () => oauthClientAssertion,
28
+ oauthClientRelations: () => oauthClientRelations,
29
+ oauthClientResource: () => oauthClientResource,
30
+ oauthClientResourceRelations: () => oauthClientResourceRelations,
31
+ oauthConsent: () => oauthConsent,
32
+ oauthConsentRelations: () => oauthConsentRelations,
33
+ oauthRefreshToken: () => oauthRefreshToken,
34
+ oauthRefreshTokenRelations: () => oauthRefreshTokenRelations,
35
+ oauthResource: () => oauthResource,
36
+ oauthResourceRelations: () => oauthResourceRelations,
37
+ session: () => session,
38
+ sessionRelations: () => sessionRelations,
39
+ user: () => user,
40
+ userRelations: () => userRelations,
41
+ verification: () => verification
42
+ });
43
+ const user = pgTable("user", {
44
+ id: text("id").primaryKey(),
45
+ name: text("name").notNull(),
46
+ email: text("email").notNull().unique(),
47
+ emailVerified: boolean("email_verified").default(false).notNull(),
48
+ image: text("image"),
49
+ createdAt: timestamp("created_at").defaultNow().notNull(),
50
+ updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()).notNull(),
51
+ role: text("role"),
52
+ banned: boolean("banned").default(false),
53
+ banReason: text("ban_reason"),
54
+ banExpires: timestamp("ban_expires")
55
+ });
56
+ const session = pgTable("session", {
57
+ id: text("id").primaryKey(),
58
+ expiresAt: timestamp("expires_at").notNull(),
59
+ token: text("token").notNull().unique(),
60
+ createdAt: timestamp("created_at").defaultNow().notNull(),
61
+ updatedAt: timestamp("updated_at").$onUpdate(() => /* @__PURE__ */ new Date()).notNull(),
62
+ ipAddress: text("ip_address"),
63
+ userAgent: text("user_agent"),
64
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
65
+ impersonatedBy: text("impersonated_by")
66
+ }, (table) => [index("session_userId_idx").on(table.userId)]);
67
+ const account = pgTable("account", {
68
+ id: text("id").primaryKey(),
69
+ accountId: text("account_id").notNull(),
70
+ providerId: text("provider_id").notNull(),
71
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
72
+ accessToken: text("access_token"),
73
+ refreshToken: text("refresh_token"),
74
+ idToken: text("id_token"),
75
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
76
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
77
+ scope: text("scope"),
78
+ password: text("password"),
79
+ createdAt: timestamp("created_at").defaultNow().notNull(),
80
+ updatedAt: timestamp("updated_at").$onUpdate(() => /* @__PURE__ */ new Date()).notNull()
81
+ }, (table) => [index("account_userId_idx").on(table.userId)]);
82
+ const verification = pgTable("verification", {
83
+ id: text("id").primaryKey(),
84
+ identifier: text("identifier").notNull(),
85
+ value: text("value").notNull(),
86
+ expiresAt: timestamp("expires_at").notNull(),
87
+ createdAt: timestamp("created_at").defaultNow().notNull(),
88
+ updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()).notNull()
89
+ }, (table) => [index("verification_identifier_idx").on(table.identifier)]);
90
+ const apikey = pgTable("apikey", {
91
+ id: text("id").primaryKey(),
92
+ configId: text("config_id").default("default").notNull(),
93
+ name: text("name"),
94
+ start: text("start"),
95
+ referenceId: text("reference_id").notNull(),
96
+ prefix: text("prefix"),
97
+ key: text("key").notNull(),
98
+ refillInterval: integer("refill_interval"),
99
+ refillAmount: integer("refill_amount"),
100
+ lastRefillAt: timestamp("last_refill_at"),
101
+ enabled: boolean("enabled").default(true),
102
+ rateLimitEnabled: boolean("rate_limit_enabled").default(true),
103
+ rateLimitTimeWindow: integer("rate_limit_time_window").default(864e5),
104
+ rateLimitMax: integer("rate_limit_max").default(10),
105
+ requestCount: integer("request_count").default(0),
106
+ remaining: integer("remaining"),
107
+ lastRequest: timestamp("last_request"),
108
+ expiresAt: timestamp("expires_at"),
109
+ createdAt: timestamp("created_at").notNull(),
110
+ updatedAt: timestamp("updated_at").notNull(),
111
+ permissions: text("permissions"),
112
+ metadata: text("metadata")
113
+ }, (table) => [
114
+ index("apikey_configId_idx").on(table.configId),
115
+ index("apikey_referenceId_idx").on(table.referenceId),
116
+ index("apikey_key_idx").on(table.key)
117
+ ]);
118
+ const jwks = pgTable("jwks", {
119
+ id: text("id").primaryKey(),
120
+ publicKey: text("public_key").notNull(),
121
+ privateKey: text("private_key").notNull(),
122
+ createdAt: timestamp("created_at").notNull(),
123
+ expiresAt: timestamp("expires_at"),
124
+ alg: text("alg"),
125
+ crv: text("crv")
126
+ });
127
+ const oauthClient = pgTable("oauth_client", {
128
+ id: text("id").primaryKey(),
129
+ clientId: text("client_id").notNull().unique(),
130
+ clientSecret: text("client_secret"),
131
+ clientDiscoveryId: text("client_discovery_id"),
132
+ disabled: boolean("disabled").default(false),
133
+ skipConsent: boolean("skip_consent"),
134
+ enableEndSession: boolean("enable_end_session"),
135
+ subjectType: text("subject_type"),
136
+ scopes: text("scopes").array(),
137
+ clientCredentialsScopes: text("client_credentials_scopes").array().default([]),
138
+ userId: text("user_id").references(() => user.id, { onDelete: "cascade" }),
139
+ createdAt: timestamp("created_at"),
140
+ updatedAt: timestamp("updated_at"),
141
+ name: text("name"),
142
+ uri: text("uri"),
143
+ icon: text("icon"),
144
+ contacts: text("contacts").array(),
145
+ tos: text("tos"),
146
+ policy: text("policy"),
147
+ softwareId: text("software_id"),
148
+ softwareVersion: text("software_version"),
149
+ softwareStatement: text("software_statement"),
150
+ redirectUris: text("redirect_uris").array().notNull(),
151
+ postLogoutRedirectUris: text("post_logout_redirect_uris").array(),
152
+ backchannelLogoutUri: text("backchannel_logout_uri"),
153
+ backchannelLogoutSessionRequired: boolean("backchannel_logout_session_required"),
154
+ tokenEndpointAuthMethod: text("token_endpoint_auth_method"),
155
+ applicationType: text("application_type"),
156
+ jwks: text("jwks"),
157
+ jwksUri: text("jwks_uri"),
158
+ grantTypes: text("grant_types").array(),
159
+ responseTypes: text("response_types").array(),
160
+ requirePKCE: boolean("require_pkce"),
161
+ dpopBoundAccessTokens: boolean("dpop_bound_access_tokens").default(false),
162
+ referenceId: text("reference_id"),
163
+ metadata: jsonb("metadata")
164
+ }, (table) => [index("oauthClient_userId_idx").on(table.userId)]);
165
+ const oauthResource = pgTable("oauth_resource", {
166
+ id: text("id").primaryKey(),
167
+ identifier: text("identifier").notNull().unique(),
168
+ name: text("name").notNull(),
169
+ accessTokenTtl: integer("access_token_ttl"),
170
+ refreshTokenTtl: integer("refresh_token_ttl"),
171
+ signingAlgorithm: text("signing_algorithm"),
172
+ signingKeyId: text("signing_key_id"),
173
+ allowedScopes: text("allowed_scopes").array(),
174
+ customClaims: jsonb("custom_claims"),
175
+ dpopBoundAccessTokensRequired: boolean("dpop_bound_access_tokens_required").default(false),
176
+ disabled: boolean("disabled").default(false),
177
+ createdAt: timestamp("created_at"),
178
+ updatedAt: timestamp("updated_at"),
179
+ policyVersion: integer("policy_version").default(1),
180
+ metadata: jsonb("metadata")
181
+ });
182
+ const oauthClientResource = pgTable("oauth_client_resource", {
183
+ id: text("id").primaryKey(),
184
+ clientId: text("client_id").notNull().references(() => oauthClient.clientId, { onDelete: "cascade" }),
185
+ resourceId: text("resource_id").notNull().references(() => oauthResource.identifier, { onDelete: "cascade" }),
186
+ metadata: jsonb("metadata"),
187
+ createdAt: timestamp("created_at")
188
+ }, (table) => [
189
+ uniqueIndex("oauthClientResource_clientId_resourceId_uidx").on(table.clientId, table.resourceId),
190
+ index("oauthClientResource_clientId_idx").on(table.clientId),
191
+ index("oauthClientResource_resourceId_idx").on(table.resourceId)
192
+ ]);
193
+ const oauthRefreshToken = pgTable("oauth_refresh_token", {
194
+ id: text("id").primaryKey(),
195
+ token: text("token").notNull().unique(),
196
+ clientId: text("client_id").notNull().references(() => oauthClient.clientId, { onDelete: "cascade" }),
197
+ sessionId: text("session_id").references(() => session.id, { onDelete: "set null" }),
198
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
199
+ referenceId: text("reference_id"),
200
+ authorizationCodeId: text("authorization_code_id"),
201
+ resources: text("resources").array(),
202
+ requestedUserInfoClaims: text("requested_user_info_claims").array(),
203
+ expiresAt: timestamp("expires_at").notNull(),
204
+ createdAt: timestamp("created_at").notNull(),
205
+ revoked: timestamp("revoked"),
206
+ rotatedAt: timestamp("rotated_at"),
207
+ rotationReplayResponse: text("rotation_replay_response"),
208
+ rotationReplayExpiresAt: timestamp("rotation_replay_expires_at"),
209
+ authTime: timestamp("auth_time"),
210
+ confirmation: jsonb("confirmation"),
211
+ scopes: text("scopes").array().notNull()
212
+ }, (table) => [
213
+ index("oauthRefreshToken_clientId_idx").on(table.clientId),
214
+ index("oauthRefreshToken_sessionId_idx").on(table.sessionId),
215
+ index("oauthRefreshToken_userId_idx").on(table.userId),
216
+ index("oauthRefreshToken_authorizationCodeId_idx").on(table.authorizationCodeId)
217
+ ]);
218
+ const oauthAccessToken = pgTable("oauth_access_token", {
219
+ id: text("id").primaryKey(),
220
+ token: text("token").notNull().unique(),
221
+ clientId: text("client_id").notNull().references(() => oauthClient.clientId, { onDelete: "cascade" }),
222
+ sessionId: text("session_id").references(() => session.id, { onDelete: "set null" }),
223
+ userId: text("user_id").references(() => user.id, { onDelete: "cascade" }),
224
+ referenceId: text("reference_id"),
225
+ authorizationCodeId: text("authorization_code_id"),
226
+ resources: text("resources").array(),
227
+ requestedUserInfoClaims: text("requested_user_info_claims").array(),
228
+ refreshId: text("refresh_id").references(() => oauthRefreshToken.id, { onDelete: "cascade" }),
229
+ expiresAt: timestamp("expires_at").notNull(),
230
+ createdAt: timestamp("created_at").notNull(),
231
+ revoked: timestamp("revoked"),
232
+ confirmation: jsonb("confirmation"),
233
+ scopes: text("scopes").array().notNull()
234
+ }, (table) => [
235
+ index("oauthAccessToken_clientId_idx").on(table.clientId),
236
+ index("oauthAccessToken_sessionId_idx").on(table.sessionId),
237
+ index("oauthAccessToken_userId_idx").on(table.userId),
238
+ index("oauthAccessToken_authorizationCodeId_idx").on(table.authorizationCodeId),
239
+ index("oauthAccessToken_refreshId_idx").on(table.refreshId)
240
+ ]);
241
+ const oauthConsent = pgTable("oauth_consent", {
242
+ id: text("id").primaryKey(),
243
+ clientId: text("client_id").notNull().references(() => oauthClient.clientId, { onDelete: "cascade" }),
244
+ userId: text("user_id").references(() => user.id, { onDelete: "cascade" }),
245
+ referenceId: text("reference_id"),
246
+ resources: text("resources").array(),
247
+ requestedUserInfoClaims: text("requested_user_info_claims").array(),
248
+ scopes: text("scopes").array().notNull(),
249
+ createdAt: timestamp("created_at").notNull(),
250
+ updatedAt: timestamp("updated_at").notNull()
251
+ }, (table) => [index("oauthConsent_clientId_idx").on(table.clientId), index("oauthConsent_userId_idx").on(table.userId)]);
252
+ const oauthClientAssertion = pgTable("oauth_client_assertion", {
253
+ id: text("id").primaryKey(),
254
+ expiresAt: timestamp("expires_at").notNull()
255
+ });
256
+ const userRelations = relations(user, ({ many }) => ({
257
+ sessions: many(session),
258
+ accounts: many(account),
259
+ oauthClients: many(oauthClient),
260
+ oauthRefreshTokens: many(oauthRefreshToken),
261
+ oauthAccessTokens: many(oauthAccessToken),
262
+ oauthConsents: many(oauthConsent)
263
+ }));
264
+ const sessionRelations = relations(session, ({ one, many }) => ({
265
+ user: one(user, {
266
+ fields: [session.userId],
267
+ references: [user.id]
268
+ }),
269
+ oauthRefreshTokens: many(oauthRefreshToken),
270
+ oauthAccessTokens: many(oauthAccessToken)
271
+ }));
272
+ const accountRelations = relations(account, ({ one }) => ({ user: one(user, {
273
+ fields: [account.userId],
274
+ references: [user.id]
275
+ }) }));
276
+ const oauthClientRelations = relations(oauthClient, ({ one, many }) => ({
277
+ user: one(user, {
278
+ fields: [oauthClient.userId],
279
+ references: [user.id]
280
+ }),
281
+ oauthClientResources: many(oauthClientResource),
282
+ oauthRefreshTokens: many(oauthRefreshToken),
283
+ oauthAccessTokens: many(oauthAccessToken),
284
+ oauthConsents: many(oauthConsent)
285
+ }));
286
+ const oauthResourceRelations = relations(oauthResource, ({ many }) => ({ oauthClientResources: many(oauthClientResource) }));
287
+ const oauthClientResourceRelations = relations(oauthClientResource, ({ one }) => ({
288
+ oauthClient: one(oauthClient, {
289
+ fields: [oauthClientResource.clientId],
290
+ references: [oauthClient.clientId]
291
+ }),
292
+ oauthResource: one(oauthResource, {
293
+ fields: [oauthClientResource.resourceId],
294
+ references: [oauthResource.identifier]
295
+ })
296
+ }));
297
+ const oauthRefreshTokenRelations = relations(oauthRefreshToken, ({ one, many }) => ({
298
+ oauthClient: one(oauthClient, {
299
+ fields: [oauthRefreshToken.clientId],
300
+ references: [oauthClient.clientId]
301
+ }),
302
+ session: one(session, {
303
+ fields: [oauthRefreshToken.sessionId],
304
+ references: [session.id]
305
+ }),
306
+ user: one(user, {
307
+ fields: [oauthRefreshToken.userId],
308
+ references: [user.id]
309
+ }),
310
+ oauthAccessTokens: many(oauthAccessToken)
311
+ }));
312
+ const oauthAccessTokenRelations = relations(oauthAccessToken, ({ one }) => ({
313
+ oauthClient: one(oauthClient, {
314
+ fields: [oauthAccessToken.clientId],
315
+ references: [oauthClient.clientId]
316
+ }),
317
+ session: one(session, {
318
+ fields: [oauthAccessToken.sessionId],
319
+ references: [session.id]
320
+ }),
321
+ user: one(user, {
322
+ fields: [oauthAccessToken.userId],
323
+ references: [user.id]
324
+ }),
325
+ oauthRefreshToken: one(oauthRefreshToken, {
326
+ fields: [oauthAccessToken.refreshId],
327
+ references: [oauthRefreshToken.id]
328
+ })
329
+ }));
330
+ const oauthConsentRelations = relations(oauthConsent, ({ one }) => ({
331
+ oauthClient: one(oauthClient, {
332
+ fields: [oauthConsent.clientId],
333
+ references: [oauthClient.clientId]
334
+ }),
335
+ user: one(user, {
336
+ fields: [oauthConsent.userId],
337
+ references: [user.id]
338
+ })
339
+ }));
340
+ //#endregion
341
+ //#region src/database.ts
342
+ function createDatabase(url) {
343
+ const pool = new pg.Pool({
344
+ connectionString: url,
345
+ max: 10,
346
+ idleTimeoutMillis: 3e4
347
+ });
348
+ pool.on("error", () => {
349
+ console.error("A PostgreSQL idle connection was lost; the pool will reconnect.");
350
+ });
351
+ return {
352
+ pool,
353
+ db: drizzle(pool, { schema: auth_schema_exports })
354
+ };
355
+ }
356
+ //#endregion
357
+ //#region ../../packages/contracts/src/instance.ts
358
+ const endpoint = z.url({ protocol: /^https?$/ }).refine((value) => !/^https?:\/\/[^/]*@/.test(value) && !/[?#]/.test(value), "Use an HTTP(S) endpoint without embedded credentials");
359
+ const s3SettingsSchema = z.object({
360
+ endpoint: z.union([endpoint, z.literal("")]).default(""),
361
+ region: z.string().min(1).max(100).default("us-east-1"),
362
+ bucket: z.string().min(3).max(255),
363
+ prefix: z.string().max(200).regex(/^[a-zA-Z0-9/_-]*$/).default("kanvil/"),
364
+ forcePathStyle: z.boolean().default(true),
365
+ accessKeyId: z.string().max(500).default(""),
366
+ secretAccessKey: z.string().max(2e3).optional()
367
+ }).strict();
368
+ const searchAuthenticationSchema = z.enum([
369
+ "none",
370
+ "apiKey",
371
+ "basic"
372
+ ]);
373
+ const searchSettingsSchema = z.object({
374
+ url: endpoint,
375
+ auth: searchAuthenticationSchema.optional(),
376
+ index: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,100}$/),
377
+ apiKey: z.string().max(4e3).optional(),
378
+ username: z.string().max(500).default(""),
379
+ password: z.string().max(2e3).optional()
380
+ }).strict();
381
+ const storageSettingsSchema = z.discriminatedUnion("provider", [z.object({ provider: z.literal("local") }).strict(), s3SettingsSchema.extend({ provider: z.literal("s3") })]);
382
+ const externalSearchSettingsSchema = z.discriminatedUnion("provider", [z.object({ provider: z.literal("postgres") }).strict(), searchSettingsSchema.extend({ provider: z.literal("elasticsearch") })]);
383
+ const brokerSettingsSchema = z.object({
384
+ url: z.url({ protocol: /^amqps?$/ }).max(4e3).optional(),
385
+ exchange: z.string().min(1).max(200).regex(/^[a-zA-Z0-9_.-]+$/).refine((value) => !value.startsWith("amq."), "Use an application exchange").default("kanvil.events")
386
+ }).strict();
387
+ const eventDeliverySettingsSchema = z.discriminatedUnion("provider", [z.object({ provider: z.literal("postgres") }).strict(), brokerSettingsSchema.extend({ provider: z.literal("rabbitmq") })]);
388
+ const instanceUpdateSchema = z.object({
389
+ expectedVersion: z.number().int().positive(),
390
+ name: z.string().trim().min(1).max(100),
391
+ description: z.string().max(500),
392
+ storage: storageSettingsSchema,
393
+ search: externalSearchSettingsSchema,
394
+ broker: eventDeliverySettingsSchema.optional()
395
+ }).strict();
396
+ z.object({
397
+ name: z.string(),
398
+ description: z.string()
399
+ });
400
+ const adminInstanceSchema = z.object({
401
+ id: z.uuid(),
402
+ version: z.number().int(),
403
+ name: z.string(),
404
+ description: z.string(),
405
+ storage: z.object({
406
+ provider: z.enum(["local", "s3"]),
407
+ source: z.enum(["deployment", "admin"]),
408
+ settings: s3SettingsSchema.omit({ secretAccessKey: true }).nullable(),
409
+ secretConfigured: z.boolean(),
410
+ pending: z.number().int(),
411
+ status: z.enum([
412
+ "ready",
413
+ "transferring",
414
+ "unavailable"
415
+ ]),
416
+ error: z.string().nullable()
417
+ }),
418
+ search: z.object({
419
+ provider: z.enum(["postgres", "elasticsearch"]),
420
+ source: z.enum(["deployment", "admin"]),
421
+ settings: searchSettingsSchema.omit({
422
+ apiKey: true,
423
+ password: true
424
+ }).nullable(),
425
+ secretConfigured: z.boolean(),
426
+ status: z.enum([
427
+ "builtin",
428
+ "indexing",
429
+ "ready",
430
+ "unavailable"
431
+ ]),
432
+ cursor: z.string(),
433
+ error: z.string().nullable()
434
+ }),
435
+ broker: z.object({
436
+ provider: z.enum(["postgres", "rabbitmq"]),
437
+ source: z.enum(["deployment", "admin"]),
438
+ settings: z.object({
439
+ endpoint: z.string(),
440
+ exchange: z.string()
441
+ }).nullable(),
442
+ secretConfigured: z.boolean(),
443
+ status: z.enum([
444
+ "builtin",
445
+ "connecting",
446
+ "ready",
447
+ "unavailable"
448
+ ]),
449
+ error: z.string().nullable()
450
+ }),
451
+ deployment: z.object({
452
+ url: z.string(),
453
+ oidc: z.object({
454
+ name: z.string(),
455
+ discoveryUrl: z.string(),
456
+ clientId: z.string()
457
+ }).nullable(),
458
+ push: z.boolean(),
459
+ version: z.string()
460
+ }),
461
+ locations: z.array(z.object({
462
+ id: z.uuid(),
463
+ bucket: z.string(),
464
+ endpoint: z.string(),
465
+ prefix: z.string(),
466
+ objects: z.number().int()
467
+ }))
468
+ });
469
+ const photoInputSchema = z.object({
470
+ data: z.string().max(7e5),
471
+ contentType: z.enum([
472
+ "image/png",
473
+ "image/jpeg",
474
+ "image/webp"
475
+ ])
476
+ }).strict();
477
+ const photoResultSchema = z.object({ image: z.string().nullable() });
478
+ function searchAuthentication(settings) {
479
+ return settings.auth ?? (settings.apiKey ? "apiKey" : settings.username && settings.password ? "basic" : "none");
480
+ }
481
+ //#endregion
482
+ //#region src/config.ts
483
+ const optionalEnv = (schema) => z.preprocess((value) => value === "" ? void 0 : value, schema.optional());
484
+ const envSchema = z.object({
485
+ DATABASE_URL: z.url(),
486
+ KANVIL_STORAGE_PROVIDER: optionalEnv(z.enum(["local", "s3"])),
487
+ S3_ENDPOINT: optionalEnv(s3SettingsSchema.shape.endpoint),
488
+ S3_REGION: s3SettingsSchema.shape.region,
489
+ S3_BUCKET: optionalEnv(s3SettingsSchema.shape.bucket),
490
+ S3_PREFIX: s3SettingsSchema.shape.prefix,
491
+ S3_ACCESS_KEY_ID: optionalEnv(s3SettingsSchema.shape.accessKeyId),
492
+ S3_SECRET_ACCESS_KEY: optionalEnv(s3SettingsSchema.shape.secretAccessKey),
493
+ S3_FORCE_PATH_STYLE: z.enum(["true", "false"]).default("true").transform((value) => value === "true"),
494
+ KANVIL_SEARCH_PROVIDER: optionalEnv(z.enum(["postgres", "elasticsearch"])),
495
+ ELASTICSEARCH_URL: optionalEnv(searchSettingsSchema.shape.url),
496
+ ELASTICSEARCH_INDEX: optionalEnv(searchSettingsSchema.shape.index),
497
+ ELASTICSEARCH_AUTH: optionalEnv(searchSettingsSchema.shape.auth),
498
+ ELASTICSEARCH_API_KEY: optionalEnv(searchSettingsSchema.shape.apiKey),
499
+ ELASTICSEARCH_USERNAME: searchSettingsSchema.shape.username,
500
+ ELASTICSEARCH_PASSWORD: optionalEnv(searchSettingsSchema.shape.password),
501
+ KANVIL_BROKER_PROVIDER: optionalEnv(z.enum(["postgres", "rabbitmq"])),
502
+ RABBITMQ_URL: optionalEnv(brokerSettingsSchema.shape.url),
503
+ RABBITMQ_EXCHANGE: brokerSettingsSchema.shape.exchange,
504
+ KANVIL_URL: z.url().default("http://localhost:5180"),
505
+ BETTER_AUTH_SECRET: z.string().min(32),
506
+ KANVIL_SETUP_TOKEN: optionalEnv(z.string().min(24)),
507
+ PORT: z.coerce.number().int().min(1).max(65535).default(5180),
508
+ HOST: z.string().default("127.0.0.1"),
509
+ KANVIL_TRUSTED_ORIGINS: z.string().default(""),
510
+ OIDC_DISCOVERY_URL: optionalEnv(z.url()),
511
+ OIDC_CLIENT_ID: optionalEnv(z.string().min(1)),
512
+ OIDC_CLIENT_SECRET: optionalEnv(z.string().min(1)),
513
+ OIDC_NAME: z.string().default("PocketID"),
514
+ KANVIL_SESSION_WRITE_LIMIT: z.coerce.number().int().min(1).max(1e5).default(120),
515
+ KANVIL_PUSH_ENABLED: z.enum(["true", "false"]).default("false").transform((value) => value === "true"),
516
+ EXPO_ACCESS_TOKEN: optionalEnv(z.string().min(1)),
517
+ NODE_ENV: z.enum([
518
+ "development",
519
+ "test",
520
+ "production"
521
+ ]).default("development")
522
+ }).superRefine((value, ctx) => {
523
+ if (value.KANVIL_STORAGE_PROVIDER === "s3" && !value.S3_BUCKET) ctx.addIssue({
524
+ code: "custom",
525
+ message: "S3_BUCKET is required for S3 storage"
526
+ });
527
+ if (value.KANVIL_SEARCH_PROVIDER === "elasticsearch" && !value.ELASTICSEARCH_URL) ctx.addIssue({
528
+ code: "custom",
529
+ message: "ELASTICSEARCH_URL is required for external search"
530
+ });
531
+ if (value.KANVIL_BROKER_PROVIDER === "rabbitmq" && !value.RABBITMQ_URL) ctx.addIssue({
532
+ code: "custom",
533
+ message: "RABBITMQ_URL is required for RabbitMQ delivery"
534
+ });
535
+ if (value.KANVIL_SEARCH_PROVIDER === "elasticsearch") {
536
+ if (value.ELASTICSEARCH_AUTH === "apiKey" && !value.ELASTICSEARCH_API_KEY) ctx.addIssue({
537
+ code: "custom",
538
+ message: "ELASTICSEARCH_API_KEY is required for API-key authentication"
539
+ });
540
+ if (value.ELASTICSEARCH_AUTH === "basic" && (!value.ELASTICSEARCH_USERNAME || !value.ELASTICSEARCH_PASSWORD)) ctx.addIssue({
541
+ code: "custom",
542
+ message: "ELASTICSEARCH_USERNAME and ELASTICSEARCH_PASSWORD are required for basic authentication"
543
+ });
544
+ }
545
+ const supplied = [
546
+ value.OIDC_DISCOVERY_URL,
547
+ value.OIDC_CLIENT_ID,
548
+ value.OIDC_CLIENT_SECRET
549
+ ].filter(Boolean).length;
550
+ if (supplied !== 0 && supplied !== 3) ctx.addIssue({
551
+ code: "custom",
552
+ message: "Configure all three OIDC settings together"
553
+ });
554
+ if (!new URL(value.DATABASE_URL).protocol.startsWith("postgres")) ctx.addIssue({
555
+ code: "custom",
556
+ message: "DATABASE_URL must be PostgreSQL"
557
+ });
558
+ if (value.NODE_ENV === "production" && !value.KANVIL_URL.startsWith("https://")) ctx.addIssue({
559
+ code: "custom",
560
+ message: "Production KANVIL_URL must use HTTPS"
561
+ });
562
+ });
563
+ function readConfig(env = process.env) {
564
+ const values = envSchema.parse(env);
565
+ return {
566
+ ...values,
567
+ trustedOrigins: [
568
+ new URL(values.KANVIL_URL).origin,
569
+ ...values.KANVIL_TRUSTED_ORIGINS.split(",").map((value) => value.trim()).filter(Boolean),
570
+ "kanvil://"
571
+ ]
572
+ };
573
+ }
574
+ //#endregion
575
+ export { photoInputSchema as a, searchAuthentication as c, account as d, apikey as f, user as h, instanceUpdateSchema as i, searchSettingsSchema as l, session as m, adminInstanceSchema as n, photoResultSchema as o, auth_schema_exports as p, brokerSettingsSchema as r, s3SettingsSchema as s, readConfig as t, createDatabase as u };
@@ -0,0 +1,23 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { parseArgs } from "node:util";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ //#region ../../scripts/configure.ts
6
+ const { values } = parseArgs({ options: { production: { type: "string" } } });
7
+ const root = new URL("../", import.meta.url);
8
+ let origin;
9
+ if (values.production) {
10
+ const url = new URL(values.production);
11
+ if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error("Use your public HTTPS origin without a path or credentials.");
12
+ origin = url.origin;
13
+ }
14
+ const databasePassword = randomBytes(32).toString("hex");
15
+ let contents = (await readFile(new URL(".env.example", root), "utf8")).replace(/^POSTGRES_PASSWORD=$/m, `POSTGRES_PASSWORD=${databasePassword}`).replace(/^BETTER_AUTH_SECRET=$/m, `BETTER_AUTH_SECRET=${randomBytes(48).toString("base64url")}`).replace(/^KANVIL_SETUP_TOKEN=$/m, `KANVIL_SETUP_TOKEN=${randomBytes(32).toString("base64url")}`);
16
+ if (origin) contents = contents.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=postgres://kanvil:${databasePassword}@db:5432/kanvil`).replace(/^KANVIL_URL=.*$/m, `KANVIL_URL=${origin}`).replace(/^HOST=.*$/m, "HOST=0.0.0.0").replace(/^KANVIL_TRUSTED_ORIGINS=.*$/m, "KANVIL_TRUSTED_ORIGINS=");
17
+ await writeFile(resolve(".env"), contents, {
18
+ mode: 384,
19
+ flag: "wx"
20
+ });
21
+ console.info("Created private .env configuration. The initial owner setup token is stored as KANVIL_SETUP_TOKEN.");
22
+ //#endregion
23
+ export {};