55 lines
1.6 KiB
TypeScript
55 lines
1.6 KiB
TypeScript
import { getHeader, readRawBody } from 'h3'
|
|
import { requireSiteSyncAuth } from '../../utils/access'
|
|
import { createApiError } from '../../utils/api-error'
|
|
import { validateStorageKey } from '../../utils/image-key'
|
|
import {
|
|
isSiteSyncChunkMetadata,
|
|
SITE_SYNC_CHUNK_SIZE,
|
|
writeSiteSyncChunk
|
|
} from '../../utils/site-sync/chunks'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
await requireSiteSyncAuth(event)
|
|
const encoded = getHeader(event, 'x-pichost-chunk-metadata') ?? ''
|
|
let metadata: unknown
|
|
try {
|
|
metadata = JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'))
|
|
} catch {
|
|
createApiError(event, 'INVALID_REQUEST', '分块元数据无效', 400)
|
|
}
|
|
if (
|
|
!isSiteSyncChunkMetadata(metadata)
|
|
|| !validateStorageKey(metadata.key)
|
|
|| !validateStorageKey(metadata.mediaKey)
|
|
) {
|
|
createApiError(event, 'INVALID_REQUEST', '分块元数据无效', 400)
|
|
}
|
|
|
|
const index = Number.parseInt(getHeader(event, 'x-pichost-chunk-index') ?? '', 10)
|
|
if (!Number.isInteger(index)) {
|
|
createApiError(event, 'INVALID_REQUEST', '分块序号无效', 400)
|
|
}
|
|
const body = await readRawBody(event, false)
|
|
if (!body?.length) {
|
|
createApiError(event, 'INVALID_REQUEST', '未收到分块数据', 400)
|
|
}
|
|
if (body.length > SITE_SYNC_CHUNK_SIZE) {
|
|
createApiError(event, 'FILE_TOO_LARGE', '分块超过 4 MB', 413)
|
|
}
|
|
|
|
try {
|
|
return await writeSiteSyncChunk({
|
|
metadata,
|
|
index,
|
|
bytes: new Uint8Array(body)
|
|
})
|
|
} catch (error) {
|
|
createApiError(
|
|
event,
|
|
'INVALID_REQUEST',
|
|
error instanceof Error ? error.message : '分块写入失败',
|
|
400
|
|
)
|
|
}
|
|
})
|