fix: harden upload resilience and contracts
This commit is contained in:
+69
-1
@@ -3,9 +3,11 @@ import { dirname, join } from 'path'
|
||||
import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import { TMP_DIR } from '@/lib/constants'
|
||||
import { isRecord } from '@/lib/guards'
|
||||
import { getModelAssetPath } from '@/lib/model-paths'
|
||||
import { prepareGitAssets } from '@/lib/prepare-git-assets'
|
||||
import type {
|
||||
AssetCategory,
|
||||
GitModelMode,
|
||||
ParsedFile,
|
||||
PreparedAssetSummary,
|
||||
@@ -26,6 +28,7 @@ interface StagedOriginalFile {
|
||||
interface StagedPreparedData {
|
||||
modelFilename: string
|
||||
compressed: boolean
|
||||
deliveryMode?: GitModelMode
|
||||
compressionError?: string
|
||||
assetSummaries: PreparedAssetSummary[]
|
||||
}
|
||||
@@ -55,6 +58,69 @@ function getManifestPath(stagingId: string) {
|
||||
return join(getStageDir(stagingId), 'manifest.json')
|
||||
}
|
||||
|
||||
function isGitModelMode(value: unknown): value is GitModelMode {
|
||||
return value === 'draco-glb' || value === 'keep-gltf'
|
||||
}
|
||||
|
||||
function isAssetCategory(value: unknown): value is AssetCategory {
|
||||
return value === 'color'
|
||||
|| value === 'diffuse'
|
||||
|| value === 'roughness'
|
||||
|| value === 'normal'
|
||||
|| value === 'metalness'
|
||||
|| value === 'height'
|
||||
|| value === 'opacity'
|
||||
|| value === 'orm'
|
||||
|| value === 'ao'
|
||||
|| value === 'assets'
|
||||
}
|
||||
|
||||
function isPreparedAssetSummary(value: unknown): value is PreparedAssetSummary {
|
||||
return isRecord(value)
|
||||
&& typeof value.filename === 'string'
|
||||
&& (value.kind === 'model' || value.kind === 'texture' || value.kind === 'asset')
|
||||
&& (value.category === undefined || isAssetCategory(value.category))
|
||||
&& typeof value.compressed === 'boolean'
|
||||
}
|
||||
|
||||
function isStagedOriginalFile(value: unknown): value is StagedOriginalFile {
|
||||
return isRecord(value)
|
||||
&& typeof value.filename === 'string'
|
||||
&& typeof value.size === 'number'
|
||||
&& typeof value.isModel === 'boolean'
|
||||
}
|
||||
|
||||
function isStagedPreparedData(value: unknown): value is StagedPreparedData {
|
||||
return isRecord(value)
|
||||
&& typeof value.modelFilename === 'string'
|
||||
&& typeof value.compressed === 'boolean'
|
||||
&& (value.deliveryMode === undefined || isGitModelMode(value.deliveryMode))
|
||||
&& (value.compressionError === undefined || typeof value.compressionError === 'string')
|
||||
&& Array.isArray(value.assetSummaries)
|
||||
&& value.assetSummaries.every(isPreparedAssetSummary)
|
||||
}
|
||||
|
||||
function isStagingManifest(value: unknown): value is StagingManifest {
|
||||
return isRecord(value)
|
||||
&& typeof value.stagingId === 'string'
|
||||
&& typeof value.folderName === 'string'
|
||||
&& isGitModelMode(value.gitModelMode)
|
||||
&& typeof value.createdAt === 'number'
|
||||
&& Array.isArray(value.originals)
|
||||
&& value.originals.every(isStagedOriginalFile)
|
||||
&& (value.prepared === undefined || isStagedPreparedData(value.prepared))
|
||||
}
|
||||
|
||||
function parseStagingManifest(content: string) {
|
||||
const parsed: unknown = JSON.parse(content)
|
||||
|
||||
if (!isStagingManifest(parsed)) {
|
||||
throw new Error('Manifest de staging invalide')
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
async function ensureParentDir(filePath: string) {
|
||||
await mkdir(dirname(filePath), { recursive: true })
|
||||
}
|
||||
@@ -126,7 +192,7 @@ export async function createStagingUpload(
|
||||
export async function readStagedManifest(stagingId: string): Promise<StagingManifest> {
|
||||
const manifestPath = getManifestPath(stagingId)
|
||||
const content = await readFile(manifestPath, 'utf-8')
|
||||
return JSON.parse(content) as StagingManifest
|
||||
return parseStagingManifest(content)
|
||||
}
|
||||
|
||||
async function readOriginalParsedFiles(stagingId: string, manifest: StagingManifest): Promise<ParsedFile[]> {
|
||||
@@ -179,6 +245,7 @@ export async function ensurePreparedStagingAssets(stagingId: string): Promise<Pr
|
||||
manifest.prepared = {
|
||||
modelFilename: prepared.modelFilename,
|
||||
compressed: prepared.compressed,
|
||||
deliveryMode: prepared.deliveryMode,
|
||||
compressionError: prepared.compressionError,
|
||||
assetSummaries: prepared.assetSummaries,
|
||||
}
|
||||
@@ -192,6 +259,7 @@ export async function ensurePreparedStagingAssets(stagingId: string): Promise<Pr
|
||||
modelFilename: manifest.prepared.modelFilename,
|
||||
assetSummaries: manifest.prepared.assetSummaries,
|
||||
compressed: manifest.prepared.compressed,
|
||||
deliveryMode: manifest.prepared.deliveryMode ?? manifest.gitModelMode,
|
||||
compressionError: manifest.prepared.compressionError,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user