- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
148 lines
5.4 KiB
JavaScript
148 lines
5.4 KiB
JavaScript
import { traversePath } from './traversal.js';
|
|
import { fail as kitFail } from '@sveltejs/kit';
|
|
import {} from './adapters/adapters.js';
|
|
import { parseRequest } from './formData.js';
|
|
import { splitPath } from './stringPath.js';
|
|
import { mapErrors, mergeDefaults, replaceInvalidDefaults } from './errors.js';
|
|
/**
|
|
* Validates a schema for data validation and usage in superForm.
|
|
* @param data Data corresponding to a schema, or RequestEvent/FormData/URL. If falsy, the schema's default values will be used.
|
|
* @param schema The schema to validate against.
|
|
*/
|
|
export async function superValidate(data, adapter, options) {
|
|
if (data && 'superFormValidationLibrary' in data) {
|
|
options = adapter;
|
|
adapter = data;
|
|
data = undefined;
|
|
}
|
|
const validator = adapter;
|
|
const defaults = options?.defaults ?? validator.defaults;
|
|
const jsonSchema = validator.jsonSchema;
|
|
const parsed = await parseRequest(data, jsonSchema, options);
|
|
const addErrors = options?.errors ?? (options?.strict ? true : !!parsed.data);
|
|
// Merge with defaults in non-strict mode.
|
|
const parsedData = options?.strict ? (parsed.data ?? {}) : mergeDefaults(parsed.data, defaults);
|
|
let status;
|
|
if (!!parsed.data || addErrors) {
|
|
status = await /* @__PURE__ */ validator.validate(parsedData);
|
|
}
|
|
else {
|
|
status = { success: false, issues: [] };
|
|
}
|
|
const valid = status.success;
|
|
const errors = valid || !addErrors ? {} : mapErrors(status.issues, validator.shape);
|
|
// Final data should always have defaults, to ensure type safety
|
|
//const dataWithDefaults = { ...defaults, ...(valid ? status.data : parsedData) };
|
|
const dataWithDefaults = valid
|
|
? status.data
|
|
: replaceInvalidDefaults(options?.strict ? mergeDefaults(parsedData, defaults) : parsedData, defaults, jsonSchema, status.issues, options?.preprocessed);
|
|
let outputData;
|
|
if (jsonSchema.additionalProperties === false) {
|
|
// Strip keys not belonging to schema
|
|
outputData = {};
|
|
for (const key of Object.keys(jsonSchema.properties ?? {})) {
|
|
if (key in dataWithDefaults)
|
|
outputData[key] = dataWithDefaults[key];
|
|
}
|
|
}
|
|
else {
|
|
outputData = dataWithDefaults;
|
|
}
|
|
const output = {
|
|
id: parsed.id ?? options?.id ?? validator.id,
|
|
valid,
|
|
posted: parsed.posted,
|
|
errors: errors,
|
|
data: outputData
|
|
};
|
|
if (!parsed.posted) {
|
|
output.constraints = validator.constraints;
|
|
if (Object.keys(validator.shape).length) {
|
|
output.shape = validator.shape;
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
/////////////////////////////////////////////////////////////////////
|
|
/**
|
|
* Sends a message with a form, with an optional HTTP status code that will set
|
|
* form.valid to false if status >= 400. A status lower than 400 cannot be sent.
|
|
*/
|
|
export function message(form, message, options) {
|
|
if (options?.status && options.status >= 400) {
|
|
form.valid = false;
|
|
}
|
|
form.message = message;
|
|
const remove = options?.removeFiles !== false;
|
|
const output = remove ? withFiles({ form }) : { form };
|
|
return form.valid ? output : kitFail(options?.status ?? 400, output);
|
|
}
|
|
export const setMessage = message;
|
|
export function setError(form, path, error, options) {
|
|
// Unify signatures
|
|
if (error == undefined || (typeof error !== 'string' && !Array.isArray(error))) {
|
|
options = error;
|
|
error = path;
|
|
path = '';
|
|
}
|
|
if (options === undefined)
|
|
options = {};
|
|
const errArr = Array.isArray(error) ? error : [error];
|
|
if (!form.errors)
|
|
form.errors = {};
|
|
if (path === null || path === '') {
|
|
if (!form.errors._errors)
|
|
form.errors._errors = [];
|
|
form.errors._errors = options.overwrite ? errArr : form.errors._errors.concat(errArr);
|
|
}
|
|
else {
|
|
const realPath = splitPath(path);
|
|
const leaf = traversePath(form.errors, realPath, ({ parent, key, value }) => {
|
|
if (value === undefined)
|
|
parent[key] = {};
|
|
return parent[key];
|
|
});
|
|
if (leaf) {
|
|
leaf.parent[leaf.key] =
|
|
Array.isArray(leaf.value) && !options.overwrite ? leaf.value.concat(errArr) : errArr;
|
|
}
|
|
}
|
|
form.valid = false;
|
|
const output = options.removeFiles === false ? { form } : withFiles({ form });
|
|
return kitFail(options.status ?? 400, output);
|
|
}
|
|
export function withFiles(obj) {
|
|
if (typeof obj !== 'object')
|
|
return obj;
|
|
for (const key in obj) {
|
|
const value = obj[key];
|
|
if (value instanceof File)
|
|
delete obj[key];
|
|
else if (value && typeof value === 'object')
|
|
withFiles(value);
|
|
}
|
|
return obj;
|
|
}
|
|
export const removeFiles = withFiles;
|
|
export function fail(status, data) {
|
|
function checkForm(data) {
|
|
return !!data && typeof data === 'object' && 'valid' in data && 'data' in data && 'id' in data;
|
|
}
|
|
function checkObj(data) {
|
|
if (data && typeof data === 'object') {
|
|
for (const key in data) {
|
|
const v = data[key];
|
|
if (checkForm(v)) {
|
|
v.valid = false;
|
|
removeFiles(v);
|
|
}
|
|
else if (v && typeof v === 'object') {
|
|
checkObj(v);
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
return kitFail(status, checkObj(data));
|
|
}
|