Files
zpan/server/lib/path-template.ts
saltbo 3064403c92 fix(ids): make normalization optional
Keep historical identifiers readable and addressable while generating all new entity IDs with Base62. Reject unsafe external ID references and preserve API keys and device codes during optional normalization.
2026-08-05 13:06:41 -04:00

41 lines
1.5 KiB
TypeScript

import { generateToken, OPAQUE_ID_PATTERN } from '../../shared/ids'
export interface TemplateVars {
uid: string
orgId: string
rawExt: string
}
function requireOpaqueIdKeyComponent(keyType: string, name: string, value: string): void {
if (!OPAQUE_ID_PATTERN.test(value)) throw new Error(`Invalid ${name} for ${keyType} storage key`)
}
export function assertObjectKeyOwner(vars: Pick<TemplateVars, 'orgId' | 'uid'>): void {
requireOpaqueIdKeyComponent('object', 'organization ID', vars.orgId)
requireOpaqueIdKeyComponent('object', 'user ID', vars.uid)
}
/** Returns the file extension including the leading dot, or '' when there is none. */
export function fileExt(name: string): string {
const dot = name.lastIndexOf('.')
return dot >= 0 ? name.slice(dot) : ''
}
export function buildObjectKey(vars: TemplateVars): string {
assertObjectKeyOwner(vars)
const now = new Date()
const year = String(now.getFullYear())
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
return `${vars.orgId}/${vars.uid}/${year}${month}${day}/${generateToken(17)}${vars.rawExt}`
}
export function buildImageStorageKey(orgId: string, imageId: string, extension: string): string {
requireOpaqueIdKeyComponent('image', 'organization ID', orgId)
requireOpaqueIdKeyComponent('image', 'image ID', imageId)
if (!/^[A-Za-z0-9]+$/.test(extension)) throw new Error('Invalid extension for image storage key')
return `ih/${orgId}/${imageId}.${extension}`
}