feat: Filemagedon
Some checks failed
Build, check & Test / run (push) Failing after 1m45s
Lint / run (push) Failing after 48s
Build Docker Image / build_docker (push) Failing after 3m18s

Signed-off-by: Avior <git@avior.me>
This commit is contained in:
2024-09-11 14:38:58 +02:00
parent 3e91597dca
commit bc97d9106b
45 changed files with 4548 additions and 64 deletions

159
src/libs/Form.ts Normal file
View File

@ -0,0 +1,159 @@
import { objectFind, objectLoop } from '@dzeio/object-util'
import type { Model, ModelInfer, ValidationError, ValidationResult } from './Schema'
import Schema from './Schema'
/**
* Handle most of the form validation and error reporting
*
* create a new one by using {@link Form.create}
*
* note: This library is made to work with {@link Model}
*/
export default class Form<M extends Model> {
private data?: ValidationResult<ModelInfer<M>>
private formData?: FormData
private schema: Schema
private globalError?: string
private errors: Partial<Record<keyof M, string>> = {}
private defaultValues: Partial<Record<keyof M, any>> = {}
/**
* Create a ready to use form
* @param model the model the form should respect
* @param request the request element
* @returns the Form object
*/
public static async create<M extends Model>(model: M, request: Request) {
const fm = new Form(model, request)
await fm.init()
return fm
}
private constructor(public readonly model: M, private readonly request: Request) {
this.schema = new Schema(model)
}
/**
* you should not use this function by itself, it is called bu {@link Form.create}
*/
public async init() {
try {
if (this.request.method === 'POST') {
if (!(this.request.headers.get('Content-Type') ?? '').startsWith('multipart/form-data')) {
console.warn('form\'s content-type is not multipart/form-data')
}
this.formData = await this.request.formData()
this.data = this.schema.validateFormData(this.formData) as any
if (this.data?.error) {
for (const error of this.data.error) {
if (error.field) {
const field = error.field
if (field.includes('.')) {
this.errors[field.slice(0, field.indexOf('.')) as keyof M] = error.message
} else {
this.errors[error.field as keyof M] = error.message
}
} else {
this.globalError = error.message
}
}
}
}
} catch {}
}
public defaultValue(name: keyof M, value: any) {
this.defaultValues[name] = value
return this
}
public defaultObject(obj: Record<string, any>) {
objectLoop(obj, (value, key) => {
this.defaultValue(key, value)
})
return this
}
/**
* indicate if the form is valid or not
* @returns if the form submitted is valid or not
*/
public isValid(): boolean {
if (this.request.method !== 'POST' || !this.data) {
return false
}
if (this.data.error) {
return false
}
return true
}
/**
*
* @param message the error message
* @param key (optionnal) the specific key to apply the error to
*/
public setError(message: string, key?: keyof M) {
if (key) {
this.errors[key] = message
} else {
this.globalError = message
}
}
public getError(key?: keyof M): string | undefined {
if (!key) {
return this.globalError
}
return this.errors[key]
}
public hasError(key?: keyof M): boolean {
return !!this.getError(key)
}
public getAnyError(): string | undefined {
if (this.globalError) {
return this.globalError
}
const other = objectFind(this.errors, (value) => !!value)
if (other) {
return `${other.key.toString()}: ${other.value}`
}
return undefined
}
public hasAnyError(): boolean {
return !!this.getAnyError()
}
public attrs(key: keyof M) {
return this.attributes(key)
}
public attributes(key: keyof M): Record<string, any> {
const schema = this.model[key]
if (!schema) {
return {}
}
const attrs: Record<string, any> = {
name: key
}
if (!schema.attributes.includes('form:password')) {
const value: any = this.formData?.get(key as string) as string ?? this.defaultValues[key]
if (value instanceof Date) {
attrs.value = `${value.getFullYear().toString().padStart(4, '0')}-${(value.getMonth() + 1).toString().padStart(2, '0')}-${value.getDate().toString().padStart(2, '0')}`
} else {
attrs.value = value
}
}
return attrs
}
public getData(): ModelInfer<M> {
return this.data!.object!
}
}