@sena-ai/platform-core 1.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.
Files changed (158) hide show
  1. package/dist/app.d.ts +9 -0
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +147 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/auth/handler.d.ts +19 -0
  6. package/dist/auth/handler.d.ts.map +1 -0
  7. package/dist/auth/handler.js +213 -0
  8. package/dist/auth/handler.js.map +1 -0
  9. package/dist/auth/session.d.ts +16 -0
  10. package/dist/auth/session.d.ts.map +1 -0
  11. package/dist/auth/session.js +54 -0
  12. package/dist/auth/session.js.map +1 -0
  13. package/dist/db/d1/index.d.ts +14 -0
  14. package/dist/db/d1/index.d.ts.map +1 -0
  15. package/dist/db/d1/index.js +252 -0
  16. package/dist/db/d1/index.js.map +1 -0
  17. package/dist/db/d1/schema.d.ts +610 -0
  18. package/dist/db/d1/schema.d.ts.map +1 -0
  19. package/dist/db/d1/schema.js +58 -0
  20. package/dist/db/d1/schema.js.map +1 -0
  21. package/dist/db/mysql/index.d.ts +14 -0
  22. package/dist/db/mysql/index.d.ts.map +1 -0
  23. package/dist/db/mysql/index.js +248 -0
  24. package/dist/db/mysql/index.js.map +1 -0
  25. package/dist/db/mysql/schema.d.ts +562 -0
  26. package/dist/db/mysql/schema.d.ts.map +1 -0
  27. package/dist/db/mysql/schema.js +61 -0
  28. package/dist/db/mysql/schema.js.map +1 -0
  29. package/dist/db/postgresql/index.d.ts +14 -0
  30. package/dist/db/postgresql/index.d.ts.map +1 -0
  31. package/dist/db/postgresql/index.js +246 -0
  32. package/dist/db/postgresql/index.js.map +1 -0
  33. package/dist/db/postgresql/schema.d.ts +591 -0
  34. package/dist/db/postgresql/schema.d.ts.map +1 -0
  35. package/dist/db/postgresql/schema.js +64 -0
  36. package/dist/db/postgresql/schema.js.map +1 -0
  37. package/dist/index.d.ts +6 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +3 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/relay/api-proxy.d.ts +10 -0
  42. package/dist/relay/api-proxy.d.ts.map +1 -0
  43. package/dist/relay/api-proxy.js +40 -0
  44. package/dist/relay/api-proxy.js.map +1 -0
  45. package/dist/runtime/cf/crypto.d.ts +7 -0
  46. package/dist/runtime/cf/crypto.d.ts.map +1 -0
  47. package/dist/runtime/cf/crypto.js +48 -0
  48. package/dist/runtime/cf/crypto.js.map +1 -0
  49. package/dist/runtime/cf/index.d.ts +20 -0
  50. package/dist/runtime/cf/index.d.ts.map +1 -0
  51. package/dist/runtime/cf/index.js +14 -0
  52. package/dist/runtime/cf/index.js.map +1 -0
  53. package/dist/runtime/cf/relay.d.ts +11 -0
  54. package/dist/runtime/cf/relay.d.ts.map +1 -0
  55. package/dist/runtime/cf/relay.js +57 -0
  56. package/dist/runtime/cf/relay.js.map +1 -0
  57. package/dist/runtime/cf/vault.d.ts +7 -0
  58. package/dist/runtime/cf/vault.d.ts.map +1 -0
  59. package/dist/runtime/cf/vault.js +68 -0
  60. package/dist/runtime/cf/vault.js.map +1 -0
  61. package/dist/runtime/node/crypto.d.ts +6 -0
  62. package/dist/runtime/node/crypto.d.ts.map +1 -0
  63. package/dist/runtime/node/crypto.js +26 -0
  64. package/dist/runtime/node/crypto.js.map +1 -0
  65. package/dist/runtime/node/index.d.ts +17 -0
  66. package/dist/runtime/node/index.d.ts.map +1 -0
  67. package/dist/runtime/node/index.js +14 -0
  68. package/dist/runtime/node/index.js.map +1 -0
  69. package/dist/runtime/node/relay.d.ts +6 -0
  70. package/dist/runtime/node/relay.d.ts.map +1 -0
  71. package/dist/runtime/node/relay.js +73 -0
  72. package/dist/runtime/node/relay.js.map +1 -0
  73. package/dist/runtime/node/vault.d.ts +7 -0
  74. package/dist/runtime/node/vault.d.ts.map +1 -0
  75. package/dist/runtime/node/vault.js +41 -0
  76. package/dist/runtime/node/vault.js.map +1 -0
  77. package/dist/slack/events.d.ts +15 -0
  78. package/dist/slack/events.d.ts.map +1 -0
  79. package/dist/slack/events.js +63 -0
  80. package/dist/slack/events.js.map +1 -0
  81. package/dist/slack/oauth.d.ts +13 -0
  82. package/dist/slack/oauth.d.ts.map +1 -0
  83. package/dist/slack/oauth.js +90 -0
  84. package/dist/slack/oauth.js.map +1 -0
  85. package/dist/slack/provisioner.d.ts +60 -0
  86. package/dist/slack/provisioner.d.ts.map +1 -0
  87. package/dist/slack/provisioner.js +156 -0
  88. package/dist/slack/provisioner.js.map +1 -0
  89. package/dist/types/crypto.d.ts +15 -0
  90. package/dist/types/crypto.d.ts.map +1 -0
  91. package/dist/types/crypto.js +2 -0
  92. package/dist/types/crypto.js.map +1 -0
  93. package/dist/types/index.d.ts +6 -0
  94. package/dist/types/index.d.ts.map +1 -0
  95. package/dist/types/index.js +2 -0
  96. package/dist/types/index.js.map +1 -0
  97. package/dist/types/platform.d.ts +25 -0
  98. package/dist/types/platform.d.ts.map +1 -0
  99. package/dist/types/platform.js +2 -0
  100. package/dist/types/platform.js.map +1 -0
  101. package/dist/types/relay.d.ts +16 -0
  102. package/dist/types/relay.d.ts.map +1 -0
  103. package/dist/types/relay.js +2 -0
  104. package/dist/types/relay.js.map +1 -0
  105. package/dist/types/repository.d.ts +78 -0
  106. package/dist/types/repository.d.ts.map +1 -0
  107. package/dist/types/repository.js +6 -0
  108. package/dist/types/repository.js.map +1 -0
  109. package/dist/types/vault.d.ts +9 -0
  110. package/dist/types/vault.d.ts.map +1 -0
  111. package/dist/types/vault.js +2 -0
  112. package/dist/types/vault.js.map +1 -0
  113. package/dist/web/api.d.ts +9 -0
  114. package/dist/web/api.d.ts.map +1 -0
  115. package/dist/web/api.js +144 -0
  116. package/dist/web/api.js.map +1 -0
  117. package/dist/web/pages.d.ts +4 -0
  118. package/dist/web/pages.d.ts.map +1 -0
  119. package/dist/web/pages.js +401 -0
  120. package/dist/web/pages.js.map +1 -0
  121. package/dist/web/setup.d.ts +5 -0
  122. package/dist/web/setup.d.ts.map +1 -0
  123. package/dist/web/setup.js +208 -0
  124. package/dist/web/setup.js.map +1 -0
  125. package/package.json +46 -0
  126. package/src/app.ts +221 -0
  127. package/src/auth/handler.ts +343 -0
  128. package/src/auth/session.ts +89 -0
  129. package/src/db/d1/index.ts +304 -0
  130. package/src/db/d1/schema.ts +62 -0
  131. package/src/db/mysql/index.ts +301 -0
  132. package/src/db/mysql/schema.ts +78 -0
  133. package/src/db/postgresql/index.ts +311 -0
  134. package/src/db/postgresql/schema.ts +82 -0
  135. package/src/index.ts +21 -0
  136. package/src/relay/api-proxy.ts +61 -0
  137. package/src/runtime/cf/crypto.ts +74 -0
  138. package/src/runtime/cf/index.ts +31 -0
  139. package/src/runtime/cf/relay.ts +74 -0
  140. package/src/runtime/cf/vault.ts +99 -0
  141. package/src/runtime/node/crypto.ts +33 -0
  142. package/src/runtime/node/index.ts +28 -0
  143. package/src/runtime/node/relay.ts +98 -0
  144. package/src/runtime/node/vault.ts +50 -0
  145. package/src/slack/events.ts +92 -0
  146. package/src/slack/oauth.ts +127 -0
  147. package/src/slack/provisioner.ts +256 -0
  148. package/src/types/crypto.ts +14 -0
  149. package/src/types/index.ts +14 -0
  150. package/src/types/platform.ts +31 -0
  151. package/src/types/relay.ts +16 -0
  152. package/src/types/repository.ts +93 -0
  153. package/src/types/vault.ts +8 -0
  154. package/src/web/api.ts +204 -0
  155. package/src/web/pages.ts +458 -0
  156. package/src/web/setup.ts +270 -0
  157. package/tsconfig.json +19 -0
  158. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,304 @@
