79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { NextRequest } from 'next/server'
|
||
import { z } from 'zod'
|
||
import { prisma } from '@/src/lib/prisma'
|
||
|
||
const ExportSchema = z.object({
|
||
ids: z.array(z.string()).min(1),
|
||
format: z.enum(['csv', 'json']),
|
||
})
|
||
|
||
export async function POST(req: NextRequest) {
|
||
try {
|
||
const body = await req.json()
|
||
const parsed = ExportSchema.safeParse(body)
|
||
if (!parsed.success) return Response.json({ error: 'invalid body' }, { status: 400 })
|
||
const { ids, format } = parsed.data
|
||
|
||
const rows = await prisma.recording.findMany({
|
||
where: { id: { in: ids } },
|
||
orderBy: { timestamp: 'asc' },
|
||
// Cast select to any to accommodate freshly added fields before prisma generate
|
||
select: {
|
||
id: true,
|
||
timestamp: true,
|
||
code_data: true,
|
||
fit_a: true,
|
||
fit_b: true,
|
||
sample_count: true,
|
||
duration: true,
|
||
rec_start_ms: true,
|
||
rec_end_ms: true,
|
||
} ,
|
||
})
|
||
|
||
if (format === 'json') {
|
||
const payload = rows.map((r) => ({
|
||
id: r.id,
|
||
timestamp: new Date(r.timestamp).toISOString(),
|
||
code: r.code_data,
|
||
fit: r.fit_a != null ? { a: r.fit_a, b: r.fit_b } : undefined,
|
||
sampleCount: r.sample_count,
|
||
duration: r.duration,
|
||
}))
|
||
return new Response(JSON.stringify(payload), {
|
||
headers: {
|
||
'content-type': 'application/json; charset=utf-8',
|
||
'content-disposition': `attachment; filename="export-${Date.now()}.json"`,
|
||
},
|
||
})
|
||
}
|
||
|
||
// CSV(去掉 recordId、timestamp,timeSec 改为毫秒单位)
|
||
let csv = 'index,timeMs,code,force\n'
|
||
for (const r of rows) {
|
||
// 以毫秒为单位的步长
|
||
let stepMs = r.sample_count > 0 ? (r.duration * 1000) / r.sample_count : 0
|
||
if (r.rec_start_ms != null && r.rec_end_ms != null && r.sample_count > 0) {
|
||
const dtMs = Number((r.rec_end_ms as bigint) - (r.rec_start_ms as bigint))
|
||
if (Number.isFinite(dtMs) && dtMs > 0) stepMs = dtMs / r.sample_count
|
||
}
|
||
const hasFit = r.fit_a != null
|
||
for (let i = 0; i < r.code_data.length; i++) {
|
||
const t = (i * stepMs).toFixed(3)
|
||
const code = r.code_data[i]
|
||
const force = hasFit ? r.fit_a! * code + (r.fit_b ?? 0) : ''
|
||
csv += `${i},${t},${code},${hasFit ? force : ''}\n`
|
||
}
|
||
}
|
||
return new Response(csv, {
|
||
headers: {
|
||
'content-type': 'text/csv; charset=utf-8',
|
||
'content-disposition': `attachment; filename="export-${Date.now()}.csv"`,
|
||
},
|
||
})
|
||
} catch (err) {
|
||
console.error(err)
|
||
return Response.json({ error: 'internal error' }, { status: 500 })
|
||
}
|
||
}
|