386 lines
13 KiB
TypeScript

import { getServerSession } from 'next-auth'
import type { Session } from 'next-auth'
import { NextResponse } from 'next/server'
import { authOptions } from '@/lib/auth'
import {
DINGTALK_PERFORMANCE_SESSION_HEADER,
verifyDingtalkPerformanceSessionToken,
} from '@/lib/dingtalk-performance-session'
import {
PerformanceSurveyApiError,
createPerformanceSurveyWorkItemFromRequest,
createPerformanceSurvey,
cancelPerformanceSurvey,
exportPerformanceSurveyCsv,
getPerformanceSurveyDashboard,
getPerformanceSurveyDashboardExport,
getPerformanceSurveyDashboardDetail,
getPerformanceSurveyAdminConfiguration,
getPerformanceSurveyDetail,
getPerformanceSurveyMe,
getPerformanceSurveyNotificationStatus,
getPerformanceSurveyResults,
getPerformanceSurveyTemplates,
getPerformanceSurveyPositionGroups,
listPerformanceSurveyDepartments,
listPerformanceSurveyNotifications,
listPerformanceSurveys,
retryPerformanceSurveyNotificationById,
runPerformanceSurveyNotificationMaintenance,
searchPerformanceSurveyEmployees,
submitPerformanceSurveyWorkItemScores,
submitPerformanceSurveyWorkItemScore,
submitPerformanceSurveyResponse,
previewPerformanceSurveyEmployeeProfiles,
syncPerformanceSurveyEmployeeProfiles,
updatePerformanceSurvey,
savePerformanceSurveyAdminPositionGroup,
publishPerformanceSurveyAdminPositionGroup,
savePerformanceSurveyAdminTemplate,
copyPerformanceSurveyAdminTemplateVersion,
publishPerformanceSurveyAdminTemplateVersion,
archivePerformanceSurveyAdminPositionAssignment,
copyPerformanceSurveyAdminPositionGroup,
createPerformanceSurveyAdminPositionAssignments,
createPerformanceSurveyAdminPositionGroup,
listPerformanceSurveyAdminConfigAuditLogs,
listPerformanceSurveyAdminPositionAssignments,
previewPerformanceSurveyAdminPositionGroup,
removePerformanceSurveyAdminPositionGroup,
removePerformanceSurveyAdminTemplate,
removePerformanceSurveyPublicDashboardAssessment,
updatePerformanceSurveyAdminPositionAssignment,
} from '@/lib/performance-survey-service'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
export const revalidate = 0
type RouteContext = {
params: {
path?: string[]
}
}
function ok(data: unknown, init?: ResponseInit) {
return NextResponse.json({ success: true, data }, init)
}
function errorResponse(error: unknown) {
if (error instanceof PerformanceSurveyApiError) {
return NextResponse.json({ success: false, error: error.message }, { status: error.status })
}
if (error && typeof error === 'object' && typeof (error as { status?: unknown }).status === 'number') {
return NextResponse.json(
{ success: false, error: error instanceof Error ? error.message : '请求失败' },
{ status: (error as { status: number }).status },
)
}
const message = error instanceof Error ? error.message : '服务器错误'
console.error('[performance-survey] API error:', error)
return NextResponse.json({ success: false, error: message }, { status: 500 })
}
function readDingtalkPerformanceToken(request: Request) {
const headerToken = request.headers.get(DINGTALK_PERFORMANCE_SESSION_HEADER)
if (headerToken) {
return headerToken
}
const authorization = request.headers.get('authorization') || ''
const match = authorization.match(/^Bearer\s+(.+)$/i)
return match?.[1]?.trim() || null
}
async function getSession(request: Request): Promise<Session | null> {
const session = await getServerSession(authOptions)
if ((session?.user as any)?.id) {
return session
}
const payload = verifyDingtalkPerformanceSessionToken(readDingtalkPerformanceToken(request))
if (!payload) {
return session
}
return {
user: {
id: payload.userId,
email: payload.email,
name: payload.name,
avatar: payload.avatar || null,
role: payload.role,
department: payload.department || '',
managedDepartment: payload.managedDepartment || null,
isSuperAdmin: payload.isSuperAdmin ?? false,
},
expires: new Date(payload.exp * 1000).toISOString(),
}
}
function getPath(context: RouteContext) {
return context.params.path || []
}
export async function GET(request: Request, context: RouteContext) {
try {
const session = await getSession(request)
const path = getPath(context)
const url = new URL(request.url)
if (path.length === 0 || path[0] === 'me') {
return ok(await getPerformanceSurveyMe(session))
}
if (path[0] === 'templates') {
return ok(await getPerformanceSurveyTemplates(session, url.searchParams))
}
if (path[0] === 'position-groups') {
return ok(await getPerformanceSurveyPositionGroups(session, url.searchParams))
}
if (path[0] === 'employees') {
return ok(await searchPerformanceSurveyEmployees(session, url.searchParams))
}
if (path[0] === 'departments') {
return ok(await listPerformanceSurveyDepartments(session, url.searchParams))
}
if (path[0] === 'dashboard' && path[1] === 'export') {
return ok(await getPerformanceSurveyDashboardExport(session, url.searchParams))
}
if (path[0] === 'dashboard' && path[1] === 'detail') {
return ok(await getPerformanceSurveyDashboardDetail(session, url.searchParams))
}
if (path[0] === 'dashboard') {
return ok(await getPerformanceSurveyDashboard(session, url.searchParams))
}
if (path[0] === 'surveys' && path.length === 1) {
return ok(await listPerformanceSurveys(session, url.searchParams))
}
if (path[0] === 'surveys' && path[1] && path.length === 2) {
return ok(await getPerformanceSurveyDetail(session, path[1]))
}
if (path[0] === 'surveys' && path[1] && path[2] === 'results') {
return ok(await getPerformanceSurveyResults(session, path[1], url.searchParams))
}
if (path[0] === 'results' && path[1]) {
return ok(await getPerformanceSurveyResults(session, path[1], url.searchParams))
}
if (path[0] === 'admin' && path[1] === 'notifications' && path[2] === 'status') {
return ok(await getPerformanceSurveyNotificationStatus(session))
}
if (
path[0] === 'admin'
&& path[1] === 'position-groups'
&& path[2]
&& path[3] === 'preview'
&& path.length === 4
) {
return ok(await previewPerformanceSurveyAdminPositionGroup(session, path[2], url.searchParams))
}
if (path[0] === 'admin' && path[1] === 'position-assignments' && path.length === 2) {
return ok(await listPerformanceSurveyAdminPositionAssignments(session, url.searchParams))
}
if (path[0] === 'admin' && path[1] === 'config-audit-logs' && path.length === 2) {
return ok(await listPerformanceSurveyAdminConfigAuditLogs(session, url.searchParams))
}
if (path[0] === 'admin' && path[1] === 'config') {
return ok(await getPerformanceSurveyAdminConfiguration(session, url.searchParams))
}
if (path[0] === 'admin' && path[1] === 'notifications') {
return ok(await listPerformanceSurveyNotifications(session, url.searchParams))
}
if (path[0] === 'admin' && path[1] === 'export') {
const csv = await exportPerformanceSurveyCsv(session)
return new NextResponse(csv, {
headers: {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': 'attachment; filename="performance-survey-export.csv"',
},
})
}
return NextResponse.json({ success: false, error: '接口不存在' }, { status: 404 })
} catch (error) {
return errorResponse(error)
}
}
export async function POST(request: Request, context: RouteContext) {
try {
const session = await getSession(request)
const path = getPath(context)
if (path[0] === 'surveys' && path[1] && path[2] === 'work-items' && path.length === 3) {
return ok(await createPerformanceSurveyWorkItemFromRequest(session, path[1], request), { status: 201 })
}
const body = await request.json().catch(() => ({}))
if (path[0] === 'surveys' && path.length === 1) {
return ok(await createPerformanceSurvey(session, body), { status: 201 })
}
if (path[0] === 'surveys' && path[1] && path[2] === 'cancel' && path.length === 3) {
return ok(await cancelPerformanceSurvey(session, path[1], body))
}
if (path[0] === 'surveys' && path[1] && path[2] === 'responses') {
return ok(await submitPerformanceSurveyResponse(session, path[1], body))
}
if (path[0] === 'surveys' && path[1] && path[2] === 'work-item-scores' && path.length === 3) {
return ok(await submitPerformanceSurveyWorkItemScores(session, path[1], body))
}
if (path[0] === 'surveys' && path[1] && path[2] === 'work-items' && path[3] && path[4] === 'scores') {
return ok(await submitPerformanceSurveyWorkItemScore(session, path[1], path[3], body))
}
if (
path[0] === 'admin'
&& path[1] === 'sync-employees'
&& path[2] === 'preview'
&& path.length === 3
) {
return ok(await previewPerformanceSurveyEmployeeProfiles(session, body))
}
if (path[0] === 'admin' && path[1] === 'sync-employees' && path.length === 2) {
return ok(await syncPerformanceSurveyEmployeeProfiles(session, body))
}
if (path[0] === 'admin' && path[1] === 'position-groups' && path.length === 2) {
return ok(await createPerformanceSurveyAdminPositionGroup(session, body), { status: 201 })
}
if (
path[0] === 'admin'
&& path[1] === 'position-groups'
&& path[2]
&& path[3] === 'copy'
&& path.length === 4
) {
return ok(await copyPerformanceSurveyAdminPositionGroup(session, path[2], body), { status: 201 })
}
if (
path[0] === 'admin'
&& path[1] === 'position-groups'
&& path[2]
&& path[3] === 'publish'
&& path.length === 4
) {
return ok(await publishPerformanceSurveyAdminPositionGroup(session, path[2], body))
}
if (path[0] === 'admin' && path[1] === 'position-assignments' && path.length === 2) {
return ok(await createPerformanceSurveyAdminPositionAssignments(session, body), { status: 201 })
}
if (path[0] === 'admin' && path[1] === 'templates' && path.length === 2) {
return ok(await savePerformanceSurveyAdminTemplate(session, body), { status: 201 })
}
if (path[0] === 'admin' && path[1] === 'templates' && path[2] && path[3] === 'copy') {
return ok(await copyPerformanceSurveyAdminTemplateVersion(session, path[2]), { status: 201 })
}
if (path[0] === 'admin' && path[1] === 'templates' && path[2] && path[3] === 'publish') {
return ok(await publishPerformanceSurveyAdminTemplateVersion(session, path[2], body))
}
if (path[0] === 'admin' && path[1] === 'notifications' && path[2] === 'maintenance') {
return ok(await runPerformanceSurveyNotificationMaintenance(session))
}
if (path[0] === 'admin' && path[1] === 'notifications' && path[2] && path[3] === 'retry') {
return ok(await retryPerformanceSurveyNotificationById(session, path[2]))
}
return NextResponse.json({ success: false, error: '接口不存在' }, { status: 404 })
} catch (error) {
return errorResponse(error)
}
}
export async function PATCH(request: Request, context: RouteContext) {
try {
const session = await getSession(request)
const path = getPath(context)
const body = await request.json().catch(() => ({}))
if (path[0] === 'surveys' && path[1] && path.length === 2) {
return ok(await updatePerformanceSurvey(session, path[1], body))
}
if (path[0] === 'admin' && path[1] === 'position-groups' && path[2] && path.length === 3) {
return ok(await savePerformanceSurveyAdminPositionGroup(session, path[2], body))
}
if (path[0] === 'admin' && path[1] === 'templates' && path[2] && path.length === 3) {
return ok(await savePerformanceSurveyAdminTemplate(session, { ...body, versionId: path[2] }))
}
if (path[0] === 'admin' && path[1] === 'position-assignments' && path[2] && path.length === 3) {
return ok(await updatePerformanceSurveyAdminPositionAssignment(session, path[2], body))
}
return NextResponse.json({ success: false, error: '接口不存在' }, { status: 404 })
} catch (error) {
return errorResponse(error)
}
}
export async function DELETE(request: Request, context: RouteContext) {
try {
const session = await getSession(request)
const path = getPath(context)
const body = await request.json().catch(() => ({}))
if (path[0] === 'admin' && path[1] === 'position-groups' && path[2] && path.length === 3) {
return ok(await removePerformanceSurveyAdminPositionGroup(session, path[2], body))
}
if (path[0] === 'admin' && path[1] === 'templates' && path[2] && path.length === 3) {
return ok(await removePerformanceSurveyAdminTemplate(session, path[2], body))
}
if (path[0] === 'admin' && path[1] === 'position-assignments' && path[2] && path.length === 3) {
return ok(await archivePerformanceSurveyAdminPositionAssignment(session, path[2], body))
}
if (
path[0] === 'dashboard'
&& path[1] === 'public'
&& path[2] === 'assessments'
&& path[3]
&& path.length === 4
) {
return ok(await removePerformanceSurveyPublicDashboardAssessment(session, path[3], body))
}
return NextResponse.json({ success: false, error: '接口不存在' }, { status: 404 })
} catch (error) {
return errorResponse(error)
}
}