1
+ import { drizzle, type DrizzleD1Database } from 'drizzle-orm/d1'
2
+ import { eq, and, lt } from 'drizzle-orm'
3
+ import type {
4
+ BotRow,
5
+ ConfigTokenRow,
6
+ WorkspaceAdminConfigRow,
7
+ BotRepository,
8
+ ConfigTokenRepository,
9
+ OAuthStateRepository,
10
+ WorkspaceAdminConfigRepository,
11
+ } from '../../types/repository.js'
12
+ import * as schema from './schema.js'
13
+
14
+ export type D1Db = DrizzleD1Database<typeof schema>
15
+
16
+ export function initD1(d1: D1Database): D1Db {
17
+ return drizzle(d1, { schema })
18
+ }
19
+
20
+ function rowToBot(row: typeof schema.bots.$inferSelect): BotRow {
21
+ return {
22
+ id: row.id,
23
+ name: row.name,
24
+ botUsername: row.botUsername,
25
+ profileImageUrl: row.profileImageUrl,
26
+ connectKey: row.connectKey,
27
+ slackAppId: row.slackAppId,
28
+ slackTeamId: row.slackTeamId,
29
+ botTokenEnc: row.botTokenEnc,
30
+ signingSecretEnc: row.signingSecretEnc,
31
+ clientId: row.clientId,
32
+ clientSecretEnc: row.clientSecretEnc,
33
+ manifestJson: row.manifestJson,
34
+ status: row.status,
35
+ createdAt: row.createdAt,
36
+ updatedAt: row.updatedAt,
37
+ }
38
+ }
39
+
40
+ function rowToWorkspaceAdminConfig(
41
+ row: typeof schema.workspaceAdminConfig.$inferSelect,
42
+ ): WorkspaceAdminConfigRow {
43
+ return {
44
+ workspaceId: row.workspaceId,
45
+ slackClientId: row.slackClientId,
46
+ slackClientSecretEnc: row.slackClientSecretEnc,
47
+ dCookieEnc: row.dCookieEnc,
48
+ xoxcTokenEnc: row.xoxcTokenEnc,
49
+ workspaceDomain: row.workspaceDomain,
50
+ updatedAt: row.updatedAt,
51
+ updatedByUserId: row.updatedByUserId,
52
+ }
53
+ }
54
+
55
+ export interface D1Repositories {
56
+ bots: BotRepository
57
+ configTokens: ConfigTokenRepository
58
+ oauthStates: OAuthStateRepository
59
+ workspaceAdminConfig: WorkspaceAdminConfigRepository
60
+ }
61
+
62
+ export function createD1Repositories(db: D1Db): D1Repositories {
63
+ return {
64
+ bots: createD1BotRepository(db),
65
+ configTokens: createD1ConfigTokenRepository(db),
66
+ oauthStates: createD1OAuthStateRepository(db),
67
+ workspaceAdminConfig: createD1WorkspaceAdminConfigRepository(db),
68
+ }
69
+ }
70
+
71
+ function createD1BotRepository(db: D1Db): BotRepository {
72
+ return {
73
+ async findById(id) {
74
+ const [row] = await db
75
+ .select()
76
+ .from(schema.bots)
77
+ .where(eq(schema.bots.id, id))
78
+ .limit(1)
79
+ return row ? rowToBot(row) : null
80
+ },
81
+
82
+ async findByConnectKey(connectKey) {
83
+ const [row] = await db
84
+ .select()
85
+ .from(schema.bots)
86
+ .where(eq(schema.bots.connectKey, connectKey))
87
+ .limit(1)
88
+ return row ? rowToBot(row) : null
89
+ },
90
+
91
+ async findByConnectKeyAndStatus(connectKey, status) {
92
+ const [row] = await db
93
+ .select()
94
+ .from(schema.bots)
95
+ .where(
96
+ and(
97
+ eq(schema.bots.connectKey, connectKey),
98
+ eq(schema.bots.status, status),
99
+ ),
100
+ )
101
+ .limit(1)
102
+ return row ? rowToBot(row) : null
103
+ },
104
+
105
+ async findByIdAndStatus(id, status) {
106
+ const [row] = await db
107
+ .select()
108
+ .from(schema.bots)
109
+ .where(and(eq(schema.bots.id, id), eq(schema.bots.status, status)))
110
+ .limit(1)
111
+ return row ? rowToBot(row) : null
112
+ },
113
+
114
+ async findAll() {
115
+ const rows = await db.select().from(schema.bots).orderBy(schema.bots.createdAt)
116
+ return rows.map(rowToBot)
117
+ },
118
+
119
+ async findAllSummary() {
120
+ const rows = await db
121
+ .select({
122
+ id: schema.bots.id,
123
+ name: schema.bots.name,
124
+ profileImageUrl: schema.bots.profileImageUrl,
125
+ slackAppId: schema.bots.slackAppId,
126
+ slackTeamId: schema.bots.slackTeamId,
127
+ status: schema.bots.status,
128
+ createdAt: schema.bots.createdAt,
129
+ })
130
+ .from(schema.bots)
131
+ .orderBy(schema.bots.createdAt)
132
+ return rows
133
+ },
134
+
135
+ async create(bot) {
136
+ await db.insert(schema.bots).values({
137
+ id: bot.id,
138
+ name: bot.name,
139
+ botUsername: bot.botUsername,
140
+ profileImageUrl: bot.profileImageUrl,
141
+ connectKey: bot.connectKey,
142
+ slackAppId: bot.slackAppId,
143
+ slackTeamId: bot.slackTeamId,
144
+ botTokenEnc: bot.botTokenEnc,
145
+ signingSecretEnc: bot.signingSecretEnc,
146
+ clientId: bot.clientId,
147
+ clientSecretEnc: bot.clientSecretEnc,
148
+ manifestJson: bot.manifestJson,
149
+ status: bot.status,
150
+ })
151
+ },
152
+
153
+ async update(id, data) {
154
+ await db
155
+ .update(schema.bots)
156
+ .set({ ...data, updatedAt: new Date() })
157
+ .where(eq(schema.bots.id, id))
158
+ },
159
+
160
+ async delete(id) {
161
+ await db.delete(schema.bots).where(eq(schema.bots.id, id))
162
+ },
163
+ }
164
+ }
165
+
166
+ function createD1ConfigTokenRepository(db: D1Db): ConfigTokenRepository {
167
+ return {
168
+ async findByWorkspaceId(id) {
169
+ const [row] = await db
170
+ .select()
171
+ .from(schema.configTokens)
172
+ .where(eq(schema.configTokens.workspaceId, id))
173
+ .limit(1)
174
+ if (!row) return null
175
+ return {
176
+ workspaceId: row.workspaceId,
177
+ accessTokenEnc: row.accessTokenEnc,
178
+ refreshTokenEnc: row.refreshTokenEnc,
179
+ expiresAt: row.expiresAt,
180
+ updatedAt: row.updatedAt,
181
+ }
182
+ },
183
+
184
+ async findAll() {
185
+ const rows = await db.select().from(schema.configTokens)
186
+ return rows.map(
187
+ (row): ConfigTokenRow => ({
188
+ workspaceId: row.workspaceId,
189
+ accessTokenEnc: row.accessTokenEnc,
190
+ refreshTokenEnc: row.refreshTokenEnc,
191
+ expiresAt: row.expiresAt,
192
+ updatedAt: row.updatedAt,
193
+ }),
194
+ )
195
+ },
196
+
197
+ async upsert(row) {
198
+ await db
199
+ .insert(schema.configTokens)
200
+ .values({
201
+ workspaceId: row.workspaceId,
202
+ accessTokenEnc: row.accessTokenEnc,
203
+ refreshTokenEnc: row.refreshTokenEnc,
204
+ expiresAt: row.expiresAt,
205
+ })
206
+ .onConflictDoUpdate({
207
+ target: schema.configTokens.workspaceId,
208
+ set: {
209
+ accessTokenEnc: row.accessTokenEnc,
210
+ refreshTokenEnc: row.refreshTokenEnc,
211
+ expiresAt: row.expiresAt,
212
+ updatedAt: new Date(),
213
+ },
214
+ })
215
+ },
216
+ }
217
+ }
218
+
219
+ function createD1OAuthStateRepository(db: D1Db): OAuthStateRepository {
220
+ return {
221
+ async create(row) {
222
+ await db.insert(schema.oauthStates).values({
223
+ state: row.state,
224
+ botId: row.botId,
225
+ expiresAt: row.expiresAt,
226
+ })
227
+ },
228
+
229
+ async consume(state) {
230
+ const [row] = await db
231
+ .select()
232
+ .from(schema.oauthStates)
233
+ .where(eq(schema.oauthStates.state, state))
234
+ .limit(1)
235
+ if (!row) return null
236
+
237
+ if (row.expiresAt < new Date()) {
238
+ await db.delete(schema.oauthStates).where(eq(schema.oauthStates.state, state))
239
+ return null
240
+ }
241
+
242
+ await db.delete(schema.oauthStates).where(eq(schema.oauthStates.state, state))
243
+
244
+ return {
245
+ state: row.state,
246
+ botId: row.botId,
247
+ expiresAt: row.expiresAt,
248
+ }
249
+ },
250
+
251
+ async deleteExpired() {
252
+ await db.delete(schema.oauthStates).where(lt(schema.oauthStates.expiresAt, new Date()))
253
+ },
254
+ }
255
+ }
256
+
257
+ function createD1WorkspaceAdminConfigRepository(
258
+ db: D1Db,
259
+ ): WorkspaceAdminConfigRepository {
260
+ return {
261
+ async findByWorkspaceId(workspaceId) {
262
+ const [row] = await db
263
+ .select()
264
+ .from(schema.workspaceAdminConfig)
265
+ .where(eq(schema.workspaceAdminConfig.workspaceId, workspaceId))
266
+ .limit(1)
267
+ return row ? rowToWorkspaceAdminConfig(row) : null
268
+ },
269
+
270
+ async findAll() {
271
+ const rows = await db.select().from(schema.workspaceAdminConfig)
272
+ return rows.map(rowToWorkspaceAdminConfig)
273
+ },
274
+
275
+ async upsert(config) {
276
+ await db
277
+ .insert(schema.workspaceAdminConfig)
278
+ .values({
279
+ workspaceId: config.workspaceId,
280
+ slackClientId: config.slackClientId,
281
+ slackClientSecretEnc: config.slackClientSecretEnc,
282
+ dCookieEnc: config.dCookieEnc,
283
+ xoxcTokenEnc: config.xoxcTokenEnc,
284
+ workspaceDomain: config.workspaceDomain,
285
+ updatedByUserId: config.updatedByUserId,
286
+ })
287
+ .onConflictDoUpdate({
288
+ target: schema.workspaceAdminConfig.workspaceId,
289
+ set: {
290
+ slackClientId: config.slackClientId,
291
+ slackClientSecretEnc: config.slackClientSecretEnc,
292
+ dCookieEnc: config.dCookieEnc,
293
+ xoxcTokenEnc: config.xoxcTokenEnc,
294
+ workspaceDomain: config.workspaceDomain,
295
+ updatedByUserId: config.updatedByUserId,
296
+ updatedAt: new Date(),
297
+ },
298
+ })
299
+ },
300
+ }
301
+ }
302
+
303
+ // Re-export schema for migrations
304
+ export { bots, configTokens, oauthStates, workspaceAdminConfig } from './schema.js'
@@ -0,0 +1,62 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
2
+
3
+ /**
4
+ * SQLite schema for Cloudflare D1.
5
+ * Equivalent to the MySQL schema in platform-node.
6
+ */
7
+
8
+ export const bots = sqliteTable('bots', {
9
+ id: text('id').primaryKey(),
10
+ name: text('name').notNull(),
11
+ botUsername: text('bot_username').notNull().default(''),
12
+ profileImageUrl: text('profile_image_url'),
13
+ connectKey: text('connect_key').notNull().unique(),
14
+ slackAppId: text('slack_app_id'),
15
+ slackTeamId: text('slack_team_id'),
16
+ botTokenEnc: text('bot_token_enc'),
17
+ signingSecretEnc: text('signing_secret_enc'),
18
+ clientId: text('client_id'),
19
+ clientSecretEnc: text('client_secret_enc'),
20
+ manifestJson: text('manifest_json'),
21
+ status: text('status', { enum: ['pending', 'active', 'disabled'] })
22
+ .notNull()
23
+ .default('pending'),
24
+ createdAt: integer('created_at', { mode: 'timestamp' })
25
+ .notNull()
26
+ .$defaultFn(() => new Date()),
27
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
28
+ .notNull()
29
+ .$defaultFn(() => new Date())
30
+ .$onUpdateFn(() => new Date()),
31
+ })
32
+
33
+ export const configTokens = sqliteTable('config_tokens', {
34
+ workspaceId: text('workspace_id').primaryKey(),
35
+ accessTokenEnc: text('access_token_enc').notNull(),
36
+ refreshTokenEnc: text('refresh_token_enc').notNull(),
37
+ expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
38
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
39
+ .notNull()
40
+ .$defaultFn(() => new Date())
41
+ .$onUpdateFn(() => new Date()),
42
+ })
43
+
44
+ export const oauthStates = sqliteTable('oauth_states', {
45
+ state: text('state').primaryKey(),
46
+ botId: text('bot_id').notNull(),
47
+ expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
48
+ })
49
+
50
+ export const workspaceAdminConfig = sqliteTable('workspace_admin_config', {
51
+ workspaceId: text('workspace_id').primaryKey(),
52
+ slackClientId: text('slack_client_id'),
53
+ slackClientSecretEnc: text('slack_client_secret_enc'),
54
+ dCookieEnc: text('d_cookie_enc'),
55
+ xoxcTokenEnc: text('xoxc_token_enc'),
56
+ workspaceDomain: text('workspace_domain'),
57
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
58
+ .notNull()
59
+ .$defaultFn(() => new Date())
60
+ .$onUpdateFn(() => new Date()),
61
+ updatedByUserId: text('updated_by_user_id'),
62
+ })
@@ -0,0 +1,301 @@
1
+ import { drizzle, type MySql2Database } from 'drizzle-orm/mysql2'
2
+ import mysql from 'mysql2/promise'
3
+ import { eq, and, lt } from 'drizzle-orm'
4
+ import type {
5
+ BotRow,
6
+ ConfigTokenRow,
7
+ WorkspaceAdminConfigRow,
8
+ BotRepository,
9
+ ConfigTokenRepository,
10
+ OAuthStateRepository,
11
+ WorkspaceAdminConfigRepository,
12
+ } from '../../types/repository.js'
13
+ import * as schema from './schema.js'
14
+
15
+ export type MySQLDatabase = MySql2Database<typeof schema>
16
+
17
+ export async function initMySQLDb(databaseUrl: string): Promise<MySQLDatabase> {
18
+ const pool = mysql.createPool({
19
+ uri: databaseUrl,
20
+ waitForConnections: true,
21
+ connectionLimit: 10,
22
+ })
23
+
24
+ return drizzle(pool, { schema, mode: 'default' })
25
+ }
26
+
27
+ function rowToBot(row: typeof schema.bots.$inferSelect): BotRow {
28
+ return {
29
+ id: row.id,
30
+ name: row.name,
31
+ botUsername: row.botUsername,
32
+ profileImageUrl: row.profileImageUrl,
33
+ connectKey: row.connectKey,
34
+ slackAppId: row.slackAppId,
35
+ slackTeamId: row.slackTeamId,
36
+ botTokenEnc: row.botTokenEnc,
37
+ signingSecretEnc: row.signingSecretEnc,
38
+ clientId: row.clientId,
39
+ clientSecretEnc: row.clientSecretEnc,
40
+ manifestJson: row.manifestJson,
41
+ status: row.status,
42
+ createdAt: row.createdAt,
43
+ updatedAt: row.updatedAt,
44
+ }
45
+ }
46
+
47
+ function rowToWorkspaceAdminConfig(
48
+ row: typeof schema.workspaceAdminConfig.$inferSelect,
49
+ ): WorkspaceAdminConfigRow {
50
+ return {
51
+ workspaceId: row.workspaceId,
52
+ slackClientId: row.slackClientId,
53
+ slackClientSecretEnc: row.slackClientSecretEnc,
54
+ dCookieEnc: row.dCookieEnc,
55
+ xoxcTokenEnc: row.xoxcTokenEnc,
56
+ workspaceDomain: row.workspaceDomain,
57
+ updatedAt: row.updatedAt,
58
+ updatedByUserId: row.updatedByUserId,
59
+ }
60
+ }
61
+
62
+ export interface MySQLRepositories {
63
+ bots: BotRepository
64
+ configTokens: ConfigTokenRepository
65
+ oauthStates: OAuthStateRepository
66
+ workspaceAdminConfig: WorkspaceAdminConfigRepository
67
+ }
68
+
69
+ export function createMySQLRepositories(db: MySQLDatabase): MySQLRepositories {
70
+ return {
71
+ bots: createBotRepository(db),
72
+ configTokens: createConfigTokenRepository(db),
73
+ oauthStates: createOAuthStateRepository(db),
74
+ workspaceAdminConfig: createWorkspaceAdminConfigRepository(db),
75
+ }
76
+ }
77
+
78
+ function createBotRepository(db: MySQLDatabase): BotRepository {
79
+ return {
80
+ async findById(id) {
81
+ const [row] = await db.select().from(schema.bots).where(eq(schema.bots.id, id)).limit(1)
82
+ return row ? rowToBot(row) : null
83
+ },
84
+
85
+ async findByConnectKey(connectKey) {
86
+ const [row] = await db
87
+ .select()
88
+ .from(schema.bots)
89
+ .where(eq(schema.bots.connectKey, connectKey))
90
+ .limit(1)
91
+ return row ? rowToBot(row) : null
92
+ },
93
+
94
+ async findByConnectKeyAndStatus(connectKey, status) {
95
+ const [row] = await db
96
+ .select()
97
+ .from(schema.bots)
98
+ .where(
99
+ and(
100
+ eq(schema.bots.connectKey, connectKey),
101
+ eq(schema.bots.status, status),
102
+ ),
103
+ )
104
+ .limit(1)
105
+ return row ? rowToBot(row) : null
106
+ },
107
+
108
+ async findByIdAndStatus(id, status) {
109
+ const [row] = await db
110
+ .select()
111
+ .from(schema.bots)
112
+ .where(and(eq(schema.bots.id, id), eq(schema.bots.status, status)))
113
+ .limit(1)
114
+ return row ? rowToBot(row) : null
115
+ },
116
+
117
+ async findAll() {
118
+ const rows = await db.select().from(schema.bots).orderBy(schema.bots.createdAt)
119
+ return rows.map(rowToBot)
120
+ },
121
+
122
+ async findAllSummary() {
123
+ const rows = await db
124
+ .select({
125
+ id: schema.bots.id,
126
+ name: schema.bots.name,
127
+ profileImageUrl: schema.bots.profileImageUrl,
128
+ slackAppId: schema.bots.slackAppId,
129
+ slackTeamId: schema.bots.slackTeamId,
130
+ status: schema.bots.status,
131
+ createdAt: schema.bots.createdAt,
132
+ })
133
+ .from(schema.bots)
134
+ .orderBy(schema.bots.createdAt)
135
+ return rows
136
+ },
137
+
138
+ async create(bot) {
139
+ await db.insert(schema.bots).values({
140
+ id: bot.id,
141
+ name: bot.name,
142
+ botUsername: bot.botUsername,
143
+ profileImageUrl: bot.profileImageUrl,
144
+ connectKey: bot.connectKey,
145
+ slackAppId: bot.slackAppId,
146
+ slackTeamId: bot.slackTeamId,
147
+ botTokenEnc: bot.botTokenEnc,
148
+ signingSecretEnc: bot.signingSecretEnc,
149
+ clientId: bot.clientId,
150
+ clientSecretEnc: bot.clientSecretEnc,
151
+ manifestJson: bot.manifestJson,
152
+ status: bot.status,
153
+ })
154
+ },
155
+
156
+ async update(id, data) {
157
+ await db.update(schema.bots).set(data).where(eq(schema.bots.id, id))
158
+ },
159
+
160
+ async delete(id) {
161
+ await db.delete(schema.bots).where(eq(schema.bots.id, id))
162
+ },
163
+ }
164
+ }
165
+
166
+ function createConfigTokenRepository(db: MySQLDatabase): ConfigTokenRepository {
167
+ return {
168
+ async findByWorkspaceId(id) {
169
+ const [row] = await db
170
+ .select()
171
+ .from(schema.configTokens)
172
+ .where(eq(schema.configTokens.workspaceId, id))
173
+ .limit(1)
174
+ if (!row) return null
175
+ return {
176
+ workspaceId: row.workspaceId,
177
+ accessTokenEnc: row.accessTokenEnc,
178
+ refreshTokenEnc: row.refreshTokenEnc,
179
+ expiresAt: row.expiresAt,
180
+ updatedAt: row.updatedAt,
181
+ }
182
+ },
183
+
184
+ async findAll() {
185
+ const rows = await db.select().from(schema.configTokens)
186
+ return rows.map(
187
+ (row): ConfigTokenRow => ({
188
+ workspaceId: row.workspaceId,
189
+ accessTokenEnc: row.accessTokenEnc,
190
+ refreshTokenEnc: row.refreshTokenEnc,
191
+ expiresAt: row.expiresAt,
192
+ updatedAt: row.updatedAt,
193
+ }),
194
+ )
195
+ },
196
+
197
+ async upsert(row) {
198
+ await db
199
+ .insert(schema.configTokens)
200
+ .values({
201
+ workspaceId: row.workspaceId,
202
+ accessTokenEnc: row.accessTokenEnc,
203
+ refreshTokenEnc: row.refreshTokenEnc,
204
+ expiresAt: row.expiresAt,
205
+ })
206
+ .onDuplicateKeyUpdate({
207
+ set: {
208
+ accessTokenEnc: row.accessTokenEnc,
209
+ refreshTokenEnc: row.refreshTokenEnc,
210
+ expiresAt: row.expiresAt,
211
+ },
212
+ })
213
+ },
214
+ }
215
+ }
216
+
217
+ function createOAuthStateRepository(db: MySQLDatabase): OAuthStateRepository {
218
+ return {
219
+ async create(row) {
220
+ await db.insert(schema.oauthStates).values({
221
+ state: row.state,
222
+ botId: row.botId,
223
+ expiresAt: row.expiresAt,
224
+ })
225
+ },
226
+
227
+ async consume(state) {
228
+ const [row] = await db
229
+ .select()
230
+ .from(schema.oauthStates)
231
+ .where(eq(schema.oauthStates.state, state))
232
+ .limit(1)
233
+ if (!row) return null
234
+
235
+ if (row.expiresAt < new Date()) {
236
+ await db.delete(schema.oauthStates).where(eq(schema.oauthStates.state, state))
237
+ return null
238
+ }
239
+
240
+ await db.delete(schema.oauthStates).where(eq(schema.oauthStates.state, state))
241
+
242
+ return {
243
+ state: row.state,
244
+ botId: row.botId,
245
+ expiresAt: row.expiresAt,
246
+ }
247
+ },
248
+
249
+ async deleteExpired() {
250
+ await db.delete(schema.oauthStates).where(lt(schema.oauthStates.expiresAt, new Date()))
251
+ },
252
+ }
253
+ }
254
+
255
+ function createWorkspaceAdminConfigRepository(
256
+ db: MySQLDatabase,
257
+ ): WorkspaceAdminConfigRepository {
258
+ return {
259
+ async findByWorkspaceId(workspaceId) {
260
+ const [row] = await db
261
+ .select()
262
+ .from(schema.workspaceAdminConfig)
263
+ .where(eq(schema.workspaceAdminConfig.workspaceId, workspaceId))
264
+ .limit(1)
265
+ return row ? rowToWorkspaceAdminConfig(row) : null
266
+ },
267
+
268
+ async findAll() {
269
+ const rows = await db.select().from(schema.workspaceAdminConfig)
270
+ return rows.map(rowToWorkspaceAdminConfig)
271
+ },
272
+
273
+ async upsert(config) {
274
+ await db
275
+ .insert(schema.workspaceAdminConfig)
276
+ .values({
277
+ workspaceId: config.workspaceId,
278
+ slackClientId: config.slackClientId,
279
+ slackClientSecretEnc: config.slackClientSecretEnc,
280
+ dCookieEnc: config.dCookieEnc,
281
+ xoxcTokenEnc: config.xoxcTokenEnc,
282
+ workspaceDomain: config.workspaceDomain,
283
+ updatedByUserId: config.updatedByUserId,
284
+ })
285
+ .onDuplicateKeyUpdate({
286
+ set: {
287
+ slackClientId: config.slackClientId,
288
+ slackClientSecretEnc: config.slackClientSecretEnc,
289
+ dCookieEnc: config.dCookieEnc,
290
+ xoxcTokenEnc: config.xoxcTokenEnc,
291
+ workspaceDomain: config.workspaceDomain,
292
+ updatedByUserId: config.updatedByUserId,
293
+ updatedAt: new Date(),
294
+ },
295
+ })
296
+ },
297
+ }
298
+ }
299
+
300
+ // Re-export schema for drizzle-kit
301
+ export { TABLE_PREFIX, bots, configTokens, oauthStates, workspaceAdminConfig } from './schema.js'