183 lines
4.1 KiB
TypeScript
183 lines
4.1 KiB
TypeScript
import { query } from '../db/client.js'
|
|
|
|
type AdminUserRow = {
|
|
id: number
|
|
username: string
|
|
password_hash: string
|
|
role: string
|
|
status: string
|
|
session_version: number
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
type AdminUserCreateInput = {
|
|
username: string
|
|
passwordHash: string
|
|
role: string
|
|
status: string
|
|
createdAt: string
|
|
updatedAt: string
|
|
}
|
|
|
|
type AdminUserPatch = Partial<Pick<
|
|
AdminUserRow,
|
|
'username' | 'password_hash' | 'role' | 'status' | 'session_version' | 'updated_at'
|
|
>>
|
|
|
|
type AdminUserListInput = {
|
|
page?: number
|
|
pageSize?: number
|
|
username?: string
|
|
role?: string
|
|
status?: string
|
|
}
|
|
|
|
type AdminUserListResult = {
|
|
items: AdminUserRow[]
|
|
total: number
|
|
}
|
|
|
|
const ADMIN_USER_SELECT = `
|
|
SELECT au.*
|
|
FROM admin_users au
|
|
`
|
|
|
|
export async function getAdminUserById(userId: number | string): Promise<AdminUserRow | null> {
|
|
const result = await query<AdminUserRow>(
|
|
`${ADMIN_USER_SELECT} WHERE au.id = $1 LIMIT 1`,
|
|
[Number(userId)],
|
|
)
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function getAdminUserByUsername(username: string): Promise<AdminUserRow | null> {
|
|
const result = await query<AdminUserRow>(
|
|
`${ADMIN_USER_SELECT} WHERE au.username = $1 LIMIT 1`,
|
|
[String(username || '').trim().toLowerCase()],
|
|
)
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function createAdminUser(input: AdminUserCreateInput): Promise<AdminUserRow | null> {
|
|
const result = await query<{ [column: string]: unknown, id: number }>(
|
|
`
|
|
INSERT INTO admin_users (
|
|
username,
|
|
password_hash,
|
|
role,
|
|
status,
|
|
session_version,
|
|
created_at,
|
|
updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id
|
|
`,
|
|
[
|
|
input.username,
|
|
input.passwordHash,
|
|
input.role,
|
|
input.status,
|
|
1,
|
|
input.createdAt,
|
|
input.updatedAt,
|
|
],
|
|
)
|
|
|
|
return getAdminUserById(result.rows[0]?.id || 0)
|
|
}
|
|
|
|
export async function updateAdminUser(
|
|
userId: number | string,
|
|
patch: AdminUserPatch,
|
|
): Promise<AdminUserRow | null> {
|
|
const current = await getAdminUserById(userId)
|
|
if (!current) {
|
|
return null
|
|
}
|
|
|
|
const next = { ...current, ...patch }
|
|
const result = await query<{ [column: string]: unknown, id: number }>(
|
|
`
|
|
UPDATE admin_users
|
|
SET
|
|
username = $1,
|
|
password_hash = $2,
|
|
role = $3,
|
|
status = $4,
|
|
session_version = $5,
|
|
updated_at = $6
|
|
WHERE id = $7
|
|
RETURNING id
|
|
`,
|
|
[
|
|
next.username,
|
|
next.password_hash,
|
|
next.role,
|
|
next.status,
|
|
Number(next.session_version || 1),
|
|
next.updated_at,
|
|
Number(userId),
|
|
],
|
|
)
|
|
|
|
return getAdminUserById(result.rows[0]?.id || 0)
|
|
}
|
|
|
|
export async function listAdminUsers({
|
|
page = 1,
|
|
pageSize = 20,
|
|
username = '',
|
|
role = '',
|
|
status = '',
|
|
}: AdminUserListInput = {}): Promise<AdminUserListResult> {
|
|
const offset = (page - 1) * pageSize
|
|
const filters: string[] = []
|
|
const params: unknown[] = []
|
|
|
|
if (username) {
|
|
params.push(`%${username}%`)
|
|
filters.push(`username ILIKE $${params.length}`)
|
|
}
|
|
|
|
if (role) {
|
|
params.push(role)
|
|
filters.push(`role = $${params.length}`)
|
|
}
|
|
|
|
if (status) {
|
|
params.push(status)
|
|
filters.push(`status = $${params.length}`)
|
|
}
|
|
|
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
|
const totalResult = await query<{ [column: string]: unknown, total: number }>(
|
|
`SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`,
|
|
params,
|
|
)
|
|
|
|
params.push(pageSize)
|
|
params.push(offset)
|
|
const itemsResult = await query<AdminUserRow>(
|
|
`
|
|
${ADMIN_USER_SELECT}
|
|
${whereClause}
|
|
ORDER BY au.id DESC
|
|
LIMIT $${params.length - 1} OFFSET $${params.length}
|
|
`,
|
|
params,
|
|
)
|
|
|
|
return {
|
|
items: itemsResult.rows,
|
|
total: Number(totalResult.rows[0]?.total || 0),
|
|
}
|
|
}
|
|
|
|
export async function countActiveAdminUsers(): Promise<number> {
|
|
const result = await query<{ [column: string]: unknown, total: number }>(
|
|
`SELECT COUNT(*)::int AS total FROM admin_users WHERE role = 'admin' AND status = 'active'`,
|
|
)
|
|
return Number(result.rows[0]?.total || 0)
|
|
}
|