141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
import { query, withTransaction } from '../../db/client.js'
|
|
import type { WorkerSessionRow } from './types.js'
|
|
|
|
export type CreateWorkerSessionInput = {
|
|
sessionId: string
|
|
workerId: number
|
|
deviceId: string
|
|
deviceType: string
|
|
deviceName: string
|
|
userAgent: string
|
|
ipAddress: string
|
|
issuedAt: string
|
|
expiresAt: string
|
|
now: string
|
|
maxDevices: number
|
|
}
|
|
|
|
export type CreateWorkerSessionResult = {
|
|
created: boolean
|
|
activeSessions: WorkerSessionRow[]
|
|
session: WorkerSessionRow | null
|
|
}
|
|
|
|
export async function createWorkerSessionRecord(
|
|
input: CreateWorkerSessionInput,
|
|
): Promise<CreateWorkerSessionResult> {
|
|
return withTransaction(async (client) => {
|
|
// 对账号加行锁,避免并发登录同时绕过设备数量限制。
|
|
await client.query('SELECT id FROM worker_users WHERE id = $1 FOR UPDATE', [input.workerId])
|
|
await client.query(
|
|
`UPDATE worker_sessions
|
|
SET status = 'revoked', revoked_at = $2, updated_at = $2
|
|
WHERE worker_id = $1 AND status = 'active' AND expires_at <= $2`,
|
|
[input.workerId, input.now],
|
|
)
|
|
await client.query(
|
|
`UPDATE worker_sessions
|
|
SET status = 'revoked', revoked_at = $3, updated_at = $3
|
|
WHERE worker_id = $1 AND device_id = $2 AND status = 'active'`,
|
|
[input.workerId, input.deviceId, input.now],
|
|
)
|
|
const activeResult = await client.query<WorkerSessionRow>(
|
|
`SELECT * FROM worker_sessions
|
|
WHERE worker_id = $1 AND device_type = $2 AND status = 'active' AND expires_at > $3
|
|
ORDER BY last_seen_at DESC, id DESC`,
|
|
[input.workerId, input.deviceType, input.now],
|
|
)
|
|
const activeSessions = activeResult.rows
|
|
if (activeSessions.length >= input.maxDevices) {
|
|
return { created: false, activeSessions, session: null }
|
|
}
|
|
const result = await client.query<WorkerSessionRow>(
|
|
`INSERT INTO worker_sessions (
|
|
session_id, worker_id, device_id, device_type, device_name,
|
|
user_agent, ip_address, status, issued_at, last_seen_at,
|
|
expires_at, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, 'active', $8, $8, $9, $10, $10)
|
|
RETURNING *`,
|
|
[
|
|
input.sessionId,
|
|
input.workerId,
|
|
input.deviceId,
|
|
input.deviceType,
|
|
input.deviceName,
|
|
input.userAgent,
|
|
input.ipAddress,
|
|
input.issuedAt,
|
|
input.expiresAt,
|
|
input.now,
|
|
],
|
|
)
|
|
return { created: true, activeSessions, session: result.rows[0] || null }
|
|
})
|
|
}
|
|
|
|
export async function getWorkerSessionBySessionId(
|
|
sessionId: string,
|
|
): Promise<WorkerSessionRow | null> {
|
|
const result = await query<WorkerSessionRow>(
|
|
'SELECT * FROM worker_sessions WHERE session_id = $1 LIMIT 1',
|
|
[String(sessionId || '').trim()],
|
|
)
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function touchWorkerSession(sessionId: string, now: string): Promise<boolean> {
|
|
const result = await query(
|
|
`UPDATE worker_sessions
|
|
SET last_seen_at = $2, updated_at = $2
|
|
WHERE session_id = $1 AND status = 'active' AND expires_at > $2`,
|
|
[String(sessionId || '').trim(), now],
|
|
)
|
|
return (result.rowCount || 0) > 0
|
|
}
|
|
|
|
export async function revokeWorkerSession(
|
|
workerId: number | string,
|
|
sessionId: string,
|
|
now: string,
|
|
): Promise<boolean> {
|
|
const result = await query(
|
|
`UPDATE worker_sessions
|
|
SET status = 'revoked', revoked_at = $3, updated_at = $3
|
|
WHERE worker_id = $1 AND session_id = $2 AND status = 'active'`,
|
|
[Number(workerId), String(sessionId || '').trim(), now],
|
|
)
|
|
return (result.rowCount || 0) > 0
|
|
}
|
|
|
|
export async function revokeAllWorkerSessions(
|
|
workerId: number | string,
|
|
now: string,
|
|
): Promise<number> {
|
|
const result = await query(
|
|
`UPDATE worker_sessions
|
|
SET status = 'revoked', revoked_at = $2, updated_at = $2
|
|
WHERE worker_id = $1 AND status = 'active'`,
|
|
[Number(workerId), now],
|
|
)
|
|
return result.rowCount || 0
|
|
}
|
|
|
|
export async function listWorkerSessions(
|
|
workerId: number | string,
|
|
now: string,
|
|
): Promise<WorkerSessionRow[]> {
|
|
await query(
|
|
`UPDATE worker_sessions
|
|
SET status = 'revoked', revoked_at = $2, updated_at = $2
|
|
WHERE worker_id = $1 AND status = 'active' AND expires_at <= $2`,
|
|
[Number(workerId), now],
|
|
)
|
|
const result = await query<WorkerSessionRow>(
|
|
`SELECT * FROM worker_sessions
|
|
WHERE worker_id = $1 AND status = 'active' AND expires_at > $2
|
|
ORDER BY device_type ASC, last_seen_at DESC, id DESC`,
|
|
[Number(workerId), now],
|
|
)
|
|
return result.rows
|
|
}
|