feat(auth): add lastSeenAt field to user schema and tests

This commit is contained in:
RainbowBird
2026-08-17 21:57:25 +08:00
parent bd790098d2
commit 5ea7bb1083
5 changed files with 52 additions and 8 deletions
+2
View File
@@ -152,3 +152,5 @@ docs/ai/context/verifications/
# Generated from packages/i18n/glossary/terms.yaml at upload time. Never committed, so it
# cannot drift from its source.
packages/i18n/glossary/glossary.tbx
/airi-docs/
@@ -0,0 +1,13 @@
import { user } from '@proj-airi/auth-shared'
import { getTableColumns } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
describe('better Auth schema contract', () => {
it('keeps the application-owned last_seen_at user column', () => {
const columns = getTableColumns(user)
expect(columns.lastSeenAt).toBeDefined()
expect(columns.lastSeenAt.name).toBe('last_seen_at')
expect(columns.lastSeenAt.notNull).toBe(false)
})
})
+11
View File
@@ -542,6 +542,17 @@ export function createAuth(
},
user: {
// Keep this server-managed activity field in Better Auth's declared
// schema so `better-auth generate` preserves it in auth-shared.
// Session creation updates it below; clients must never supply it.
additionalFields: {
lastSeenAt: {
type: 'date',
required: false,
input: false,
returned: true,
},
},
changeEmail: {
enabled: true,
// NOTICE:
+26
View File
@@ -3,6 +3,7 @@ import type { AuthEnv } from '../env'
import { generateKeyPairSync } from 'node:crypto'
import { getAuthTables } from 'better-auth/db'
import { decodeJwt, decodeProtectedHeader, importSPKI, jwtVerify } from 'jose'
import { describe, expect, it, vi } from 'vitest'
@@ -59,6 +60,31 @@ describe('createAuth', () => {
expect(auth.options.account?.accountLinking?.allowDifferentEmails).toBe(true)
})
it('registers lastSeenAt as a server-managed Better Auth user field', () => {
const auth = createAuth({} as unknown as AuthDatabase, {
PUBLIC_URL: 'http://localhost:3000',
AUTH_GOOGLE_CLIENT_ID: 'google-client',
AUTH_GOOGLE_CLIENT_SECRET: 'google-secret',
AUTH_GITHUB_CLIENT_ID: 'github-client',
AUTH_GITHUB_CLIENT_SECRET: 'github-secret',
BETTER_AUTH_SECRET: 'test-secret-test-secret-test-secret',
ADDITIONAL_TRUSTED_ORIGINS: [],
} as unknown as AuthEnv)
expect(auth.options.user?.additionalFields?.lastSeenAt).toMatchObject({
type: 'date',
required: false,
input: false,
returned: true,
})
expect(getAuthTables(auth.options).user.fields.lastSeenAt).toMatchObject({
type: 'date',
required: false,
input: false,
returned: true,
})
})
it('asks social providers to show the account picker during OAuth authorization', () => {
const auth = createAuth({} as unknown as AuthDatabase, {
PUBLIC_URL: 'http://localhost:3000',
@@ -13,14 +13,6 @@ export const user = pgTable('user', {
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')