import { hasFieldOfType, is, predicates } from "@sealcode/ts-predicates"; import { Context } from "koa"; import { FilePointer, PathFilePointer } from "@sealcode/file-manager"; import { FormDataValue } from "../form-types.js"; import { FieldParseResult, FormField } from "./field.js"; import { File as FileControl } from "../controls/file.js"; import { FormControl, FormControlContext } from "../controls/form-control.js"; export type FileContainer = { old: FilePointer | null; new: FilePointer | null; }; export class File extends FormField< Required, FileContainer, FormControl > { constructor(public readonly required: Required) { super(required); } async parse( ctx: Context, raw_value: FormDataValue ): Promise> { let result; if (is(raw_value, predicates.string)) { const file = await ctx.$app.fileManager.fromToken(raw_value); result = { parsed_value: { old: file, new: null }, error: null, parsable: true as const, }; } else if (!is(raw_value, predicates.object)) { result = { parsed_value: null, error: "Expected a field with old.id:string, old.filename:string and new?:File", parsable: false as const, }; } else { const old_file = hasFieldOfType( raw_value, "old", predicates.instanceOf(PathFilePointer) ) ? await ctx.$app.fileManager.toPointer( raw_value.old as string | FilePointer ) : null; const new_file = hasFieldOfType( raw_value, "new", predicates.or( predicates.array(predicates.instanceOf(PathFilePointer)), predicates.instanceOf(PathFilePointer) ) ) ? Array.isArray(raw_value.new) ? raw_value.new[0] : raw_value.new : null; result = { parsable: true as const, error: null, parsed_value: { old: old_file, new: new_file }, }; } return result; } async getDatabaseValue(ctx: Context, data: Record) { const { parsed } = await this.getParsedValue(ctx, data); return parsed?.new || undefined; } getEmptyValue() { return { old: null, new: null }; } getControl(): FileControl { return new FileControl(this, { label: this.name, autocomplete: false }); } async getSealiousCreateValue( fctx: FormControlContext ): Promise { const { parsed: value } = await this.getParsedValue( fctx.ctx, fctx.data.raw_values ); const pointer = value?.new || value?.old || undefined; if (this.required && !pointer) { throw new Error("File is required, but no value is provided"); } return pointer; } async sealiousValueToForm( ctx: Context, sealiousValue: FilePointer | string | null ): Promise<{ old: FilePointer | undefined } | Record> { if (!sealiousValue) { return {}; } if (typeof sealiousValue == "string") { sealiousValue = await ctx.$app.fileManager.fromToken(sealiousValue); } return { old: sealiousValue, // FilePointer }; } }