- 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
106 lines
3.9 KiB
JavaScript
106 lines
3.9 KiB
JavaScript
import { assertSchema } from '../utils.js';
|
|
import { merge } from 'ts-deepmerge';
|
|
const conversionFormatTypes = [
|
|
'unix-time',
|
|
'bigint',
|
|
'any',
|
|
'symbol',
|
|
'set',
|
|
'map',
|
|
'int64',
|
|
'stringbool'
|
|
];
|
|
/**
|
|
* Normalizes the different kind of schema variations (anyOf, oneOf, union, const null, etc)
|
|
* to figure out the field type, optional, nullable, etc.
|
|
*/
|
|
export function schemaInfo(schema, isOptional, path) {
|
|
assertSchema(schema, path);
|
|
const types = schemaTypes(schema, path);
|
|
const array = schema.items && types.includes('array')
|
|
? (Array.isArray(schema.items) ? schema.items : [schema.items]).filter((s) => typeof s !== 'boolean')
|
|
: undefined;
|
|
const additionalProperties = schema.additionalProperties &&
|
|
typeof schema.additionalProperties === 'object' &&
|
|
types.includes('object')
|
|
? Object.fromEntries(Object.entries(schema.additionalProperties).filter(([, value]) => typeof value !== 'boolean'))
|
|
: undefined;
|
|
const properties = schema.properties && types.includes('object')
|
|
? Object.fromEntries(Object.entries(schema.properties).filter(([, value]) => typeof value !== 'boolean'))
|
|
: undefined;
|
|
const union = unionInfo(schema)?.filter((u) => u.type !== 'null' && u.const !== null);
|
|
const result = {
|
|
types: types.filter((s) => s !== 'null'),
|
|
isOptional,
|
|
isNullable: types.includes('null'),
|
|
schema,
|
|
union: union?.length ? union : undefined,
|
|
array,
|
|
properties,
|
|
additionalProperties,
|
|
required: schema.required
|
|
};
|
|
if (!schema.allOf || !schema.allOf.length) {
|
|
return result;
|
|
}
|
|
return {
|
|
...merge.withOptions({ allowUndefinedOverrides: false }, result, ...schema.allOf.map((s) => schemaInfo(s, false, []))),
|
|
schema
|
|
};
|
|
}
|
|
function schemaTypes(schema, path) {
|
|
assertSchema(schema, path);
|
|
let types = schema.const === null ? ['null'] : [];
|
|
if (schema.type) {
|
|
types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
}
|
|
if (schema.anyOf) {
|
|
types = schema.anyOf.flatMap((s) => schemaTypes(s, path));
|
|
}
|
|
if (schema.oneOf) {
|
|
types = schema.oneOf.flatMap((s) => schemaTypes(s, path));
|
|
}
|
|
if (types.includes('array') && schema.uniqueItems) {
|
|
const i = types.findIndex((t) => t === 'array');
|
|
if (i !== -1)
|
|
types[i] = 'set';
|
|
}
|
|
else if (schema.format && conversionFormatTypes.includes(schema.format)) {
|
|
types.unshift(schema.format);
|
|
// For dates and int64 (bigint), remove the integer type, as the schema format will be used
|
|
// instead in the following cases
|
|
if (schema.format == 'unix-time' || schema.format == 'int64') {
|
|
const i = types.findIndex((t) => t == 'integer');
|
|
types.splice(i, 1);
|
|
}
|
|
// For bigint, remove the string type, as the schema format will be used
|
|
// instead in the following cases
|
|
if (schema.format == 'bigint') {
|
|
const i = types.findIndex((t) => t == 'string');
|
|
types.splice(i, 1);
|
|
}
|
|
// For stringbool, remove the string type, as the schema format will be used
|
|
// stringbool should be treated as a special string that validates to boolean
|
|
if (schema.format == 'stringbool') {
|
|
const i = types.findIndex((t) => t == 'string');
|
|
if (i !== -1)
|
|
types.splice(i, 1);
|
|
}
|
|
}
|
|
if (schema.const && schema.const !== null && typeof schema.const !== 'function') {
|
|
types.push(typeof schema.const);
|
|
}
|
|
return Array.from(new Set(types));
|
|
}
|
|
function unionInfo(schema) {
|
|
if (!schema.oneOf && !schema.anyOf)
|
|
return undefined;
|
|
if (schema.oneOf && schema.oneOf.length) {
|
|
return schema.oneOf.filter((s) => typeof s !== 'boolean');
|
|
}
|
|
if (schema.anyOf && schema.anyOf.length) {
|
|
return schema.anyOf.filter((s) => typeof s !== 'boolean');
|
|
}
|
|
return undefined;
|
|
}
|