Files
dticket.tootaio.com/server/utils/users.ts
xiaomai 4e40bfd804 feat(users): add drag-and-drop reordering for PICs
Introduce pic_sort_order to persist custom user ordering
Replace data table with a custom draggable grid layout
Add API endpoint to handle bulk order updates
2026-05-04 14:07:43 +08:00

92 lines
2.5 KiB
TypeScript

import type { H3Event } from 'h3'
import {
hasValidFullName,
isUserRole,
isValidPhoneNumber,
isValidUsername,
normalizeFullName,
normalizePhoneNumber,
normalizeUsername,
type UserRole
} from '~~/shared/auth'
import { assertBadRequest, getRequiredRouteParam, httpError } from './http'
import { getUserById } from './user-repository'
export function requireUserIdParam(event: H3Event) {
return getRequiredRouteParam(event, 'id', 'User id')
}
export async function requireExistingUser(userId: string, statusMessage = 'User not found') {
const user = await getUserById(userId)
if (!user) {
httpError(404, statusMessage)
}
return user
}
export function parseCreateUserInput(body: {
username?: string
fullName?: string
phoneNumber?: string
role?: UserRole
}) {
const username = normalizeUsername(body.username || '')
const fullName = normalizeFullName(body.fullName || '')
const phoneNumber = normalizePhoneNumber(body.phoneNumber || '')
const role = body.role === 'super_admin' ? 'super_admin' : 'staff'
assertBadRequest(
isValidUsername(username),
'Username must be 3 to 32 characters using lowercase letters, numbers, dot, dash, or underscore'
)
assertBadRequest(hasValidFullName(fullName), 'Full name must be at least 2 characters')
assertBadRequest(isValidPhoneNumber(phoneNumber), 'Phone number must include a country code, e.g. +60123456789')
return {
username,
fullName,
phoneNumber,
role
}
}
export function parseUserProfileInput(body: {
fullName?: string
phoneNumber?: string
role?: UserRole
}) {
const fullName = normalizeFullName(body.fullName || '')
const phoneNumber = normalizePhoneNumber(body.phoneNumber || '')
const role = body.role
assertBadRequest(hasValidFullName(fullName), 'Display name must be at least 2 characters')
assertBadRequest(isValidPhoneNumber(phoneNumber), 'Phone number must include a country code, e.g. +60123456789')
assertBadRequest(isUserRole(role), 'Role is invalid')
return {
fullName,
phoneNumber,
role
}
}
export function parseUserOrderInput(body: {
userIds?: unknown
}) {
assertBadRequest(Array.isArray(body.userIds), 'User ids must be an array')
assertBadRequest(body.userIds.every((value) => typeof value === 'string' && value.trim()), 'Every user id is required')
const userIds = body.userIds.map((value) => value.trim())
const uniqueUserIds = new Set(userIds)
assertBadRequest(uniqueUserIds.size === userIds.length, 'User ids must be unique')
return {
userIds
}
}