@coursebuilder/adapter-drizzle 0.0.1

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 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["src/schema.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,eAAO,MAAM,UAAU,0DAAiG,CAAA;AAExH,QAAA,MAAQ,KAAK,OAAE,QAAQ,OAAE,QAAQ,OAAE,eAAe,OAAE,uBAAuB,KAAkC,CAAA;AAE7G,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,uBAAuB,EAAE,CAAA;AAE9E,eAAO,MAAM,cAAc,KAOxB,CAAA;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBvB,CAAA;AAED,eAAO,MAAM,oBAAoB,KAE9B,CAAA;AAEH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBjB,CAAA;AAED,eAAO,MAAM,cAAc,KAExB,CAAA;AAEH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwBrB,CAAA;AAED,eAAO,MAAM,kBAAkB,KAG5B,CAAA;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwB3B,CAAA;AAED,eAAO,MAAM,wBAAwB,KAMlC,CAAA;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwB3B,CAAA;AAED,eAAO,MAAM,wBAAwB,KAMlC,CAAA;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BhC,CAAA;AAED,eAAO,MAAM,4BAA4B,KAOtC,CAAA;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyB7B,CAAA;AAED,eAAO,MAAM,0BAA0B,KAEpC,CAAA;AAEH,eAAO,MAAM,wBAAwB,KAKlC,CAAA;AAEH,eAAO,MAAM,gCAAgC,KAW1C,CAAA;AAEH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiBvC,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBhC,CAAA;AAED,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCpC,CAAA;AAED,eAAO,MAAM,iCAAiC,KAa3C,CAAA;AAEH,eAAO,MAAM,iBAAiB,KAE3B,CAAA;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAc9B,CAAA"}
package/schema.js ADDED
@@ -0,0 +1,318 @@
1
+ import { boolean, index, mysqlEnum, mysqlTableCreator, primaryKey, text, timestamp, varchar, } from 'drizzle-orm/mysql-core';
2
+ import { createMySqlTables, relations } from '@coursebuilder/adapter-drizzle';
3
+ /**
4
+ * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
5
+ * database instance for multiple projects.
6
+ *
7
+ * @see https://orm.drizzle.team/docs/goodies#multi-project-schema
8
+ */
9
+ export const mysqlTable = mysqlTableCreator((name) => `${process.env.NEXT_PUBLIC_APP_NAME || 'course_builder'}_${name}`);
10
+ const { users, accounts, sessions, contentResource, contentResourceResource } = createMySqlTables(mysqlTable);
11
+ export { users, accounts, sessions, contentResource, contentResourceResource };
12
+ export const usersRelations = relations(users, ({ many }) => ({
13
+ accounts: many(accounts),
14
+ communicationPreferences: many(communicationPreferences),
15
+ userRoles: many(userRoles),
16
+ userPermissions: many(userPermissions),
17
+ contributions: many(contentContributions),
18
+ createdContent: many(contentResource),
19
+ }));
20
+ export const permissions = mysqlTable('permission', {
21
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
22
+ name: varchar('name', { length: 255 }).notNull().unique(),
23
+ description: text('description'),
24
+ active: boolean('active').notNull().default(true),
25
+ createdAt: timestamp('createdAt', {
26
+ mode: 'date',
27
+ fsp: 3,
28
+ }).defaultNow(),
29
+ updatedAt: timestamp('updatedAt', {
30
+ mode: 'date',
31
+ fsp: 3,
32
+ }).defaultNow(),
33
+ deletedAt: timestamp('deletedAt', {
34
+ mode: 'date',
35
+ fsp: 3,
36
+ }),
37
+ }, (permission) => ({
38
+ nameIdx: index('name_idx').on(permission.name),
39
+ }));
40
+ export const permissionsRelations = relations(permissions, ({ many }) => ({
41
+ rolePermissions: many(rolePermissions),
42
+ }));
43
+ export const roles = mysqlTable('role', {
44
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
45
+ name: varchar('name', { length: 255 }).notNull().unique(),
46
+ description: text('description'),
47
+ active: boolean('active').notNull().default(true),
48
+ createdAt: timestamp('createdAt', {
49
+ mode: 'date',
50
+ fsp: 3,
51
+ }).defaultNow(),
52
+ updatedAt: timestamp('updatedAt', {
53
+ mode: 'date',
54
+ fsp: 3,
55
+ }).defaultNow(),
56
+ deletedAt: timestamp('deletedAt', {
57
+ mode: 'date',
58
+ fsp: 3,
59
+ }),
60
+ }, (role) => ({
61
+ nameIdx: index('name_idx').on(role.name),
62
+ }));
63
+ export const rolesRelations = relations(roles, ({ many }) => ({
64
+ userRoles: many(userRoles),
65
+ }));
66
+ export const userRoles = mysqlTable('userRole', {
67
+ userId: varchar('userId', { length: 255 }).notNull(),
68
+ roleId: varchar('roleId', { length: 255 }).notNull(),
69
+ active: boolean('active').notNull().default(true),
70
+ createdAt: timestamp('createdAt', {
71
+ mode: 'date',
72
+ fsp: 3,
73
+ }).defaultNow(),
74
+ updatedAt: timestamp('updatedAt', {
75
+ mode: 'date',
76
+ fsp: 3,
77
+ }).defaultNow(),
78
+ deletedAt: timestamp('deletedAt', {
79
+ mode: 'date',
80
+ fsp: 3,
81
+ }),
82
+ }, (ur) => ({
83
+ pk: primaryKey({ columns: [ur.userId, ur.roleId] }),
84
+ userIdIdx: index('userId_idx').on(ur.userId),
85
+ roleIdIdx: index('roleId_idx').on(ur.roleId),
86
+ }));
87
+ export const userRolesRelations = relations(userRoles, ({ one }) => ({
88
+ user: one(users, { fields: [userRoles.userId], references: [users.id] }),
89
+ role: one(roles, { fields: [userRoles.roleId], references: [roles.id] }),
90
+ }));
91
+ export const userPermissions = mysqlTable('userPermission', {
92
+ userId: varchar('userId', { length: 255 }).notNull(),
93
+ permissionId: varchar('permissionId', { length: 255 }).notNull(),
94
+ active: boolean('active').notNull().default(true),
95
+ createdAt: timestamp('createdAt', {
96
+ mode: 'date',
97
+ fsp: 3,
98
+ }).defaultNow(),
99
+ updatedAt: timestamp('updatedAt', {
100
+ mode: 'date',
101
+ fsp: 3,
102
+ }).defaultNow(),
103
+ deletedAt: timestamp('deletedAt', {
104
+ mode: 'date',
105
+ fsp: 3,
106
+ }),
107
+ }, (up) => ({
108
+ pk: primaryKey({ columns: [up.userId, up.permissionId] }),
109
+ userIdIdx: index('userId_idx').on(up.userId),
110
+ permissionIdIdx: index('permissionId_idx').on(up.permissionId),
111
+ }));
112
+ export const userPermissionsRelations = relations(userPermissions, ({ one }) => ({
113
+ user: one(users, { fields: [userPermissions.userId], references: [users.id] }),
114
+ permission: one(permissions, {
115
+ fields: [userPermissions.permissionId],
116
+ references: [permissions.id],
117
+ }),
118
+ }));
119
+ export const rolePermissions = mysqlTable('rolePermission', {
120
+ roleId: varchar('roleId', { length: 255 }).notNull(),
121
+ permissionId: varchar('permissionId', { length: 255 }).notNull(),
122
+ active: boolean('active').notNull().default(true),
123
+ createdAt: timestamp('createdAt', {
124
+ mode: 'date',
125
+ fsp: 3,
126
+ }).defaultNow(),
127
+ updatedAt: timestamp('updatedAt', {
128
+ mode: 'date',
129
+ fsp: 3,
130
+ }).defaultNow(),
131
+ deletedAt: timestamp('deletedAt', {
132
+ mode: 'date',
133
+ fsp: 3,
134
+ }),
135
+ }, (rp) => ({
136
+ pk: primaryKey({ columns: [rp.roleId, rp.permissionId] }),
137
+ roleIdIdx: index('roleId_idx').on(rp.roleId),
138
+ permissionIdIdx: index('permissionId_idx').on(rp.permissionId),
139
+ }));
140
+ export const rolePermissionsRelations = relations(rolePermissions, ({ one }) => ({
141
+ role: one(roles, { fields: [rolePermissions.roleId], references: [roles.id] }),
142
+ permission: one(permissions, {
143
+ fields: [rolePermissions.permissionId],
144
+ references: [permissions.id],
145
+ }),
146
+ }));
147
+ export const contentContributions = mysqlTable('contentContribution', {
148
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
149
+ userId: varchar('userId', { length: 255 }).notNull(),
150
+ contentId: varchar('contentId', { length: 255 }).notNull(),
151
+ contributionTypeId: varchar('contributionTypeId', { length: 255 }).notNull(),
152
+ active: boolean('active').notNull().default(true),
153
+ createdAt: timestamp('createdAt', {
154
+ mode: 'date',
155
+ fsp: 3,
156
+ }).defaultNow(),
157
+ updatedAt: timestamp('updatedAt', {
158
+ mode: 'date',
159
+ fsp: 3,
160
+ }).defaultNow(),
161
+ deletedAt: timestamp('deletedAt', {
162
+ mode: 'date',
163
+ fsp: 3,
164
+ }),
165
+ }, (cc) => ({
166
+ userIdIdx: index('userId_idx').on(cc.userId),
167
+ contentIdIdx: index('contentId_idx').on(cc.contentId),
168
+ contributionTypeIdIdx: index('contributionTypeId_idx').on(cc.contributionTypeId),
169
+ }));
170
+ export const contentContributionRelations = relations(contentContributions, ({ one }) => ({
171
+ user: one(users, { fields: [contentContributions.userId], references: [users.id] }),
172
+ content: one(contentResource, { fields: [contentContributions.contentId], references: [contentResource.id] }),
173
+ contributionType: one(contributionTypes, {
174
+ fields: [contentContributions.contributionTypeId],
175
+ references: [contributionTypes.id],
176
+ }),
177
+ }));
178
+ export const contributionTypes = mysqlTable('contributionType', {
179
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
180
+ slug: varchar('slug', { length: 255 }).notNull().unique(),
181
+ name: varchar('name', { length: 255 }).notNull(),
182
+ description: text('description'),
183
+ active: boolean('active').notNull().default(true),
184
+ createdAt: timestamp('createdAt', {
185
+ mode: 'date',
186
+ fsp: 3,
187
+ }).defaultNow(),
188
+ updatedAt: timestamp('updatedAt', {
189
+ mode: 'date',
190
+ fsp: 3,
191
+ }).defaultNow(),
192
+ deletedAt: timestamp('deletedAt', {
193
+ mode: 'date',
194
+ fsp: 3,
195
+ }),
196
+ }, (ct) => ({
197
+ nameIdx: index('name_idx').on(ct.name),
198
+ slugIdx: index('slug_idx').on(ct.slug),
199
+ }));
200
+ export const contributionTypesRelations = relations(contributionTypes, ({ many }) => ({
201
+ contributions: many(contentContributions),
202
+ }));
203
+ export const contentResourceRelations = relations(contentResource, ({ one, many }) => ({
204
+ createdBy: one(users, { fields: [contentResource.createdById], references: [users.id] }),
205
+ contributions: many(contentContributions),
206
+ resources: many(contentResourceResource, { relationName: 'resource' }),
207
+ resourceOf: many(contentResourceResource, { relationName: 'resourceOf' }),
208
+ }));
209
+ export const contentResourceResourceRelations = relations(contentResourceResource, ({ one }) => ({
210
+ resourceOf: one(contentResource, {
211
+ fields: [contentResourceResource.resourceOfId],
212
+ references: [contentResource.id],
213
+ relationName: 'resourceOf',
214
+ }),
215
+ resource: one(contentResource, {
216
+ fields: [contentResourceResource.resourceId],
217
+ references: [contentResource.id],
218
+ relationName: 'resource',
219
+ }),
220
+ }));
221
+ export const communicationPreferenceTypes = mysqlTable('communicationPreferenceType', {
222
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
223
+ name: varchar('name', { length: 255 }).notNull(),
224
+ description: text('description'),
225
+ active: boolean('active').notNull().default(true),
226
+ createdAt: timestamp('createdAt', {
227
+ mode: 'date',
228
+ fsp: 3,
229
+ }).defaultNow(),
230
+ updatedAt: timestamp('updatedAt', {
231
+ mode: 'date',
232
+ fsp: 3,
233
+ }),
234
+ deletedAt: timestamp('deletedAt', {
235
+ mode: 'date',
236
+ fsp: 3,
237
+ }),
238
+ });
239
+ export const communicationChannel = mysqlTable('communicationChannel', {
240
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
241
+ name: varchar('name', { length: 255 }).notNull(),
242
+ description: text('description'),
243
+ active: boolean('active').notNull().default(true),
244
+ createdAt: timestamp('createdAt', {
245
+ mode: 'date',
246
+ fsp: 3,
247
+ }).defaultNow(),
248
+ updatedAt: timestamp('updatedAt', {
249
+ mode: 'date',
250
+ fsp: 3,
251
+ }).defaultNow(),
252
+ deletedAt: timestamp('deletedAt', {
253
+ mode: 'date',
254
+ fsp: 3,
255
+ }),
256
+ }, (cc) => ({
257
+ nameIdx: index('name_idx').on(cc.name),
258
+ }));
259
+ export const communicationPreferences = mysqlTable('communicationPreference', {
260
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
261
+ userId: varchar('userId', { length: 255 }).notNull(),
262
+ channelId: varchar('channelId', { length: 255 }).notNull(),
263
+ preferenceLevel: mysqlEnum('preferenceLevel', ['low', 'medium', 'high']).notNull().default('medium'),
264
+ preferenceTypeId: varchar('preferenceTypeId', { length: 255 }).notNull(),
265
+ active: boolean('active').notNull().default(true),
266
+ createdAt: timestamp('createdAt', {
267
+ mode: 'date',
268
+ fsp: 3,
269
+ }).defaultNow(),
270
+ optInAt: timestamp('optInAt', {
271
+ mode: 'date',
272
+ fsp: 3,
273
+ }),
274
+ optOutAt: timestamp('optOutAt', {
275
+ mode: 'date',
276
+ fsp: 3,
277
+ }),
278
+ updatedAt: timestamp('updatedAt', {
279
+ mode: 'date',
280
+ fsp: 3,
281
+ }).defaultNow(),
282
+ deletedAt: timestamp('deletedAt', {
283
+ mode: 'date',
284
+ fsp: 3,
285
+ }),
286
+ }, (cp) => ({
287
+ userIdIdx: index('userId_idx').on(cp.userId),
288
+ preferenceTypeIdx: index('preferenceTypeId_idx').on(cp.preferenceTypeId),
289
+ channelIdIdx: index('channelId_idx').on(cp.channelId),
290
+ }));
291
+ export const communicationPreferencesRelations = relations(communicationPreferences, ({ one }) => ({
292
+ user: one(users, {
293
+ fields: [communicationPreferences.userId],
294
+ references: [users.id],
295
+ }),
296
+ channel: one(communicationChannel, {
297
+ fields: [communicationPreferences.channelId],
298
+ references: [communicationChannel.id],
299
+ }),
300
+ preferenceType: one(communicationPreferenceTypes, {
301
+ fields: [communicationPreferences.preferenceTypeId],
302
+ references: [communicationPreferenceTypes.id],
303
+ }),
304
+ }));
305
+ export const sessionsRelations = relations(sessions, ({ one }) => ({
306
+ user: one(users, { fields: [sessions.userId], references: [users.id] }),
307
+ }));
308
+ export const verificationTokens = mysqlTable('verificationToken', {
309
+ identifier: varchar('identifier', { length: 255 }).notNull(),
310
+ token: varchar('token', { length: 255 }).notNull(),
311
+ expires: timestamp('expires', { mode: 'date' }).notNull(),
312
+ createdAt: timestamp('createdAt', {
313
+ mode: 'date',
314
+ fsp: 3,
315
+ }).defaultNow(),
316
+ }, (vt) => ({
317
+ pk: primaryKey({ columns: [vt.identifier, vt.token] }),
318
+ }));
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { is } from 'drizzle-orm'
2
+ import { MySqlDatabase, MySqlTableFn } from 'drizzle-orm/mysql-core'
3
+ import { PgDatabase, PgTableFn } from 'drizzle-orm/pg-core'
4
+ import { BaseSQLiteDatabase, SQLiteTableFn } from 'drizzle-orm/sqlite-core'
5
+
6
+ import { createTables as createMySqlTables, mySqlDrizzleAdapter } from './lib/mysql.js'
7
+ import { pgDrizzleAdapter } from './lib/pg.js'
8
+ import { SQLiteDrizzleAdapter } from './lib/sqlite.js'
9
+ import { SqlFlavorOptions, TableFn } from './lib/utils.js'
10
+
11
+ type NO_IT_AINT = any
12
+
13
+ export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
14
+ db: SqlFlavor,
15
+ table?: TableFn<SqlFlavor>,
16
+ ): NO_IT_AINT {
17
+ if (is(db, MySqlDatabase)) {
18
+ return mySqlDrizzleAdapter(db, table as MySqlTableFn)
19
+ } else if (is(db, PgDatabase)) {
20
+ return pgDrizzleAdapter(db, table as PgTableFn)
21
+ } else if (is(db, BaseSQLiteDatabase)) {
22
+ return SQLiteDrizzleAdapter(db, table as SQLiteTableFn)
23
+ }
24
+
25
+ throw new Error(`Unsupported database type (${typeof db}) in Auth.js Drizzle adapter.`)
26
+ }
27
+
28
+ export { createMySqlTables }
@@ -0,0 +1,344 @@
1
+ import type { AdapterAccount, AdapterSession, AdapterUser } from '@auth/core/adapters'
2
+ import { addSeconds, isAfter } from 'date-fns'
3
+ import { and, eq, sql } from 'drizzle-orm'
4
+ import {
5
+ mysqlTable as defaultMySqlTableFn,
6
+ double,
7
+ index,
8
+ int,
9
+ json,
10
+ MySqlDatabase,
11
+ mysqlEnum,
12
+ MySqlTableFn,
13
+ primaryKey,
14
+ timestamp,
15
+ varchar,
16
+ } from 'drizzle-orm/mysql-core'
17
+
18
+ import { CourseBuilderAdapter } from '@coursebuilder/core/adapters'
19
+
20
+ export function createTables(mySqlTable: MySqlTableFn) {
21
+ const users = mySqlTable('user', {
22
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
23
+ name: varchar('name', { length: 255 }),
24
+ email: varchar('email', { length: 255 }).notNull(),
25
+ role: mysqlEnum('role', ['user', 'admin']).default('user'),
26
+ emailVerified: timestamp('emailVerified', {
27
+ mode: 'date',
28
+ fsp: 3,
29
+ }).defaultNow(),
30
+ image: varchar('image', { length: 255 }),
31
+ })
32
+
33
+ const accounts = mySqlTable(
34
+ 'account',
35
+ {
36
+ userId: varchar('userId', { length: 255 })
37
+ .notNull()
38
+ .references(() => users.id, { onDelete: 'cascade' }),
39
+ type: varchar('type', { length: 255 }).$type<AdapterAccount['type']>().notNull(),
40
+ provider: varchar('provider', { length: 255 }).notNull(),
41
+ providerAccountId: varchar('providerAccountId', {
42
+ length: 255,
43
+ }).notNull(),
44
+ refresh_token: varchar('refresh_token', { length: 255 }),
45
+ access_token: varchar('access_token', { length: 255 }),
46
+ expires_at: int('expires_at'),
47
+ token_type: varchar('token_type', { length: 255 }),
48
+ scope: varchar('scope', { length: 255 }),
49
+ id_token: varchar('id_token', { length: 255 }),
50
+ session_state: varchar('session_state', { length: 255 }),
51
+ },
52
+ (account) => ({
53
+ pk: primaryKey({ columns: [account.provider, account.providerAccountId] }),
54
+ userIdIdx: index('userId_idx').on(account.userId),
55
+ }),
56
+ )
57
+
58
+ const sessions = mySqlTable(
59
+ 'session',
60
+ {
61
+ sessionToken: varchar('sessionToken', { length: 255 }).notNull().primaryKey(),
62
+ userId: varchar('userId', { length: 255 })
63
+ .notNull()
64
+ .references(() => users.id, { onDelete: 'cascade' }),
65
+ expires: timestamp('expires', { mode: 'date' }).notNull(),
66
+ },
67
+ (session) => ({
68
+ userIdIdx: index('userId_idx').on(session.userId),
69
+ }),
70
+ )
71
+
72
+ const verificationTokens = mySqlTable(
73
+ 'verificationToken',
74
+ {
75
+ identifier: varchar('identifier', { length: 255 }).notNull(),
76
+ token: varchar('token', { length: 255 }).notNull(),
77
+ expires: timestamp('expires', { mode: 'date' }).notNull(),
78
+ createdAt: timestamp('createdAt', {
79
+ mode: 'date',
80
+ fsp: 3,
81
+ }).default(sql`CURRENT_TIMESTAMP(3)`),
82
+ },
83
+ (vt) => ({
84
+ pk: primaryKey({ columns: [vt.identifier, vt.token] }),
85
+ }),
86
+ )
87
+
88
+ const contentResource = mySqlTable(
89
+ 'contentResource',
90
+ {
91
+ id: varchar('id', { length: 255 }).notNull().primaryKey(),
92
+ type: varchar('type', { length: 255 }).notNull(),
93
+ createdById: varchar('createdById', { length: 255 }).notNull(),
94
+ fields: json('fields').$type<Record<string, any>>().default({}),
95
+ createdAt: timestamp('createdAt', {
96
+ mode: 'date',
97
+ fsp: 3,
98
+ }).defaultNow(),
99
+ updatedAt: timestamp('updatedAt', {
100
+ mode: 'date',
101
+ fsp: 3,
102
+ }).defaultNow(),
103
+ deletedAt: timestamp('deletedAt', {
104
+ mode: 'date',
105
+ fsp: 3,
106
+ }),
107
+ },
108
+ (cm) => ({
109
+ typeIdx: index('type_idx').on(cm.type),
110
+ createdByIdx: index('createdById_idx').on(cm.createdById),
111
+ createdAtIdx: index('createdAt_idx').on(cm.createdAt),
112
+ }),
113
+ )
114
+
115
+ const contentResourceResource = mySqlTable(
116
+ 'contentResourceResource',
117
+ {
118
+ resourceOfId: varchar('resourceOfId', { length: 255 }).notNull(),
119
+ resourceId: varchar('resourceId', { length: 255 }).notNull(),
120
+ position: double('position').notNull().default(0),
121
+ metadata: json('fields').$type<Record<string, any>>().default({}),
122
+ createdAt: timestamp('createdAt', {
123
+ mode: 'date',
124
+ fsp: 3,
125
+ }).defaultNow(),
126
+ updatedAt: timestamp('updatedAt', {
127
+ mode: 'date',
128
+ fsp: 3,
129
+ }).defaultNow(),
130
+ deletedAt: timestamp('deletedAt', {
131
+ mode: 'date',
132
+ fsp: 3,
133
+ }),
134
+ },
135
+ (crr) => ({
136
+ pk: primaryKey({ columns: [crr.resourceOfId, crr.resourceId] }),
137
+ contentResourceIdIdx: index('contentResourceId_idx').on(crr.resourceOfId),
138
+ resourceIdIdx: index('resourceId_idx').on(crr.resourceId),
139
+ }),
140
+ )
141
+
142
+ return { users, accounts, sessions, verificationTokens, contentResource, contentResourceResource }
143
+ }
144
+
145
+ export type DefaultSchema = ReturnType<typeof createTables>
146
+
147
+ export function mySqlDrizzleAdapter(
148
+ client: InstanceType<typeof MySqlDatabase>,
149
+ tableFn = defaultMySqlTableFn,
150
+ ): CourseBuilderAdapter {
151
+ const { users, accounts, sessions, verificationTokens, contentResource } = createTables(tableFn)
152
+
153
+ return {
154
+ async createContentResource(data) {
155
+ const id = crypto.randomUUID()
156
+
157
+ await client.insert(contentResource).values({ ...data, id })
158
+
159
+ return await client
160
+ .select()
161
+ .from(contentResource)
162
+ .where(eq(contentResource.id, id))
163
+ .then((res) => res[0])
164
+ },
165
+ async getContentResource(data) {
166
+ const thing =
167
+ (await client
168
+ .select()
169
+ .from(contentResource)
170
+ .where(eq(contentResource.id, data))
171
+ .then((res) => res[0])) ?? null
172
+
173
+ return thing
174
+ },
175
+ async createUser(data) {
176
+ const id = crypto.randomUUID()
177
+
178
+ await client.insert(users).values({ ...data, id })
179
+
180
+ return await client
181
+ .select()
182
+ .from(users)
183
+ .where(eq(users.id, id))
184
+ .then((res) => res[0] as AdapterUser)
185
+ },
186
+ async getUser(data) {
187
+ const thing =
188
+ (await client
189
+ .select()
190
+ .from(users)
191
+ .where(eq(users.id, data))
192
+ .then((res) => res[0])) ?? null
193
+
194
+ return thing
195
+ },
196
+ async getUserByEmail(data) {
197
+ const user =
198
+ (await client
199
+ .select()
200
+ .from(users)
201
+ .where(eq(users.email, data))
202
+ .then((res) => res[0])) ?? null
203
+
204
+ return user
205
+ },
206
+ async createSession(data) {
207
+ await client.insert(sessions).values(data)
208
+
209
+ return await client
210
+ .select()
211
+ .from(sessions)
212
+ .where(eq(sessions.sessionToken, data.sessionToken))
213
+ .then((res) => res[0] as AdapterSession)
214
+ },
215
+ async getSessionAndUser(data) {
216
+ const sessionAndUser =
217
+ (await client
218
+ .select({
219
+ session: sessions,
220
+ user: users,
221
+ })
222
+ .from(sessions)
223
+ .where(eq(sessions.sessionToken, data))
224
+ .innerJoin(users, eq(users.id, sessions.userId))
225
+ .then((res) => res[0])) ?? null
226
+
227
+ return sessionAndUser
228
+ },
229
+ async updateUser(data) {
230
+ if (!data.id) {
231
+ throw new Error('No user id.')
232
+ }
233
+
234
+ await client.update(users).set(data).where(eq(users.id, data.id))
235
+
236
+ return await client
237
+ .select()
238
+ .from(users)
239
+ .where(eq(users.id, data.id))
240
+ .then((res) => res[0] as AdapterUser)
241
+ },
242
+ async updateSession(data) {
243
+ await client.update(sessions).set(data).where(eq(sessions.sessionToken, data.sessionToken))
244
+
245
+ return await client
246
+ .select()
247
+ .from(sessions)
248
+ .where(eq(sessions.sessionToken, data.sessionToken))
249
+ .then((res) => res[0])
250
+ },
251
+ async linkAccount(rawAccount) {
252
+ await client.insert(accounts).values(rawAccount)
253
+ },
254
+ async getUserByAccount(account) {
255
+ const dbAccount =
256
+ (await client
257
+ .select()
258
+ .from(accounts)
259
+ .where(
260
+ and(eq(accounts.providerAccountId, account.providerAccountId), eq(accounts.provider, account.provider)),
261
+ )
262
+ .leftJoin(users, eq(accounts.userId, users.id))
263
+ .then((res) => res[0])) ?? null
264
+
265
+ if (!dbAccount) {
266
+ return null
267
+ }
268
+
269
+ return dbAccount.user
270
+ },
271
+ async deleteSession(sessionToken) {
272
+ const session =
273
+ (await client
274
+ .select()
275
+ .from(sessions)
276
+ .where(eq(sessions.sessionToken, sessionToken))
277
+ .then((res) => res[0])) ?? null
278
+
279
+ await client.delete(sessions).where(eq(sessions.sessionToken, sessionToken))
280
+
281
+ return session
282
+ },
283
+ async createVerificationToken(token) {
284
+ await client.insert(verificationTokens).values(token)
285
+
286
+ return await client
287
+ .select()
288
+ .from(verificationTokens)
289
+ .where(eq(verificationTokens.identifier, token.identifier))
290
+ .then((res) => res[0])
291
+ },
292
+ async useVerificationToken(token) {
293
+ try {
294
+ const deletedToken =
295
+ (await client
296
+ .select()
297
+ .from(verificationTokens)
298
+ .where(and(eq(verificationTokens.identifier, token.identifier), eq(verificationTokens.token, token.token)))
299
+ .then((res) => res[0])) ?? null
300
+
301
+ if (deletedToken?.createdAt) {
302
+ const TIMEOUT_IN_SECONDS = 90
303
+ const expireMultipleClicks = addSeconds(deletedToken.createdAt, TIMEOUT_IN_SECONDS)
304
+ const now = new Date()
305
+
306
+ if (isAfter(expireMultipleClicks, now)) {
307
+ // @ts-ignore
308
+ const { id: _, ...verificationToken } = token
309
+ return deletedToken
310
+ } else {
311
+ await client
312
+ .delete(verificationTokens)
313
+ .where(
314
+ and(eq(verificationTokens.identifier, token.identifier), eq(verificationTokens.token, token.token)),
315
+ )
316
+ return deletedToken
317
+ }
318
+ }
319
+
320
+ return deletedToken
321
+ } catch (err) {
322
+ throw new Error('No verification token found.')
323
+ }
324
+ },
325
+ async deleteUser(id) {
326
+ const user = await client
327
+ .select()
328
+ .from(users)
329
+ .where(eq(users.id, id))
330
+ .then((res) => res[0] ?? null)
331
+
332
+ await client.delete(users).where(eq(users.id, id))
333
+
334
+ return user
335
+ },
336
+ async unlinkAccount(account) {
337
+ await client
338
+ .delete(accounts)
339
+ .where(and(eq(accounts.providerAccountId, account.providerAccountId), eq(accounts.provider, account.provider)))
340
+
341
+ return undefined
342
+ },
343
+ }
344
+ }