mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 00:48:06 +00:00
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
292 lines
9.8 KiB
TypeScript
292 lines
9.8 KiB
TypeScript
import { relations } from 'drizzle-orm'
|
|
import { boolean, index, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core'
|
|
|
|
export const user = pgTable('user', {
|
|
id: text('id').primaryKey(),
|
|
name: text('name').notNull(),
|
|
email: text('email').notNull().unique(),
|
|
emailVerified: boolean('email_verified').default(false).notNull(),
|
|
image: text('image'),
|
|
// better-auth `admin` plugin fields. Field names must match the plugin's
|
|
// schema (role/banned/banReason/banExpires) so its drizzle adapter resolves
|
|
// them. `role` gates /admin/* (role === 'admin'); `banned` is enforced by the
|
|
// plugin at session.create.before AND re-checked on the OIDC JWT hot path
|
|
// (resolveRequestAuth / userinfo guard). Roles are granted out-of-band
|
|
// (manual DB update) — there is no env allowlist anymore.
|
|
role: text('role'),
|
|
banned: boolean('banned').default(false),
|
|
banReason: text('ban_reason'),
|
|
banExpires: timestamp('ban_expires'),
|
|
// NOTICE:
|
|
// Touched in `databaseHooks.session.create.after` (see auth.ts) which
|
|
// fires on sign-in AND on every OIDC access-token refresh (~hourly), so
|
|
// this is effectively "last activity" for any user with a live client.
|
|
// Better Auth has `session.updatedAt` but that's per-session-row; we
|
|
// want one stable per-user timestamp for DAU-style queries without
|
|
// joining/aggregating session rows. Nullable so existing rows backfill
|
|
// lazily on next login instead of needing a migration-time seed.
|
|
lastSeenAt: timestamp('last_seen_at'),
|
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
updatedAt: timestamp('updated_at')
|
|
.defaultNow()
|
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
.notNull(),
|
|
})
|
|
|
|
export const session = pgTable(
|
|
'session',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
expiresAt: timestamp('expires_at').notNull(),
|
|
token: text('token').notNull().unique(),
|
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
updatedAt: timestamp('updated_at')
|
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
.notNull(),
|
|
ipAddress: text('ip_address'),
|
|
userAgent: text('user_agent'),
|
|
userId: text('user_id')
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: 'cascade' }),
|
|
// better-auth `admin` plugin: set when this session is an admin
|
|
// impersonating the user. Impersonation endpoints are disabled via
|
|
// disabledPaths, but the column stays so the schema matches the plugin.
|
|
impersonatedBy: text('impersonated_by'),
|
|
},
|
|
table => [
|
|
index('session_userId_idx').on(table.userId),
|
|
index('session_expires_at_idx').on(table.expiresAt),
|
|
],
|
|
)
|
|
|
|
export const account = pgTable(
|
|
'account',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
accountId: text('account_id').notNull(),
|
|
providerId: text('provider_id').notNull(),
|
|
userId: text('user_id')
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: 'cascade' }),
|
|
accessToken: text('access_token'),
|
|
refreshToken: text('refresh_token'),
|
|
idToken: text('id_token'),
|
|
accessTokenExpiresAt: timestamp('access_token_expires_at'),
|
|
refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
|
|
scope: text('scope'),
|
|
password: text('password'),
|
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
updatedAt: timestamp('updated_at')
|
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
.notNull(),
|
|
},
|
|
table => [
|
|
index('account_userId_idx').on(table.userId),
|
|
index('account_account_id_provider_id_idx').on(table.accountId, table.providerId),
|
|
],
|
|
)
|
|
|
|
export const verification = pgTable(
|
|
'verification',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
identifier: text('identifier').notNull(),
|
|
value: text('value').notNull(),
|
|
expiresAt: timestamp('expires_at').notNull(),
|
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
updatedAt: timestamp('updated_at')
|
|
.defaultNow()
|
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
.notNull(),
|
|
},
|
|
table => [index('verification_identifier_idx').on(table.identifier)],
|
|
)
|
|
|
|
export const jwks = pgTable('jwks', {
|
|
id: text('id').primaryKey(),
|
|
publicKey: text('public_key').notNull(),
|
|
privateKey: text('private_key').notNull(),
|
|
createdAt: timestamp('created_at').notNull(),
|
|
expiresAt: timestamp('expires_at'),
|
|
})
|
|
|
|
export const oauthClient = pgTable('oauth_client', {
|
|
id: text('id').primaryKey(),
|
|
clientId: text('client_id').notNull().unique(),
|
|
clientSecret: text('client_secret'),
|
|
disabled: boolean('disabled').default(false),
|
|
skipConsent: boolean('skip_consent'),
|
|
enableEndSession: boolean('enable_end_session'),
|
|
subjectType: text('subject_type'),
|
|
scopes: text('scopes').array(),
|
|
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
|
|
createdAt: timestamp('created_at'),
|
|
updatedAt: timestamp('updated_at'),
|
|
name: text('name'),
|
|
uri: text('uri'),
|
|
icon: text('icon'),
|
|
contacts: text('contacts').array(),
|
|
tos: text('tos'),
|
|
policy: text('policy'),
|
|
softwareId: text('software_id'),
|
|
softwareVersion: text('software_version'),
|
|
softwareStatement: text('software_statement'),
|
|
redirectUris: text('redirect_uris').array().notNull(),
|
|
postLogoutRedirectUris: text('post_logout_redirect_uris').array(),
|
|
tokenEndpointAuthMethod: text('token_endpoint_auth_method'),
|
|
grantTypes: text('grant_types').array(),
|
|
responseTypes: text('response_types').array(),
|
|
public: boolean('public'),
|
|
type: text('type'),
|
|
requirePKCE: boolean('require_pkce'),
|
|
referenceId: text('reference_id'),
|
|
metadata: jsonb('metadata'),
|
|
})
|
|
|
|
export const oauthRefreshToken = pgTable(
|
|
'oauth_refresh_token',
|
|
{
|
|
id: text('id').primaryKey(),
|
|
token: text('token').notNull(),
|
|
clientId: text('client_id')
|
|
.notNull()
|
|
.references(() => oauthClient.clientId, { onDelete: 'cascade' }),
|
|
sessionId: text('session_id').references(() => session.id, {
|
|
onDelete: 'set null',
|
|
}),
|
|
userId: text('user_id')
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: 'cascade' }),
|
|
referenceId: text('reference_id'),
|
|
expiresAt: timestamp('expires_at'),
|
|
createdAt: timestamp('created_at'),
|
|
revoked: timestamp('revoked'),
|
|
authTime: timestamp('auth_time'),
|
|
scopes: text('scopes').array().notNull(),
|
|
},
|
|
table => [
|
|
index('oauth_refresh_token_token_idx').on(table.token),
|
|
index('oauth_refresh_token_user_id_idx').on(table.userId),
|
|
index('oauth_refresh_token_session_id_idx').on(table.sessionId),
|
|
index('oauth_refresh_token_client_id_idx').on(table.clientId),
|
|
],
|
|
)
|
|
|
|
export const oauthAccessToken = pgTable('oauth_access_token', {
|
|
id: text('id').primaryKey(),
|
|
token: text('token').unique(),
|
|
clientId: text('client_id')
|
|
.notNull()
|
|
.references(() => oauthClient.clientId, { onDelete: 'cascade' }),
|
|
sessionId: text('session_id').references(() => session.id, {
|
|
onDelete: 'set null',
|
|
}),
|
|
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
|
|
referenceId: text('reference_id'),
|
|
refreshId: text('refresh_id').references(() => oauthRefreshToken.id, {
|
|
onDelete: 'cascade',
|
|
}),
|
|
expiresAt: timestamp('expires_at'),
|
|
createdAt: timestamp('created_at'),
|
|
scopes: text('scopes').array().notNull(),
|
|
})
|
|
|
|
export const oauthConsent = pgTable('oauth_consent', {
|
|
id: text('id').primaryKey(),
|
|
clientId: text('client_id')
|
|
.notNull()
|
|
.references(() => oauthClient.clientId, { onDelete: 'cascade' }),
|
|
userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
|
|
referenceId: text('reference_id'),
|
|
scopes: text('scopes').array().notNull(),
|
|
createdAt: timestamp('created_at'),
|
|
updatedAt: timestamp('updated_at'),
|
|
})
|
|
|
|
export const userRelations = relations(user, ({ many }) => ({
|
|
sessions: many(session),
|
|
accounts: many(account),
|
|
oauthClients: many(oauthClient),
|
|
oauthRefreshTokens: many(oauthRefreshToken),
|
|
oauthAccessTokens: many(oauthAccessToken),
|
|
oauthConsents: many(oauthConsent),
|
|
}))
|
|
|
|
export const sessionRelations = relations(session, ({ one, many }) => ({
|
|
user: one(user, {
|
|
fields: [session.userId],
|
|
references: [user.id],
|
|
}),
|
|
oauthRefreshTokens: many(oauthRefreshToken),
|
|
oauthAccessTokens: many(oauthAccessToken),
|
|
}))
|
|
|
|
export const accountRelations = relations(account, ({ one }) => ({
|
|
user: one(user, {
|
|
fields: [account.userId],
|
|
references: [user.id],
|
|
}),
|
|
}))
|
|
|
|
export const oauthClientRelations = relations(oauthClient, ({ one, many }) => ({
|
|
user: one(user, {
|
|
fields: [oauthClient.userId],
|
|
references: [user.id],
|
|
}),
|
|
oauthRefreshTokens: many(oauthRefreshToken),
|
|
oauthAccessTokens: many(oauthAccessToken),
|
|
oauthConsents: many(oauthConsent),
|
|
}))
|
|
|
|
export const oauthRefreshTokenRelations = relations(
|
|
oauthRefreshToken,
|
|
({ one, many }) => ({
|
|
oauthClient: one(oauthClient, {
|
|
fields: [oauthRefreshToken.clientId],
|
|
references: [oauthClient.clientId],
|
|
}),
|
|
session: one(session, {
|
|
fields: [oauthRefreshToken.sessionId],
|
|
references: [session.id],
|
|
}),
|
|
user: one(user, {
|
|
fields: [oauthRefreshToken.userId],
|
|
references: [user.id],
|
|
}),
|
|
oauthAccessTokens: many(oauthAccessToken),
|
|
}),
|
|
)
|
|
|
|
export const oauthAccessTokenRelations = relations(
|
|
oauthAccessToken,
|
|
({ one }) => ({
|
|
oauthClient: one(oauthClient, {
|
|
fields: [oauthAccessToken.clientId],
|
|
references: [oauthClient.clientId],
|
|
}),
|
|
session: one(session, {
|
|
fields: [oauthAccessToken.sessionId],
|
|
references: [session.id],
|
|
}),
|
|
user: one(user, {
|
|
fields: [oauthAccessToken.userId],
|
|
references: [user.id],
|
|
}),
|
|
oauthRefreshToken: one(oauthRefreshToken, {
|
|
fields: [oauthAccessToken.refreshId],
|
|
references: [oauthRefreshToken.id],
|
|
}),
|
|
}),
|
|
)
|
|
|
|
export const oauthConsentRelations = relations(oauthConsent, ({ one }) => ({
|
|
oauthClient: one(oauthClient, {
|
|
fields: [oauthConsent.clientId],
|
|
references: [oauthClient.clientId],
|
|
}),
|
|
user: one(user, {
|
|
fields: [oauthConsent.userId],
|
|
references: [user.id],
|
|
}),
|
|
}))
|