fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,467 @@
/**
* Validation Module
*
* Provides standalone validation system for fields, forms, and data.
* Features:
* - Schema-based validation
* - Field-level validation
* - Async validation
* - Custom validation rules
* - Integration with form-handling
* - Integration with LiveComponents
*/
import { Logger } from '../../core/logger.js';
/**
* Built-in validation rules
*/
const builtInRules = {
required: (value, options = {}) => {
if (value === null || value === undefined || value === '') {
return options.message || 'This field is required';
}
return true;
},
email: (value, options = {}) => {
if (!value) return true; // Optional if not required
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return options.message || 'Invalid email address';
}
return true;
},
url: (value, options = {}) => {
if (!value) return true;
try {
new URL(value);
return true;
} catch {
return options.message || 'Invalid URL';
}
},
min: (value, options = {}) => {
if (!value && value !== 0) return true;
const min = parseFloat(options.value);
const numValue = typeof value === 'string' ? parseFloat(value) : value;
if (isNaN(numValue) || numValue < min) {
return options.message || `Value must be at least ${min}`;
}
return true;
},
max: (value, options = {}) => {
if (!value && value !== 0) return true;
const max = parseFloat(options.value);
const numValue = typeof value === 'string' ? parseFloat(value) : value;
if (isNaN(numValue) || numValue > max) {
return options.message || `Value must be at most ${max}`;
}
return true;
},
minLength: (value, options = {}) => {
if (!value) return true;
const min = parseInt(options.value, 10);
if (typeof value !== 'string' || value.length < min) {
return options.message || `Must be at least ${min} characters`;
}
return true;
},
maxLength: (value, options = {}) => {
if (!value) return true;
const max = parseInt(options.value, 10);
if (typeof value !== 'string' || value.length > max) {
return options.message || `Must be at most ${max} characters`;
}
return true;
},
pattern: (value, options = {}) => {
if (!value) return true;
const regex = new RegExp(options.value);
if (!regex.test(value)) {
return options.message || 'Invalid format';
}
return true;
},
number: (value, options = {}) => {
if (!value && value !== 0) return true;
if (isNaN(parseFloat(value))) {
return options.message || 'Must be a number';
}
return true;
},
integer: (value, options = {}) => {
if (!value && value !== 0) return true;
if (!Number.isInteger(parseFloat(value))) {
return options.message || 'Must be an integer';
}
return true;
},
phone: (value, options = {}) => {
if (!value) return true;
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
if (!phoneRegex.test(value)) {
return options.message || 'Invalid phone number';
}
return true;
},
postalCode: (value, options = {}) => {
if (!value) return true;
const country = options.country || 'DE';
const patterns = {
DE: /^\d{5}$/,
US: /^\d{5}(-\d{4})?$/,
UK: /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i,
FR: /^\d{5}$/
};
const pattern = patterns[country] || patterns.DE;
if (!pattern.test(value)) {
return options.message || `Invalid postal code for ${country}`;
}
return true;
},
custom: (value, options = {}) => {
if (!options.validator || typeof options.validator !== 'function') {
return 'Custom validator function required';
}
return options.validator(value, options);
}
};
/**
* Validator - Schema-based validation
*/
export class Validator {
constructor(schema = {}) {
this.schema = schema;
this.customRules = new Map();
this.errors = {};
this.validatedFields = new Set();
}
/**
* Create a new Validator instance
*/
static create(schema = {}) {
return new Validator(schema);
}
/**
* Register a custom validation rule
*/
registerRule(name, rule) {
if (typeof rule !== 'function') {
throw new Error('Validation rule must be a function');
}
this.customRules.set(name, rule);
}
/**
* Validate a single field
*/
async validateField(fieldName, value, schema = null) {
const fieldSchema = schema || this.schema[fieldName];
if (!fieldSchema) {
return { valid: true, errors: [] };
}
const errors = [];
// Handle array of rules
const rules = Array.isArray(fieldSchema) ? fieldSchema : [fieldSchema];
for (const ruleConfig of rules) {
const result = await this.validateRule(value, ruleConfig);
if (result !== true) {
errors.push(result);
}
}
// Store errors
if (errors.length > 0) {
this.errors[fieldName] = errors;
} else {
delete this.errors[fieldName];
}
this.validatedFields.add(fieldName);
return {
valid: errors.length === 0,
errors
};
}
/**
* Validate a single rule
*/
async validateRule(value, ruleConfig) {
if (typeof ruleConfig === 'function') {
// Custom validator function
const result = await ruleConfig(value);
return result === true ? true : (result || 'Validation failed');
}
if (typeof ruleConfig === 'string') {
// Rule name only
return this.executeRule(ruleConfig, value, {});
}
if (typeof ruleConfig === 'object' && ruleConfig !== null) {
// Rule with options
const ruleName = ruleConfig.rule || ruleConfig.type || Object.keys(ruleConfig)[0];
const options = ruleConfig.options || ruleConfig[ruleName] || {};
// Check for async validation
if (ruleConfig.async && typeof ruleConfig.validator === 'function') {
const result = await ruleConfig.validator(value, options);
return result === true ? true : (result || options.message || 'Validation failed');
}
return this.executeRule(ruleName, value, options);
}
return true;
}
/**
* Execute a validation rule
*/
executeRule(ruleName, value, options) {
// Check custom rules first
if (this.customRules.has(ruleName)) {
const result = this.customRules.get(ruleName)(value, options);
return result === true ? true : (result || options.message || 'Validation failed');
}
// Check built-in rules
if (builtInRules[ruleName]) {
const result = builtInRules[ruleName](value, options);
return result === true ? true : (result || options.message || 'Validation failed');
}
Logger.warn(`[Validator] Unknown validation rule: ${ruleName}`);
return true; // Unknown rules pass by default
}
/**
* Validate entire schema
*/
async validate(data) {
this.errors = {};
this.validatedFields.clear();
const results = {};
let isValid = true;
for (const fieldName in this.schema) {
const value = data[fieldName];
const result = await this.validateField(fieldName, value);
results[fieldName] = result;
if (!result.valid) {
isValid = false;
}
}
return {
valid: isValid,
errors: this.errors,
results
};
}
/**
* Validate specific fields
*/
async validateFields(data, fieldNames) {
this.errors = {};
const results = {};
let isValid = true;
for (const fieldName of fieldNames) {
if (!(fieldName in this.schema)) {
continue;
}
const value = data[fieldName];
const result = await this.validateField(fieldName, value);
results[fieldName] = result;
if (!result.valid) {
isValid = false;
}
}
return {
valid: isValid,
errors: this.errors,
results
};
}
/**
* Get errors for a specific field
*/
getFieldErrors(fieldName) {
return this.errors[fieldName] || [];
}
/**
* Get all errors
*/
getErrors() {
return { ...this.errors };
}
/**
* Check if field is valid
*/
isFieldValid(fieldName) {
return !this.errors[fieldName] || this.errors[fieldName].length === 0;
}
/**
* Check if all fields are valid
*/
isValid() {
return Object.keys(this.errors).length === 0;
}
/**
* Clear errors
*/
clearErrors(fieldName = null) {
if (fieldName) {
delete this.errors[fieldName];
} else {
this.errors = {};
}
}
/**
* Reset validator
*/
reset() {
this.errors = {};
this.validatedFields.clear();
}
/**
* Create validator from HTML form
*/
static fromForm(form) {
const schema = {};
const fields = form.querySelectorAll('input, textarea, select');
fields.forEach(field => {
if (!field.name) return;
const rules = [];
// Required
if (field.hasAttribute('required')) {
rules.push('required');
}
// Type-based validation
if (field.type === 'email') {
rules.push('email');
} else if (field.type === 'url') {
rules.push('url');
} else if (field.type === 'number') {
rules.push('number');
}
// Min/Max length
if (field.hasAttribute('minlength')) {
rules.push({
rule: 'minLength',
options: { value: field.getAttribute('minlength') }
});
}
if (field.hasAttribute('maxlength')) {
rules.push({
rule: 'maxLength',
options: { value: field.getAttribute('maxlength') }
});
}
// Min/Max (for numbers)
if (field.hasAttribute('min')) {
rules.push({
rule: 'min',
options: { value: field.getAttribute('min') }
});
}
if (field.hasAttribute('max')) {
rules.push({
rule: 'max',
options: { value: field.getAttribute('max') }
});
}
// Pattern
if (field.hasAttribute('pattern')) {
rules.push({
rule: 'pattern',
options: {
value: field.getAttribute('pattern'),
message: field.getAttribute('data-error-pattern') || 'Invalid format'
}
});
}
// Custom validation
if (field.hasAttribute('data-validate')) {
const validateAttr = field.getAttribute('data-validate');
try {
const customRule = JSON.parse(validateAttr);
rules.push(customRule);
} catch {
// Treat as rule name
rules.push(validateAttr);
}
}
if (rules.length > 0) {
schema[field.name] = rules;
}
});
return new Validator(schema);
}
}
/**
* ValidationRule - Individual validation rule
*/
export class ValidationRule {
constructor(name, validator, options = {}) {
this.name = name;
this.validator = validator;
this.options = options;
}
async validate(value) {
if (typeof this.validator === 'function') {
const result = await this.validator(value, this.options);
return result === true ? true : (result || this.options.message || 'Validation failed');
}
return true;
}
}