diff --git a/Dockerfile b/Dockerfile index 425cb31..0e5754b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -107,7 +107,8 @@ ENV NODE_ENV=production \ TUDUDI_USER_PASSWORD="" \ DISABLE_TELEGRAM=false \ DISABLE_SCHEDULER=false \ - TUDUDI_UPLOAD_PATH="/app/backend/uploads" + TUDUDI_UPLOAD_PATH="/app/backend/uploads" \ + SWAGGER_ENABLED=false HEALTHCHECK --interval=60s --timeout=3s --start-period=10s --retries=2 \ CMD curl -sf http://localhost:3002/api/health || exit 1 diff --git a/README.md b/README.md index eaa0eaa..1887c76 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ For the thinking behind tududi, read: - Create tasks directly through Telegram messages - Receive daily digests of your tasks - Quick capture of ideas and todos on the go +- **Open API & Access Tokens**: Versioned Swagger docs exposed at `/api/v1` plus personal API keys for integrating tududi with your own tooling or automations. ## 🗺️ Roadmap diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..6146e0c --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,160 @@ +# ============================================================================== +# Tududi Environment Configuration +# ============================================================================== +# Copy this file to .env and update the values for your environment +# ============================================================================== + +# ============================================================================== +# Application Configuration +# ============================================================================== + +# Environment: production, development, or test +NODE_ENV=development + +# Server host and port +HOST=0.0.0.0 +PORT=3002 + +# Frontend URL (for redirects and CORS) +FRONTEND_URL=http://localhost:8080 + +# ============================================================================== +# User Configuration +# ============================================================================== + +# Default user credentials (used during initial setup) +TUDUDI_USER_EMAIL=admin@example.com +TUDUDI_USER_PASSWORD=change-me-to-secure-password + +# Session secret (generate with: openssl rand -hex 64) +TUDUDI_SESSION_SECRET=your-random-64-character-hex-string-here + +# ============================================================================== +# Database Configuration +# ============================================================================== + +# Custom database file location (optional) +# If not set, defaults to backend/db/{environment}.sqlite3 +# DB_FILE=/path/to/custom/database.sqlite3 + +# ============================================================================== +# CORS Configuration +# ============================================================================== + +# Comma-separated list of allowed origins for CORS +# If not set, defaults to localhost development URLs +# TUDUDI_ALLOWED_ORIGINS=https://yourdomain.com,http://localhost:8080,http://localhost:9292 + +# ============================================================================== +# File Upload Configuration +# ============================================================================== + +# Custom upload directory path (optional) +# If not set, defaults to backend/uploads +# TUDUDI_UPLOAD_PATH=/path/to/custom/uploads + +# ============================================================================== +# Email/SMTP Configuration +# ============================================================================== + +# Enable/disable email functionality +ENABLE_EMAIL=false + +# SMTP server configuration +EMAIL_SMTP_HOST=smtp.gmail.com +EMAIL_SMTP_PORT=587 +EMAIL_SMTP_SECURE=false + +# SMTP authentication +EMAIL_SMTP_USERNAME=your-email@example.com +EMAIL_SMTP_PASSWORD=your-app-password + +# Email sender information +EMAIL_FROM_ADDRESS=noreply@example.com +EMAIL_FROM_NAME=Tududi + +# ============================================================================== +# Task Scheduler Configuration +# ============================================================================== + +# Disable the task scheduler (useful for development/testing) +# Set to 'true' to disable recurring task processing +DISABLE_SCHEDULER=false + +# ============================================================================== +# Telegram Bot Configuration +# ============================================================================== + +# Disable Telegram integration (useful for development/testing) +# Set to 'true' to disable Telegram bot functionality +DISABLE_TELEGRAM=false + +# Telegram bot token (get from @BotFather on Telegram) +# TELEGRAM_BOT_TOKEN=your-telegram-bot-token + +# ============================================================================== +# API Documentation (Swagger) +# ============================================================================== + +# Enable/disable Swagger API documentation +# Default: enabled in development, disabled in production +# Set to 'true' to force enable in production (not recommended for public APIs) +# SWAGGER_ENABLED=false + +# ============================================================================== +# API Versioning +# ============================================================================== + +# API version (e.g., v1, v2) +# If not set, defaults to 'v1' +# API_VERSION=v1 + +# ============================================================================== +# Rate Limiting Configuration +# ============================================================================== +# Rate limiting helps prevent abuse and brute force attacks +# All time windows are in milliseconds +# Set RATE_LIMITING_ENABLED=false to completely disable rate limiting + +# Enable/disable rate limiting globally +# Automatically disabled in test environment +# RATE_LIMITING_ENABLED=true + +# Authentication endpoints (login, register) +# Default: 5 requests per 15 minutes +# RATE_LIMIT_AUTH_WINDOW_MS=900000 +# RATE_LIMIT_AUTH_MAX=5 + +# General API for unauthenticated requests +# Default: 100 requests per 15 minutes +# RATE_LIMIT_API_WINDOW_MS=900000 +# RATE_LIMIT_API_MAX=100 + +# Authenticated API requests +# Default: 1000 requests per 15 minutes +# RATE_LIMIT_AUTH_API_WINDOW_MS=900000 +# RATE_LIMIT_AUTH_API_MAX=1000 + +# Resource creation endpoints (POST requests) +# Default: 50 requests per 15 minutes +# RATE_LIMIT_CREATE_WINDOW_MS=900000 +# RATE_LIMIT_CREATE_MAX=50 + +# API key management endpoints +# Default: 10 requests per hour +# RATE_LIMIT_API_KEY_WINDOW_MS=3600000 +# RATE_LIMIT_API_KEY_MAX=10 + +# ============================================================================== +# Production Security Notes +# ============================================================================== +# When deploying to production, make sure to: +# 1. Change NODE_ENV to 'production' +# 2. Use strong, randomly generated TUDUDI_SESSION_SECRET +# 3. Use strong TUDUDI_USER_PASSWORD +# 4. Set proper TUDUDI_ALLOWED_ORIGINS for your domain +# 5. Enable HTTPS and set EMAIL_SMTP_SECURE=true if using TLS +# 6. Keep email passwords and API tokens secure +# 7. Consider adjusting rate limits based on your traffic patterns +# 8. Regularly update dependencies and review security advisories +# ============================================================================== diff --git a/backend/app.js b/backend/app.js index a471cc0..18fd5a5 100644 --- a/backend/app.js +++ b/backend/app.js @@ -12,6 +12,8 @@ const { initializeTelegramPolling } = require('./services/telegramInitializer'); const taskScheduler = require('./services/taskScheduler'); const { setConfig, getConfig } = require('./config/config'); const config = getConfig(); +const API_VERSION = process.env.API_VERSION || 'v1'; +const API_BASE_PATH = `/api/${API_VERSION}`; const app = express(); @@ -86,39 +88,105 @@ if (config.production) { } // Serve uploaded files -app.use('/api/uploads', express.static(config.uploadPath)); +const registerUploadsStatic = (basePath) => { + app.use(`${basePath}/uploads`, express.static(config.uploadPath)); +}; + +registerUploadsStatic('/api'); +if (API_VERSION && API_BASE_PATH !== '/api') { + registerUploadsStatic(API_BASE_PATH); +} // Authentication middleware const { requireAuth } = require('./middleware/auth'); const { logError } = require('./services/logService'); +// Rate limiting middleware +const { + apiLimiter, + authenticatedApiLimiter, +} = require('./middleware/rateLimiter'); + +// Swagger documentation (only enabled in development by default) +if (config.swagger.enabled) { + const swaggerUi = require('swagger-ui-express'); + const swaggerSpec = require('./config/swagger'); + + const swaggerUiOptions = { + customSiteTitle: 'Tududi API Documentation', + customfavIcon: '/favicon.ico', + customCss: '.swagger-ui .topbar { display: none }', + }; + + const registerSwaggerDocs = (basePath) => { + app.use(basePath, swaggerUi.serve); + app.get(basePath, swaggerUi.setup(swaggerSpec, swaggerUiOptions)); + }; + + const docPaths = new Set(['/api']); + if (API_VERSION && API_BASE_PATH !== '/api') { + docPaths.add(API_BASE_PATH); + } + docPaths.forEach(registerSwaggerDocs); +} + +// Apply rate limiting to API routes +// Use both limiters: apiLimiter for unauthenticated, authenticatedApiLimiter for authenticated +// Each has skip logic to handle their specific use case +const registerRateLimiting = (basePath) => { + app.use(basePath, apiLimiter); + app.use(basePath, authenticatedApiLimiter); +}; + +const rateLimitPaths = new Set(['/api']); +if (API_VERSION && API_BASE_PATH !== '/api') { + rateLimitPaths.add(API_BASE_PATH); +} +rateLimitPaths.forEach(registerRateLimiting); + // Health check (before auth middleware) - ensure it's completely bypassed -app.get('/api/health', (req, res) => { - res.status(200).json({ - status: 'ok', - timestamp: new Date().toISOString(), - uptime: process.uptime(), - environment: config.environment, +const registerHealthCheck = (basePath) => { + app.get(`${basePath}/health`, (req, res) => { + res.status(200).json({ + status: 'ok', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: config.environment, + }); }); -}); +}; + +const healthPaths = new Set(['/api']); +if (API_VERSION && API_BASE_PATH !== '/api') { + healthPaths.add(API_BASE_PATH); +} +healthPaths.forEach(registerHealthCheck); // Routes -app.use('/api', require('./routes/auth')); -app.use('/api', requireAuth, require('./routes/tasks')); -app.use('/api', requireAuth, require('./routes/projects')); -app.use('/api', requireAuth, require('./routes/admin')); -app.use('/api', requireAuth, require('./routes/shares')); -app.use('/api', requireAuth, require('./routes/areas')); -app.use('/api', requireAuth, require('./routes/notes')); -app.use('/api', requireAuth, require('./routes/tags')); -app.use('/api', requireAuth, require('./routes/users')); -app.use('/api', requireAuth, require('./routes/inbox')); -app.use('/api', requireAuth, require('./routes/url')); -app.use('/api', requireAuth, require('./routes/telegram')); -app.use('/api', requireAuth, require('./routes/quotes')); -app.use('/api', requireAuth, require('./routes/task-events')); -app.use('/api/search', requireAuth, require('./routes/search')); -app.use('/api/views', requireAuth, require('./routes/views')); +const registerApiRoutes = (basePath) => { + app.use(basePath, require('./routes/auth')); + app.use(basePath, requireAuth, require('./routes/tasks')); + app.use(basePath, requireAuth, require('./routes/projects')); + app.use(basePath, requireAuth, require('./routes/admin')); + app.use(basePath, requireAuth, require('./routes/shares')); + app.use(basePath, requireAuth, require('./routes/areas')); + app.use(basePath, requireAuth, require('./routes/notes')); + app.use(basePath, requireAuth, require('./routes/tags')); + app.use(basePath, requireAuth, require('./routes/users')); + app.use(basePath, requireAuth, require('./routes/inbox')); + app.use(basePath, requireAuth, require('./routes/url')); + app.use(basePath, requireAuth, require('./routes/telegram')); + app.use(basePath, requireAuth, require('./routes/quotes')); + app.use(basePath, requireAuth, require('./routes/task-events')); + app.use(`${basePath}/search`, requireAuth, require('./routes/search')); + app.use(`${basePath}/views`, requireAuth, require('./routes/views')); +}; + +const routeBases = new Set(['/api']); +if (API_VERSION && API_BASE_PATH !== '/api') { + routeBases.add(API_BASE_PATH); +} +routeBases.forEach(registerApiRoutes); // SPA fallback app.get('*', (req, res) => { diff --git a/backend/config/config.js b/backend/config/config.js index 038b2ef..34e69ce 100644 --- a/backend/config/config.js +++ b/backend/config/config.js @@ -67,6 +67,59 @@ const config = { uploadPath: process.env.TUDUDI_UPLOAD_PATH || path.join(projectRootPath, 'uploads'), + + // API Documentation (Swagger) + swagger: { + enabled: process.env.SWAGGER_ENABLED !== 'false' && !production, + }, + + // Rate limiting configuration + rateLimiting: { + // Disable rate limiting in test environment + enabled: + process.env.RATE_LIMITING_ENABLED !== 'false' && + environment !== 'test', + + // Authentication endpoints (login, register) + auth: { + windowMs: + parseInt(process.env.RATE_LIMIT_AUTH_WINDOW_MS) || + 15 * 60 * 1000, // 15 minutes + max: parseInt(process.env.RATE_LIMIT_AUTH_MAX) || 5, // 5 requests per window + }, + + // General API for unauthenticated requests + api: { + windowMs: + parseInt(process.env.RATE_LIMIT_API_WINDOW_MS) || + 15 * 60 * 1000, // 15 minutes + max: parseInt(process.env.RATE_LIMIT_API_MAX) || 100, // 100 requests per window + }, + + // Authenticated API requests + authenticatedApi: { + windowMs: + parseInt(process.env.RATE_LIMIT_AUTH_API_WINDOW_MS) || + 15 * 60 * 1000, // 15 minutes + max: parseInt(process.env.RATE_LIMIT_AUTH_API_MAX) || 1000, // 1000 requests per window + }, + + // Resource creation endpoints + createResource: { + windowMs: + parseInt(process.env.RATE_LIMIT_CREATE_WINDOW_MS) || + 15 * 60 * 1000, // 15 minutes + max: parseInt(process.env.RATE_LIMIT_CREATE_MAX) || 50, // 50 requests per window + }, + + // API key management endpoints + apiKeyManagement: { + windowMs: + parseInt(process.env.RATE_LIMIT_API_KEY_WINDOW_MS) || + 60 * 60 * 1000, // 1 hour + max: parseInt(process.env.RATE_LIMIT_API_KEY_MAX) || 10, // 10 requests per window + }, + }, }; console.log(`Using database file '${config.dbFile}'`); diff --git a/backend/config/swagger.js b/backend/config/swagger.js new file mode 100644 index 0000000..c334053 --- /dev/null +++ b/backend/config/swagger.js @@ -0,0 +1,335 @@ +const swaggerJsdoc = require('swagger-jsdoc'); + +const API_VERSION = process.env.API_VERSION || 'v1'; +const API_BASE_PATH = `/api/${API_VERSION}`; + +const options = { + definition: { + openapi: '3.0.0', + info: { + title: 'Tududi API', + version: '1.0.0', + description: 'REST API for Tududi task management application', + contact: { + name: 'Tududi', + url: 'https://github.com/chrisvel/tududi', + }, + license: { + name: 'MIT', + url: 'https://opensource.org/licenses/MIT', + }, + }, + servers: [ + { + url: 'http://localhost:3002', + description: `Backend server (base path ${API_BASE_PATH})`, + }, + { + url: 'http://localhost:8080', + description: `Frontend dev server (proxy to ${API_BASE_PATH})`, + }, + ], + components: { + securitySchemes: { + cookieAuth: { + type: 'apiKey', + in: 'cookie', + name: 'connect.sid', + description: 'Session cookie authentication', + }, + }, + schemas: { + Error: { + type: 'object', + properties: { + error: { + type: 'string', + description: 'Error type', + }, + message: { + type: 'string', + description: 'Error message', + }, + }, + }, + Task: { + type: 'object', + properties: { + id: { + type: 'integer', + description: 'Task ID', + }, + uid: { + type: 'string', + description: 'Task unique identifier', + }, + name: { + type: 'string', + description: 'Task name', + }, + description: { + type: 'string', + description: + 'Task description (Markdown supported)', + }, + status: { + type: 'string', + enum: ['pending', 'completed', 'archived'], + description: 'Task status', + }, + priority: { + type: 'string', + enum: ['low', 'medium', 'high'], + description: 'Task priority', + }, + due_date: { + type: 'string', + format: 'date-time', + description: 'Task due date', + }, + project_id: { + type: 'integer', + description: 'Associated project ID', + }, + recurring_pattern: { + type: 'string', + enum: ['daily', 'weekly', 'monthly', 'yearly'], + description: 'Recurring pattern', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + Project: { + type: 'object', + properties: { + id: { + type: 'integer', + description: 'Project ID', + }, + uid: { + type: 'string', + description: 'Project unique identifier', + }, + name: { + type: 'string', + description: 'Project name', + }, + description: { + type: 'string', + description: 'Project description', + }, + state: { + type: 'string', + enum: ['active', 'archived', 'completed'], + description: 'Project state', + }, + priority: { + type: 'string', + enum: ['low', 'medium', 'high'], + description: 'Project priority', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + Note: { + type: 'object', + properties: { + id: { + type: 'integer', + description: 'Note ID', + }, + uid: { + type: 'string', + description: 'Note unique identifier', + }, + title: { + type: 'string', + description: 'Note title', + }, + content: { + type: 'string', + description: 'Note content (Markdown supported)', + }, + color: { + type: 'string', + description: 'Note background color (hex)', + }, + project_id: { + type: 'integer', + description: 'Associated project ID', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + Tag: { + type: 'object', + properties: { + id: { + type: 'integer', + description: 'Tag ID', + }, + uid: { + type: 'string', + description: 'Tag unique identifier', + }, + name: { + type: 'string', + description: 'Tag name', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + InboxItem: { + type: 'object', + properties: { + id: { + type: 'integer', + description: 'Inbox item ID', + }, + uid: { + type: 'string', + description: 'Inbox item unique identifier', + }, + content: { + type: 'string', + description: 'Inbox item content', + }, + status: { + type: 'string', + enum: ['added', 'processed', 'ignored'], + description: 'Processing status', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + Area: { + type: 'object', + properties: { + id: { + type: 'integer', + description: 'Area ID', + }, + uid: { + type: 'string', + description: 'Area unique identifier', + }, + name: { + type: 'string', + description: 'Area name', + }, + description: { + type: 'string', + description: 'Area description', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + ApiKey: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { + type: 'string', + description: 'Friendly label for the API key', + }, + token_prefix: { + type: 'string', + description: + 'First characters displayed to help identify the key', + }, + created_at: { + type: 'string', + format: 'date-time', + }, + updated_at: { + type: 'string', + format: 'date-time', + }, + last_used_at: { + type: 'string', + format: 'date-time', + }, + expires_at: { + type: 'string', + format: 'date-time', + }, + revoked_at: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + security: [ + { + cookieAuth: [], + }, + ], + }, + apis: ['./routes/*.js'], // Path to route files for JSDoc comments +}; + +const swaggerSpec = swaggerJsdoc(options); + +if (swaggerSpec?.paths) { + const updatedPaths = {}; + + Object.entries(swaggerSpec.paths).forEach(([pathKey, pathValue]) => { + if (pathKey.startsWith('/api/')) { + const versionedPath = + API_BASE_PATH === '/api' + ? pathKey + : `${API_BASE_PATH}${pathKey.slice(4)}`; + updatedPaths[versionedPath] = pathValue; + } else { + updatedPaths[pathKey] = pathValue; + } + }); + + swaggerSpec.paths = updatedPaths; +} + +module.exports = swaggerSpec; diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index f2ada0e..2d2511d 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -1,4 +1,14 @@ const { User } = require('../models'); +const { findValidTokenByValue } = require('../services/apiTokenService'); + +const getBearerToken = (req) => { + const authHeader = req.headers?.authorization || ''; + const [scheme, token] = authHeader.split(' '); + if (scheme && token && scheme.toLowerCase() === 'bearer') { + return token.trim(); + } + return null; +}; const requireAuth = async (req, res, next) => { try { @@ -8,24 +18,37 @@ const requireAuth = async (req, res, next) => { return next(); } - if (!req.session || !req.session.userId) { + if (req.session && req.session.userId) { + const user = await User.findByPk(req.session.userId); + if (!user) { + req.session.destroy(); + return res.status(401).json({ error: 'User not found' }); + } + req.currentUser = user; + return next(); + } + + const bearerToken = getBearerToken(req); + if (!bearerToken) { return res.status(401).json({ error: 'Authentication required' }); } - const user = await User.findByPk(req.session.userId); + const apiToken = await findValidTokenByValue(bearerToken); + if (!apiToken) { + return res + .status(401) + .json({ error: 'Invalid or expired API token' }); + } + + const user = await User.findByPk(apiToken.user_id); if (!user) { - req.session.destroy(); return res.status(401).json({ error: 'User not found' }); } - // Debug logging to verify correct user is authenticated - if (req.path.includes('/tasks') && req.method === 'GET') { - console.log( - `[AUTH DEBUG] ${req.method} ${req.path} - User: ${user.email} (ID: ${user.id})` - ); - } - req.currentUser = user; + req.authToken = apiToken; + await apiToken.update({ last_used_at: new Date() }); + next(); } catch (error) { console.error('Authentication error:', error); diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js new file mode 100644 index 0000000..f40b64a --- /dev/null +++ b/backend/middleware/rateLimiter.js @@ -0,0 +1,168 @@ +const rateLimit = require('express-rate-limit'); +const { getConfig } = require('../config/config'); + +/** + * Rate limiting middleware for different endpoint types + * + * Rate limits are configured based on authentication state and endpoint sensitivity: + * - Strict limits for authentication endpoints (login, register) + * - Moderate limits for general API endpoints + * - Higher limits for authenticated users with API tokens + * + * Configuration is centralized in backend/config/config.js and can be customized via environment variables. + * Rate limiting is automatically disabled in test environment. + */ + +const config = getConfig(); +const rateLimitConfig = config.rateLimiting; + +// Skip rate limiting if disabled in config +const skipInTest = (req) => !rateLimitConfig.enabled; + +/** + * Strict rate limiting for authentication endpoints + * Prevents brute force attacks on login/register + */ +const authLimiter = rateLimit({ + windowMs: rateLimitConfig.auth.windowMs, + max: rateLimitConfig.auth.max, + message: { + error: 'Too many authentication attempts from this IP, please try again after 15 minutes', + }, + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + skip: skipInTest, + handler: (req, res) => { + res.status(429).json({ + error: 'Too many authentication attempts', + message: + 'You have exceeded the maximum number of login attempts. Please try again after 15 minutes.', + retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), + }); + }, +}); + +/** + * General API rate limiting for unauthenticated requests + */ +const apiLimiter = rateLimit({ + windowMs: rateLimitConfig.api.windowMs, + max: rateLimitConfig.api.max, + message: { + error: 'Too many requests from this IP, please try again later', + }, + standardHeaders: true, + legacyHeaders: false, + // Skip rate limiting for authenticated users or if disabled + skip: (req) => { + // Skip if rate limiting is disabled + if (!rateLimitConfig.enabled) return true; + // If user is authenticated via session or API token, skip this limiter + return !!(req.session?.userId || req.user); + }, + handler: (req, res) => { + res.status(429).json({ + error: 'Rate limit exceeded', + message: + 'You have exceeded the maximum number of requests. Please try again later.', + retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), + }); + }, +}); + +/** + * Rate limiting for authenticated API requests + * More lenient limits for authenticated users + */ +const authenticatedApiLimiter = rateLimit({ + windowMs: rateLimitConfig.authenticatedApi.windowMs, + max: rateLimitConfig.authenticatedApi.max, + standardHeaders: true, + legacyHeaders: false, + // Use user ID as the key instead of IP for authenticated requests + keyGenerator: (req) => { + // Prefer user ID from session or API token authentication + return ( + req.session?.userId?.toString() || + req.user?.id?.toString() || + req.ip + ); + }, + // Only apply to authenticated requests or if disabled + skip: (req) => { + // Skip if rate limiting is disabled + if (!rateLimitConfig.enabled) return true; + // Skip if not authenticated + return !(req.session?.userId || req.user); + }, + handler: (req, res) => { + res.status(429).json({ + error: 'Rate limit exceeded', + message: + 'You have exceeded the maximum number of requests. Please try again later.', + retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), + }); + }, +}); + +/** + * Stricter rate limiting for resource creation endpoints + * Prevents spam and abuse + */ +const createResourceLimiter = rateLimit({ + windowMs: rateLimitConfig.createResource.windowMs, + max: rateLimitConfig.createResource.max, + standardHeaders: true, + legacyHeaders: false, + skip: skipInTest, + keyGenerator: (req) => { + return ( + req.session?.userId?.toString() || + req.user?.id?.toString() || + req.ip + ); + }, + handler: (req, res) => { + res.status(429).json({ + error: 'Rate limit exceeded', + message: + 'You have exceeded the maximum number of resource creation requests. Please try again later.', + retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), + }); + }, +}); + +/** + * Rate limiting for API key management endpoints + * Very strict to prevent abuse + */ +const apiKeyManagementLimiter = rateLimit({ + windowMs: rateLimitConfig.apiKeyManagement.windowMs, + max: rateLimitConfig.apiKeyManagement.max, + standardHeaders: true, + legacyHeaders: false, + skip: skipInTest, + keyGenerator: (req) => { + return ( + req.session?.userId?.toString() || + req.user?.id?.toString() || + req.ip + ); + }, + handler: (req, res) => { + res.status(429).json({ + error: 'Rate limit exceeded', + message: + 'You have exceeded the maximum number of API key management requests. Please try again later.', + retryAfter: Math.ceil(req.rateLimit.resetTime / 1000), + }); + }, +}); + +module.exports = { + authLimiter, + apiLimiter, + authenticatedApiLimiter, + createResourceLimiter, + apiKeyManagementLimiter, +}; diff --git a/backend/migrations/20251115000001-create-api-tokens.js b/backend/migrations/20251115000001-create-api-tokens.js new file mode 100644 index 0000000..f215fea --- /dev/null +++ b/backend/migrations/20251115000001-create-api-tokens.js @@ -0,0 +1,84 @@ +'use strict'; + +const { safeAddIndex } = require('../utils/migration-utils'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + const tableName = 'api_tokens'; + let tableExists = false; + try { + await queryInterface.describeTable(tableName); + tableExists = true; + } catch (error) { + tableExists = false; + } + + if (!tableExists) { + await queryInterface.createTable(tableName, { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + user_id: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id', + }, + onDelete: 'CASCADE', + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + token_hash: { + type: Sequelize.STRING, + allowNull: false, + }, + token_prefix: { + type: Sequelize.STRING(32), + allowNull: false, + }, + abilities: { + type: Sequelize.JSON, + allowNull: true, + }, + expires_at: { + type: Sequelize.DATE, + allowNull: true, + }, + last_used_at: { + type: Sequelize.DATE, + allowNull: true, + }, + revoked_at: { + type: Sequelize.DATE, + allowNull: true, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + updated_at: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + }); + } + + await safeAddIndex(queryInterface, tableName, ['user_id']); + await safeAddIndex(queryInterface, tableName, ['token_prefix'], { + name: 'api_tokens_token_prefix_idx', + unique: false, + }); + }, + + async down(queryInterface) { + await queryInterface.dropTable('api_tokens'); + }, +}; diff --git a/backend/models/api_token.js b/backend/models/api_token.js new file mode 100644 index 0000000..0049a23 --- /dev/null +++ b/backend/models/api_token.js @@ -0,0 +1,52 @@ +const { DataTypes } = require('sequelize'); + +module.exports = (sequelize) => { + const ApiToken = sequelize.define( + 'ApiToken', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + user_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + token_hash: { + type: DataTypes.STRING, + allowNull: false, + }, + token_prefix: { + type: DataTypes.STRING(32), + allowNull: false, + }, + abilities: { + type: DataTypes.JSON, + allowNull: true, + }, + expires_at: { + type: DataTypes.DATE, + allowNull: true, + }, + last_used_at: { + type: DataTypes.DATE, + allowNull: true, + }, + revoked_at: { + type: DataTypes.DATE, + allowNull: true, + }, + }, + { + tableName: 'api_tokens', + underscored: true, + } + ); + + return ApiToken; +}; diff --git a/backend/models/index.js b/backend/models/index.js index d989da8..1239bee 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -33,6 +33,7 @@ const Role = require('./role')(sequelize); const Action = require('./action')(sequelize); const Permission = require('./permission')(sequelize); const View = require('./view')(sequelize); +const ApiToken = require('./api_token')(sequelize); // Define associations User.hasMany(Area, { foreignKey: 'user_id' }); @@ -139,6 +140,9 @@ Action.belongsTo(User, { foreignKey: 'target_user_id', as: 'Target' }); User.hasMany(View, { foreignKey: 'user_id' }); View.belongsTo(User, { foreignKey: 'user_id' }); +User.hasMany(ApiToken, { foreignKey: 'user_id', as: 'apiTokens' }); +ApiToken.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); + module.exports = { sequelize, User, @@ -153,4 +157,5 @@ module.exports = { Action, Permission, View, + ApiToken, }; diff --git a/backend/routes/areas.js b/backend/routes/areas.js index 13c23ff..4b28372 100644 --- a/backend/routes/areas.js +++ b/backend/routes/areas.js @@ -4,12 +4,41 @@ const { isValidUid } = require('../utils/slug-utils'); const { logError } = require('../services/logService'); const _ = require('lodash'); const router = express.Router(); +const { getAuthenticatedUserId } = require('../utils/request-utils'); -// GET /api/areas +/** + * @swagger + * /api/areas: + * get: + * summary: Get all areas + * tags: [Areas] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: List of areas + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Area' + * 401: + * description: Unauthorized + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ router.get('/areas', async (req, res) => { try { + const userId = getAuthenticatedUserId(req); + if (!userId) + return res.status(401).json({ error: 'Authentication required' }); const areas = await Area.findAll({ - where: { user_id: req.session.userId }, + where: { user_id: userId }, attributes: ['id', 'uid', 'name', 'description'], order: [['name', 'ASC']], }); @@ -21,13 +50,50 @@ router.get('/areas', async (req, res) => { } }); -// GET /api/areas/:uid +/** + * @swagger + * /api/areas/{uid}: + * get: + * summary: Get an area by UID + * tags: [Areas] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Area UID + * responses: + * 200: + * description: Area details + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Area' + * 400: + * description: Invalid UID + * 401: + * description: Unauthorized + * 404: + * description: Area not found + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ router.get('/areas/:uid', async (req, res) => { try { + const userId = getAuthenticatedUserId(req); + if (!userId) + return res.status(401).json({ error: 'Authentication required' }); if (!isValidUid(req.params.uid)) return res.status(400).json({ error: 'Invalid UID' }); const area = await Area.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, attributes: ['uid', 'name', 'description'], }); @@ -44,9 +110,54 @@ router.get('/areas/:uid', async (req, res) => { } }); -// POST /api/areas +/** + * @swagger + * /api/areas: + * post: + * summary: Create a new area + * tags: [Areas] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * description: Area name + * example: "Work" + * description: + * type: string + * description: Area description + * example: "Work-related projects and tasks" + * responses: + * 201: + * description: Area created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Area' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ router.post('/areas', async (req, res) => { try { + const userId = getAuthenticatedUserId(req); + if (!userId) + return res.status(401).json({ error: 'Authentication required' }); const { name, description } = req.body; if (!name || _.isEmpty(name.trim())) { @@ -56,7 +167,7 @@ router.post('/areas', async (req, res) => { const area = await Area.create({ name: name.trim(), description: description || '', - user_id: req.session.userId, + user_id: userId, }); res.status(201).json(_.pick(area, ['uid', 'name', 'description'])); @@ -71,13 +182,63 @@ router.post('/areas', async (req, res) => { } }); -// PATCH /api/areas/:uid +/** + * @swagger + * /api/areas/{uid}: + * patch: + * summary: Update an area + * tags: [Areas] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Area UID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: Area name + * description: + * type: string + * description: Area description + * responses: + * 200: + * description: Area updated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Area' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 404: + * description: Area not found + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ router.patch('/areas/:uid', async (req, res) => { try { + const userId = getAuthenticatedUserId(req); + if (!userId) + return res.status(401).json({ error: 'Authentication required' }); if (!isValidUid(req.params.uid)) return res.status(400).json({ error: 'Invalid UID' }); const area = await Area.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, }); if (!area) { @@ -103,14 +264,47 @@ router.patch('/areas/:uid', async (req, res) => { } }); -// DELETE /api/areas/:uid +/** + * @swagger + * /api/areas/{uid}: + * delete: + * summary: Delete an area + * tags: [Areas] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Area UID + * responses: + * 204: + * description: Area deleted successfully + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 404: + * description: Area not found + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ router.delete('/areas/:uid', async (req, res) => { try { + const userId = getAuthenticatedUserId(req); + if (!userId) + return res.status(401).json({ error: 'Authentication required' }); if (!isValidUid(req.params.uid)) return res.status(400).json({ error: 'Invalid UID' }); const area = await Area.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, }); if (!area) { diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 61237dd..458cdd7 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -3,14 +3,67 @@ const { User } = require('../models'); const { isAdmin } = require('../services/rolesService'); const { logError } = require('../services/logService'); const packageJson = require('../../package.json'); +const { authLimiter } = require('../middleware/rateLimiter'); const router = express.Router(); -// Get version +/** + * @swagger + * /api/version: + * get: + * summary: Get API version + * tags: [Authentication] + * responses: + * 200: + * description: API version + * content: + * application/json: + * schema: + * type: object + * properties: + * version: + * type: string + * example: "1.0.0" + */ router.get('/version', (req, res) => { res.json({ version: packageJson.version }); }); -// Get current user +/** + * @swagger + * /api/current_user: + * get: + * summary: Get current authenticated user + * tags: [Authentication] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: Current user information + * content: + * application/json: + * schema: + * type: object + * properties: + * user: + * type: object + * properties: + * uid: + * type: string + * email: + * type: string + * name: + * type: string + * surname: + * type: string + * language: + * type: string + * appearance: + * type: string + * timezone: + * type: string + * is_admin: + * type: boolean + */ router.get('/current_user', async (req, res) => { try { if (req.session && req.session.userId) { @@ -49,8 +102,65 @@ router.get('/current_user', async (req, res) => { } }); -// Login -router.post('/login', async (req, res) => { +/** + * @swagger + * /api/login: + * post: + * summary: Login to the application + * tags: [Authentication] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * - password + * properties: + * email: + * type: string + * format: email + * example: "user@example.com" + * password: + * type: string + * format: password + * example: "password123" + * responses: + * 200: + * description: Successfully logged in + * content: + * application/json: + * schema: + * type: object + * properties: + * user: + * type: object + * properties: + * uid: + * type: string + * email: + * type: string + * name: + * type: string + * surname: + * type: string + * language: + * type: string + * appearance: + * type: string + * timezone: + * type: string + * is_admin: + * type: boolean + * 400: + * description: Invalid parameters + * 401: + * description: Invalid credentials + * 500: + * description: Internal server error + */ +router.post('/login', authLimiter, async (req, res) => { try { const { email, password } = req.body; @@ -99,7 +209,28 @@ router.post('/login', async (req, res) => { } }); -// Logout +/** + * @swagger + * /api/logout: + * get: + * summary: Logout from the application + * tags: [Authentication] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: Successfully logged out + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Logged out successfully" + * 500: + * description: Could not log out + */ router.get('/logout', (req, res) => { req.session.destroy((err) => { if (err) { diff --git a/backend/routes/inbox.js b/backend/routes/inbox.js index ab1d349..166545e 100644 --- a/backend/routes/inbox.js +++ b/backend/routes/inbox.js @@ -4,11 +4,67 @@ const { processInboxItem } = require('../services/inboxProcessingService'); const { isValidUid } = require('../utils/slug-utils'); const _ = require('lodash'); const { logError } = require('../services/logService'); +const { getAuthenticatedUserId } = require('../utils/request-utils'); const router = express.Router(); -// GET /api/inbox +const getUserIdOrUnauthorized = (req, res) => { + const userId = getAuthenticatedUserId(req); + if (!userId) { + res.status(401).json({ error: 'Authentication required' }); + return null; + } + return userId; +}; + +/** + * @swagger + * /api/inbox: + * get: + * summary: Get inbox items + * tags: [Inbox] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: limit + * schema: + * type: integer + * default: 20 + * description: Number of items to return + * - in: query + * name: offset + * schema: + * type: integer + * default: 0 + * description: Number of items to skip + * responses: + * 200: + * description: List of inbox items + * content: + * application/json: + * schema: + * type: object + * properties: + * items: + * type: array + * items: + * $ref: '#/components/schemas/InboxItem' + * pagination: + * type: object + * properties: + * total: + * type: integer + * limit: + * type: integer + * offset: + * type: integer + * 401: + * description: Unauthorized + */ router.get('/inbox', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; // Check if pagination parameters are provided const hasPagination = !_.isEmpty(req.query.limit) || !_.isEmpty(req.query.offset); @@ -21,14 +77,14 @@ router.get('/inbox', async (req, res) => { // Get total count for pagination info const totalCount = await InboxItem.count({ where: { - user_id: req.session.userId, + user_id: userId, status: 'added', }, }); const items = await InboxItem.findAll({ where: { - user_id: req.session.userId, + user_id: userId, status: 'added', }, order: [['created_at', 'DESC']], @@ -49,7 +105,7 @@ router.get('/inbox', async (req, res) => { // Return simple array for backward compatibility (used by tests) const items = await InboxItem.findAll({ where: { - user_id: req.session.userId, + user_id: userId, status: 'added', }, order: [['created_at', 'DESC']], @@ -63,9 +119,47 @@ router.get('/inbox', async (req, res) => { } }); -// POST /api/inbox +/** + * @swagger + * /api/inbox: + * post: + * summary: Create a new inbox item + * tags: [Inbox] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - content + * properties: + * content: + * type: string + * description: Inbox item content + * example: "Remember to call John" + * source: + * type: string + * description: Source of the item + * example: "manual" + * responses: + * 201: + * description: Inbox item created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/InboxItem' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + */ router.post('/inbox', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; const { content, source } = req.body; if (!content || _.isEmpty(content.trim())) { @@ -78,7 +172,7 @@ router.post('/inbox', async (req, res) => { const item = await InboxItem.create({ content: content.trim(), source: finalSource, - user_id: req.session.userId, + user_id: userId, }); res.status(201).json( @@ -105,12 +199,14 @@ router.post('/inbox', async (req, res) => { // GET /api/inbox/:uid router.get('/inbox/:uid', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; if (!isValidUid(req.params.uid)) { return res.status(400).json({ error: 'Invalid UID' }); } const item = await InboxItem.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, attributes: [ 'uid', 'content', @@ -135,12 +231,14 @@ router.get('/inbox/:uid', async (req, res) => { // PATCH /api/inbox/:uid router.patch('/inbox/:uid', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; if (!isValidUid(req.params.uid)) { return res.status(400).json({ error: 'Invalid UID' }); } const item = await InboxItem.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, }); if (_.isEmpty(item)) { @@ -178,12 +276,14 @@ router.patch('/inbox/:uid', async (req, res) => { // DELETE /api/inbox/:uid router.delete('/inbox/:uid', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; if (!isValidUid(req.params.uid)) { return res.status(400).json({ error: 'Invalid UID' }); } const item = await InboxItem.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, }); if (_.isEmpty(item)) { @@ -204,12 +304,14 @@ router.delete('/inbox/:uid', async (req, res) => { // PATCH /api/inbox/:uid/process router.patch('/inbox/:uid/process', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; if (!isValidUid(req.params.uid)) { return res.status(400).json({ error: 'Invalid UID' }); } const item = await InboxItem.findOne({ - where: { uid: req.params.uid, user_id: req.session.userId }, + where: { uid: req.params.uid, user_id: userId }, }); if (_.isEmpty(item)) { diff --git a/backend/routes/notes.js b/backend/routes/notes.js index 420dc29..0e911e5 100644 --- a/backend/routes/notes.js +++ b/backend/routes/notes.js @@ -3,6 +3,16 @@ const { Note, Tag, Project } = require('../models'); const { extractUidFromSlug, isValidUid } = require('../utils/slug-utils'); const { validateTagName } = require('../services/tagsService'); const router = express.Router(); +const { getAuthenticatedUserId } = require('../utils/request-utils'); + +router.use((req, res, next) => { + const userId = getAuthenticatedUserId(req); + if (!userId) { + return res.status(401).json({ error: 'Authentication required' }); + } + req.authUserId = userId; + next(); +}); const permissionsService = require('../services/permissionsService'); const { hasAccess } = require('../middleware/authorize'); const _ = require('lodash'); @@ -54,7 +64,38 @@ async function updateNoteTags(note, tagsArray, userId) { } } -// GET /api/notes +/** + * @swagger + * /api/notes: + * get: + * summary: Get all notes + * tags: [Notes] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: order_by + * schema: + * type: string + * example: "title:asc" + * description: Sort order (field:direction) + * - in: query + * name: project_id + * schema: + * type: integer + * description: Filter by project ID + * responses: + * 200: + * description: List of notes + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Note' + * 401: + * description: Unauthorized + */ router.get('/notes', async (req, res) => { try { const orderBy = req.query.order_by || 'title:asc'; @@ -62,7 +103,7 @@ router.get('/notes', async (req, res) => { const whereClause = await permissionsService.ownershipOrPermissionWhere( 'note', - req.session.userId + req.authUserId ); let includeClause = [ { @@ -139,7 +180,56 @@ router.get( } ); -// POST /api/note +/** + * @swagger + * /api/note: + * post: + * summary: Create a new note + * tags: [Notes] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - title + * - content + * properties: + * title: + * type: string + * description: Note title + * example: "Meeting notes" + * content: + * type: string + * description: Note content (Markdown supported) + * example: "# Meeting Summary\n- Point 1\n- Point 2" + * color: + * type: string + * description: Background color (hex) + * example: "#B71C1C" + * project_uid: + * type: string + * description: Associated project UID + * tags: + * type: array + * items: + * type: string + * description: Array of tag names + * responses: + * 201: + * description: Note created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Note' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + */ router.post('/note', async (req, res) => { try { const { title, content, project_uid, project_id, tags, color } = @@ -148,7 +238,7 @@ router.post('/note', async (req, res) => { const noteAttributes = { title, content, - user_id: req.session.userId, + user_id: req.authUserId, }; // Add color if provided @@ -185,11 +275,11 @@ router.post('/note', async (req, res) => { // Check if user has write access to the project const projectAccess = await permissionsService.getAccess( - req.session.userId, + req.authUserId, 'project', project.uid ); - const isOwner = project.user_id === req.session.userId; + const isOwner = project.user_id === req.authUserId; const canWrite = isOwner || projectAccess === 'rw' || projectAccess === 'admin'; @@ -213,7 +303,7 @@ router.post('/note', async (req, res) => { } } - await updateNoteTags(note, tagNames, req.session.userId); + await updateNoteTags(note, tagNames, req.authUserId); // Reload note with associations const noteWithAssociations = await Note.findByPk(note.id, { @@ -246,6 +336,59 @@ router.post('/note', async (req, res) => { } }); +/** + * @swagger + * /api/note/{uid}: + * patch: + * summary: Update a note + * tags: [Notes] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Note UID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * title: + * type: string + * description: Note title + * content: + * type: string + * description: Note content (Markdown supported) + * color: + * type: string + * description: Background color (hex) + * project_uid: + * type: string + * description: Associated project UID + * tags: + * type: array + * items: + * type: string + * description: Array of tag names + * responses: + * 200: + * description: Note updated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Note' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 404: + * description: Note not found + */ router.patch( '/note/:uid', hasAccess( @@ -304,11 +447,11 @@ router.patch( .json({ error: 'Invalid project.' }); } const projectAccess = await permissionsService.getAccess( - req.session.userId, + req.authUserId, 'project', project.uid ); - const isOwner = project.user_id === req.session.userId; + const isOwner = project.user_id === req.authUserId; const canWrite = isOwner || projectAccess === 'rw' || @@ -336,7 +479,7 @@ router.patch( tagNames = tags.map((t) => t.name); } } - await updateNoteTags(note, tagNames, req.session.userId); + await updateNoteTags(note, tagNames, req.authUserId); } // Reload note with associations @@ -368,6 +511,37 @@ router.patch( } ); +/** + * @swagger + * /api/note/{uid}: + * delete: + * summary: Delete a note + * tags: [Notes] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Note UID + * responses: + * 200: + * description: Note deleted successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Note deleted successfully." + * 401: + * description: Unauthorized + * 404: + * description: Note not found + */ router.delete( '/note/:uid', hasAccess( diff --git a/backend/routes/projects.js b/backend/routes/projects.js index 87bf4b1..752f1bc 100644 --- a/backend/routes/projects.js +++ b/backend/routes/projects.js @@ -21,6 +21,16 @@ const { validateTagName } = require('../services/tagsService'); const { uid } = require('../utils/uid'); const { logError } = require('../services/logService'); const router = express.Router(); +const { getAuthenticatedUserId } = require('../utils/request-utils'); + +router.use((req, res, next) => { + const userId = getAuthenticatedUserId(req); + if (!userId) { + return res.status(401).json({ error: 'Authentication required' }); + } + req.authUserId = userId; + next(); +}); const { hasAccess } = require('../middleware/authorize'); const { requireAuth } = require('../middleware/auth'); @@ -146,7 +156,38 @@ router.post( } ); -// GET /api/projects +/** + * @swagger + * /api/projects: + * get: + * summary: Get all projects + * tags: [Projects] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: state + * schema: + * type: string + * enum: [planned, in_progress, blocked, completed, archived, all] + * description: Filter by project state + * - in: query + * name: area_id + * schema: + * type: integer + * description: Filter by area ID + * responses: + * 200: + * description: List of projects + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Project' + * 401: + * description: Unauthorized + */ router.get('/projects', async (req, res) => { try { const { state, active, pin_to_sidebar, area_id, area } = req.query; @@ -155,7 +196,7 @@ router.get('/projects', async (req, res) => { const ownedOrShared = await permissionsService.ownershipOrPermissionWhere( 'project', - req.session.userId + req.authUserId ); let whereClause = ownedOrShared; @@ -468,7 +509,66 @@ router.get( } ); -// POST /api/project +/** + * @swagger + * /api/project: + * post: + * summary: Create a new project + * tags: [Projects] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * description: Project name + * example: "Website Redesign" + * description: + * type: string + * description: Project description + * example: "Complete redesign of company website" + * priority: + * type: string + * enum: [low, medium, high] + * description: Project priority + * state: + * type: string + * enum: [idea, planned, in_progress, blocked, completed, archived] + * description: Project state + * area_id: + * type: integer + * description: Associated area ID + * due_date_at: + * type: string + * format: date-time + * description: Project due date + * image_url: + * type: string + * description: Project image URL + * tags: + * type: array + * items: + * type: string + * description: Array of tag names + * responses: + * 201: + * description: Project created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Project' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + */ router.post('/project', async (req, res) => { try { const { @@ -503,7 +603,7 @@ router.post('/project', async (req, res) => { due_date_at: due_date_at || null, image_url: image_url || null, state: state || 'idea', - user_id: req.session.userId, + user_id: req.authUserId, }; // Create is always allowed for the authenticated user; project is owned by creator @@ -511,7 +611,7 @@ router.post('/project', async (req, res) => { // Update tags if provided, but don't let tag errors break project creation try { - await updateProjectTags(project, tagsData, req.session.userId); + await updateProjectTags(project, tagsData, req.authUserId); } catch (tagError) { logError( 'Tag update failed, but project created successfully:', @@ -536,7 +636,74 @@ router.post('/project', async (req, res) => { } }); -// PATCH /api/project/:uid +/** + * @swagger + * /api/project/{uid}: + * patch: + * summary: Update a project + * tags: [Projects] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Project UID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: Project name + * description: + * type: string + * description: Project description + * priority: + * type: string + * enum: [low, medium, high] + * description: Project priority + * state: + * type: string + * enum: [idea, planned, in_progress, blocked, completed, archived] + * description: Project state + * area_id: + * type: integer + * description: Associated area ID + * due_date_at: + * type: string + * format: date-time + * description: Project due date + * image_url: + * type: string + * description: Project image URL + * pin_to_sidebar: + * type: boolean + * description: Pin project to sidebar + * tags: + * type: array + * items: + * type: string + * description: Array of tag names + * responses: + * 200: + * description: Project updated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Project' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 404: + * description: Project not found + */ router.patch( '/project/:uid', hasAccess( @@ -587,7 +754,7 @@ router.patch( if (state !== undefined) updateData.state = state; await project.update(updateData); - await updateProjectTags(project, tagsData, req.session.userId); + await updateProjectTags(project, tagsData, req.authUserId); // Reload project with associations const projectWithAssociations = await Project.findByPk(project.id, { @@ -624,7 +791,37 @@ router.patch( } ); -// DELETE /api/project/:uid +/** + * @swagger + * /api/project/{uid}: + * delete: + * summary: Delete a project + * tags: [Projects] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: uid + * required: true + * schema: + * type: string + * description: Project UID + * responses: + * 200: + * description: Project deleted successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Project deleted successfully." + * 401: + * description: Unauthorized + * 404: + * description: Project not found + */ router.delete( '/project/:uid', hasAccess( @@ -661,7 +858,7 @@ router.delete( { where: { project_id: project.id, - user_id: req.session.userId, + user_id: req.authUserId, }, transaction, } @@ -673,7 +870,7 @@ router.delete( { where: { project_id: project.id, - user_id: req.session.userId, + user_id: req.authUserId, }, transaction, } diff --git a/backend/routes/shares.js b/backend/routes/shares.js index 94c4836..a077453 100644 --- a/backend/routes/shares.js +++ b/backend/routes/shares.js @@ -3,6 +3,16 @@ const { User, Permission, Project, Task, Note } = require('../models'); const { execAction } = require('../services/execAction'); const { logError } = require('../services/logService'); const router = express.Router(); +const { getAuthenticatedUserId } = require('../utils/request-utils'); + +const getUserIdOrUnauthorized = (req, res) => { + const userId = getAuthenticatedUserId(req); + if (!userId) { + res.status(401).json({ error: 'Authentication required' }); + return null; + } + return userId; +}; const permissionsService = require('../services/permissionsService'); const { isAdmin } = require('../services/rolesService'); @@ -37,6 +47,8 @@ async function isResourceOwner(userId, resourceType, resourceUid) { // POST /api/shares router.post('/shares', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; const { resource_type, resource_uid, target_user_email, access_level } = req.body; if ( @@ -48,9 +60,9 @@ router.post('/shares', async (req, res) => { return res.status(400).json({ error: 'Missing parameters' }); } // Only owner (or admin) can grant shares - const userIsAdmin = await isAdmin(req.session.userId); + const userIsAdmin = await isAdmin(userId); const userIsOwner = await isResourceOwner( - req.session.userId, + userId, resource_type, resource_uid ); @@ -96,7 +108,7 @@ router.post('/shares', async (req, res) => { await execAction({ verb: 'share_grant', - actorUserId: req.session.userId, + actorUserId: userId, targetUserId: target.id, resourceType: resource_type, resourceUid: resource_uid, @@ -112,14 +124,16 @@ router.post('/shares', async (req, res) => { // DELETE /api/shares router.delete('/shares', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; const { resource_type, resource_uid, target_user_id } = req.body; if (!resource_type || !resource_uid || !target_user_id) { return res.status(400).json({ error: 'Missing parameters' }); } // Only owner (or admin) can revoke shares - const userIsAdmin = await isAdmin(req.session.userId); + const userIsAdmin = await isAdmin(userId); const userIsOwner = await isResourceOwner( - req.session.userId, + userId, resource_type, resource_uid ); @@ -156,7 +170,7 @@ router.delete('/shares', async (req, res) => { await execAction({ verb: 'share_revoke', - actorUserId: req.session.userId, + actorUserId: userId, targetUserId: Number(target_user_id), resourceType: resource_type, resourceUid: resource_uid, @@ -171,15 +185,17 @@ router.delete('/shares', async (req, res) => { // GET /api/shares?resource_type=...&resource_uid=... router.get('/shares', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; const { resource_type, resource_uid } = req.query; if (!resource_type || !resource_uid) { return res.status(400).json({ error: 'Missing parameters' }); } // Only owner (or admin) can view shares - const userIsAdmin = await isAdmin(req.session.userId); + const userIsAdmin = await isAdmin(userId); const userIsOwner = await isResourceOwner( - req.session.userId, + userId, resource_type, resource_uid ); diff --git a/backend/routes/tags.js b/backend/routes/tags.js index 8b0b568..5b8a21b 100644 --- a/backend/routes/tags.js +++ b/backend/routes/tags.js @@ -7,7 +7,26 @@ const _ = require('lodash'); const { Op } = require('sequelize'); const { logError } = require('../services/logService'); -// GET /api/tags +/** + * @swagger + * /api/tags: + * get: + * summary: Get all tags + * tags: [Tags] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: List of tags + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/Tag' + * 401: + * description: Unauthorized + */ router.get('/tags', async (req, res) => { try { const tags = await Tag.findAll({ @@ -53,7 +72,39 @@ router.get('/tag', async (req, res) => { } }); -// POST /api/tag +/** + * @swagger + * /api/tag: + * post: + * summary: Create a new tag + * tags: [Tags] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * description: Tag name + * example: "urgent" + * responses: + * 201: + * description: Tag created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Tag' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + */ router.post('/tag', async (req, res) => { try { const { name } = req.body; diff --git a/backend/routes/tasks.js b/backend/routes/tasks.js index fd2b30b..82d8fb5 100644 --- a/backend/routes/tasks.js +++ b/backend/routes/tasks.js @@ -1182,7 +1182,68 @@ async function computeTaskMetrics(userId, userTimezone = 'UTC') { }; } -// GET /api/tasks +/** + * @swagger + * /api/tasks: + * get: + * summary: Get tasks with filtering and grouping options + * tags: [Tasks] + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: type + * schema: + * type: string + * enum: [today, upcoming, completed, archived, all] + * description: Filter tasks by type + * - in: query + * name: status + * schema: + * type: string + * enum: [pending, completed, archived] + * description: Filter by task status + * - in: query + * name: project_id + * schema: + * type: integer + * description: Filter by project ID + * - in: query + * name: groupBy + * schema: + * type: string + * enum: [day, project] + * description: Group tasks by day or project + * - in: query + * name: order_by + * schema: + * type: string + * example: "created_at:desc" + * description: Sort order (field:direction) + * responses: + * 200: + * description: List of tasks with metrics + * content: + * application/json: + * schema: + * type: object + * properties: + * tasks: + * type: array + * items: + * $ref: '#/components/schemas/Task' + * metrics: + * type: object + * properties: + * total_open_tasks: + * type: integer + * tasks_pending_over_month: + * type: integer + * tasks_in_progress_count: + * type: integer + * 401: + * description: Unauthorized + */ router.get('/tasks', async (req, res) => { try { // Generate recurring tasks for upcoming view, but prevent concurrent execution @@ -1453,7 +1514,66 @@ router.get('/task/:id/subtasks', async (req, res) => { } }); -// POST /api/task +/** + * @swagger + * /api/task: + * post: + * summary: Create a new task + * tags: [Tasks] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * description: Task name + * example: "Complete project documentation" + * priority: + * type: string + * enum: [low, medium, high] + * description: Task priority + * status: + * type: string + * enum: [pending, completed, archived] + * description: Task status + * due_date: + * type: string + * format: date-time + * description: Task due date + * project_id: + * type: integer + * description: Associated project ID + * note: + * type: string + * description: Task description (Markdown supported) + * tags: + * type: array + * items: + * type: string + * description: Array of tag names + * recurrence_type: + * type: string + * enum: [daily, weekly, monthly, yearly] + * description: Recurring pattern + * responses: + * 201: + * description: Task created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Task' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + */ router.post('/task', async (req, res) => { try { const { @@ -1676,7 +1796,72 @@ router.post('/task', async (req, res) => { } }); -// PATCH /api/task/:id +/** + * @swagger + * /api/task/{id}: + * patch: + * summary: Update a task + * tags: [Tasks] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: Task ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: Task name + * note: + * type: string + * description: Task description (Markdown supported) + * priority: + * type: string + * enum: [low, medium, high] + * description: Task priority + * status: + * type: string + * enum: [pending, completed, archived] + * description: Task status + * due_date: + * type: string + * format: date-time + * description: Task due date + * project_id: + * type: integer + * description: Associated project ID + * tags: + * type: array + * items: + * type: string + * description: Array of tag names + * recurrence_type: + * type: string + * enum: [daily, weekly, monthly, yearly] + * description: Recurring pattern + * responses: + * 200: + * description: Task updated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Task' + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 404: + * description: Task not found + */ router.patch( '/task/:id', hasAccess( @@ -2483,7 +2668,37 @@ router.patch( } ); -// DELETE /api/task/:id +/** + * @swagger + * /api/task/{id}: + * delete: + * summary: Delete a task + * tags: [Tasks] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: Task ID + * responses: + * 200: + * description: Task deleted successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Task deleted successfully." + * 401: + * description: Unauthorized + * 404: + * description: Task not found + */ router.delete( '/task/:id', hasAccess( diff --git a/backend/routes/telegram.js b/backend/routes/telegram.js index f16a859..74524f1 100644 --- a/backend/routes/telegram.js +++ b/backend/routes/telegram.js @@ -4,11 +4,23 @@ const { logError } = require('../services/logService'); const telegramPoller = require('../services/telegramPoller'); const { getBotInfo } = require('../services/telegramApi'); const router = express.Router(); +const { getAuthenticatedUserId } = require('../utils/request-utils'); + +const getUserIdOrUnauthorized = (req, res) => { + const userId = getAuthenticatedUserId(req); + if (!userId) { + res.status(401).json({ error: 'Authentication required' }); + return null; + } + return userId; +}; // POST /api/telegram/start-polling router.post('/telegram/start-polling', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; + const user = await User.findByPk(userId); if (!user || !user.telegram_bot_token) { return res .status(400) @@ -37,7 +49,9 @@ router.post('/telegram/start-polling', async (req, res) => { // POST /api/telegram/stop-polling router.post('/telegram/stop-polling', async (req, res) => { try { - const success = telegramPoller.removeUser(req.session.userId); + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; + const success = telegramPoller.removeUser(userId); res.json({ success: true, @@ -66,6 +80,8 @@ router.get('/telegram/polling-status', async (req, res) => { // POST /api/telegram/setup router.post('/telegram/setup', async (req, res) => { try { + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; const { token } = req.body; if (!token) { @@ -74,7 +90,7 @@ router.post('/telegram/setup', async (req, res) => { .json({ error: 'Telegram bot token is required.' }); } - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(userId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } @@ -123,7 +139,9 @@ router.post('/telegram/setup', async (req, res) => { // POST /api/telegram/send-welcome router.post('/telegram/send-welcome', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const userId = getUserIdOrUnauthorized(req, res); + if (!userId) return; + const user = await User.findByPk(userId); if (!user || !user.telegram_bot_token) { return res .status(400) diff --git a/backend/routes/users.js b/backend/routes/users.js index 7b152d0..466e238 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -1,8 +1,26 @@ const express = require('express'); -const { User, Role } = require('../models'); +const { User, Role, ApiToken } = require('../models'); +const _ = require('lodash'); const { logError } = require('../services/logService'); const taskSummaryService = require('../services/taskSummaryService'); const router = express.Router(); +const { getAuthenticatedUserId } = require('../utils/request-utils'); +const { + createApiToken, + revokeApiToken, + deleteApiToken, + serializeApiToken, +} = require('../services/apiTokenService'); +const { apiKeyManagementLimiter } = require('../middleware/rateLimiter'); + +router.use((req, res, next) => { + const userId = getAuthenticatedUserId(req); + if (!userId) { + return res.status(401).json({ error: 'Authentication required' }); + } + req.authUserId = userId; + next(); +}); const VALID_FREQUENCIES = [ 'daily', @@ -44,10 +62,61 @@ router.get('/users', async (req, res) => { } }); -// GET /api/profile +/** + * @swagger + * /api/profile: + * get: + * summary: Get user profile + * tags: [Profile] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: User profile details + * content: + * application/json: + * schema: + * type: object + * properties: + * uid: + * type: string + * email: + * type: string + * name: + * type: string + * surname: + * type: string + * appearance: + * type: string + * enum: [light, dark, system] + * language: + * type: string + * timezone: + * type: string + * first_day_of_week: + * type: integer + * avatar_image: + * type: string + * telegram_bot_token: + * type: string + * telegram_chat_id: + * type: string + * task_summary_enabled: + * type: boolean + * task_summary_frequency: + * type: string + * task_intelligence_enabled: + * type: boolean + * pomodoro_enabled: + * type: boolean + * 401: + * description: Unauthorized + * 404: + * description: Profile not found + */ router.get('/profile', async (req, res) => { try { - const user = await User.findByPk(req.session.userId, { + const user = await User.findByPk(req.authUserId, { attributes: [ 'uid', 'email', @@ -94,10 +163,82 @@ router.get('/profile', async (req, res) => { } }); -// PATCH /api/profile +/** + * @swagger + * /api/profile: + * patch: + * summary: Update user profile + * tags: [Profile] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * description: User's first name + * surname: + * type: string + * description: User's last name + * appearance: + * type: string + * enum: [light, dark, system] + * description: Theme preference + * language: + * type: string + * description: Language code (e.g., "en", "es") + * timezone: + * type: string + * description: Timezone (e.g., "America/New_York") + * first_day_of_week: + * type: integer + * description: First day of week (0=Sunday, 1=Monday) + * avatar_image: + * type: string + * description: Avatar image URL + * telegram_bot_token: + * type: string + * description: Telegram bot token + * telegram_allowed_users: + * type: string + * description: Comma-separated list of allowed Telegram users + * task_intelligence_enabled: + * type: boolean + * description: Enable task intelligence features + * task_summary_enabled: + * type: boolean + * description: Enable task summary emails + * pomodoro_enabled: + * type: boolean + * description: Enable Pomodoro timer + * responses: + * 200: + * description: Profile updated successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * uid: + * type: string + * email: + * type: string + * name: + * type: string + * 400: + * description: Invalid request + * 401: + * description: Unauthorized + * 404: + * description: Profile not found + */ router.patch('/profile', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'Profile not found.' }); } @@ -252,7 +393,7 @@ router.post('/profile/change-password', async (req, res) => { }); } - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found' }); } @@ -280,10 +421,203 @@ router.post('/profile/change-password', async (req, res) => { } }); +/** + * @swagger + * /api/profile/api-keys: + * get: + * summary: List API keys for the current user + * tags: [Profile] + * security: + * - cookieAuth: [] + * responses: + * 200: + * description: List of API keys + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/ApiKey' + */ +router.get('/profile/api-keys', apiKeyManagementLimiter, async (req, res) => { + try { + const tokens = await ApiToken.findAll({ + where: { user_id: req.authUserId }, + order: [['created_at', 'DESC']], + }); + + res.json(tokens.map(serializeApiToken)); + } catch (error) { + logError('Error listing API keys:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +/** + * @swagger + * /api/profile/api-keys: + * post: + * summary: Create a new API key + * tags: [Profile] + * security: + * - cookieAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * description: Friendly name for the API key + * expires_at: + * type: string + * format: date-time + * description: Optional expiration timestamp + * responses: + * 201: + * description: API key created + * content: + * application/json: + * schema: + * type: object + * properties: + * token: + * type: string + * description: The plain API key. This value is only returned once. + * apiKey: + * $ref: '#/components/schemas/ApiKey' + * 400: + * description: Invalid payload + */ +router.post('/profile/api-keys', apiKeyManagementLimiter, async (req, res) => { + try { + const { name, expires_at } = req.body || {}; + + if (!name || _.isEmpty(name.trim())) { + return res.status(400).json({ error: 'API key name is required.' }); + } + + let expiresAtDate = null; + if (expires_at) { + const parsedDate = new Date(expires_at); + if (Number.isNaN(parsedDate.getTime())) { + return res + .status(400) + .json({ error: 'expires_at must be a valid date.' }); + } + expiresAtDate = parsedDate; + } + + const { rawToken, tokenRecord } = await createApiToken({ + userId: req.authUserId, + name: name.trim(), + expiresAt: expiresAtDate, + }); + + res.status(201).json({ + token: rawToken, + apiKey: serializeApiToken(tokenRecord), + }); + } catch (error) { + logError('Error creating API key:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +/** + * @swagger + * /api/profile/api-keys/{id}/revoke: + * post: + * summary: Revoke an API key + * tags: [Profile] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 200: + * description: Revoked key details + * 404: + * description: API key not found + */ +router.post( + '/profile/api-keys/:id/revoke', + apiKeyManagementLimiter, + async (req, res) => { + try { + const tokenId = parseInt(req.params.id, 10); + if (Number.isNaN(tokenId)) { + return res.status(400).json({ error: 'Invalid API key id.' }); + } + + const token = await revokeApiToken(tokenId, req.authUserId); + if (!token) { + return res.status(404).json({ error: 'API key not found.' }); + } + + res.json(serializeApiToken(token)); + } catch (error) { + logError('Error revoking API key:', error); + res.status(500).json({ error: 'Internal server error' }); + } + } +); + +/** + * @swagger + * /api/profile/api-keys/{id}: + * delete: + * summary: Delete an API key + * tags: [Profile] + * security: + * - cookieAuth: [] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * responses: + * 204: + * description: API key deleted + * 404: + * description: API key not found + */ +router.delete( + '/profile/api-keys/:id', + apiKeyManagementLimiter, + async (req, res) => { + try { + const tokenId = parseInt(req.params.id, 10); + if (Number.isNaN(tokenId)) { + return res.status(400).json({ error: 'Invalid API key id.' }); + } + + const deleted = await deleteApiToken(tokenId, req.authUserId); + if (!deleted) { + return res.status(404).json({ error: 'API key not found.' }); + } + + res.status(204).send(); + } catch (error) { + logError('Error deleting API key:', error); + res.status(500).json({ error: 'Internal server error' }); + } + } +); + // POST /api/profile/task-summary/toggle router.post('/profile/task-summary/toggle', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } @@ -326,7 +660,7 @@ router.post('/profile/task-summary/frequency', async (req, res) => { return res.status(400).json({ error: 'Invalid frequency value.' }); } - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } @@ -352,7 +686,7 @@ router.post('/profile/task-summary/frequency', async (req, res) => { // POST /api/profile/task-summary/send-now router.post('/profile/task-summary/send-now', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } @@ -388,7 +722,7 @@ router.post('/profile/task-summary/send-now', async (req, res) => { // GET /api/profile/task-summary/status router.get('/profile/task-summary/status', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } @@ -409,7 +743,7 @@ router.get('/profile/task-summary/status', async (req, res) => { // PUT /api/profile/today-settings router.put('/profile/today-settings', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } @@ -485,7 +819,7 @@ router.put('/profile/today-settings', async (req, res) => { // PUT /api/profile/sidebar-settings router.put('/profile/sidebar-settings', async (req, res) => { try { - const user = await User.findByPk(req.session.userId); + const user = await User.findByPk(req.authUserId); if (!user) { return res.status(404).json({ error: 'User not found.' }); } diff --git a/backend/services/apiTokenService.js b/backend/services/apiTokenService.js new file mode 100644 index 0000000..daca73d --- /dev/null +++ b/backend/services/apiTokenService.js @@ -0,0 +1,98 @@ +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const { ApiToken } = require('../models'); + +const TOKEN_PREFIX_LENGTH = 12; + +const serializeApiToken = (tokenInstance) => { + if (!tokenInstance) return null; + const tokenJson = tokenInstance.toJSON(); + return { + id: tokenJson.id, + name: tokenJson.name, + token_prefix: tokenJson.token_prefix, + created_at: tokenJson.created_at, + updated_at: tokenJson.updated_at, + last_used_at: tokenJson.last_used_at, + expires_at: tokenJson.expires_at, + revoked_at: tokenJson.revoked_at, + }; +}; + +const generateRawToken = () => `tt_${crypto.randomBytes(32).toString('hex')}`; + +async function createApiToken({ userId, name, expiresAt, abilities = null }) { + const rawToken = generateRawToken(); + const tokenHash = await bcrypt.hash(rawToken, 12); + const tokenPrefix = rawToken.slice(0, TOKEN_PREFIX_LENGTH); + + const tokenRecord = await ApiToken.create({ + user_id: userId, + name: name || 'Personal Access Token', + token_hash: tokenHash, + token_prefix: tokenPrefix, + abilities, + expires_at: expiresAt || null, + }); + + return { rawToken, tokenRecord }; +} + +async function findValidTokenByValue(tokenValue) { + if (!tokenValue) return null; + const prefix = tokenValue.slice(0, TOKEN_PREFIX_LENGTH); + const possibleTokens = await ApiToken.findAll({ + where: { token_prefix: prefix }, + order: [['created_at', 'DESC']], + }); + + for (const token of possibleTokens) { + if (token.revoked_at) continue; + if (token.expires_at && token.expires_at < new Date()) continue; + const match = await bcrypt.compare(tokenValue, token.token_hash); + if (match) { + return token; + } + } + + return null; +} + +async function revokeApiToken(tokenId, userId) { + const token = await ApiToken.findOne({ + where: { id: tokenId, user_id: userId }, + }); + + if (!token) { + return null; + } + + if (!token.revoked_at) { + token.revoked_at = new Date(); + await token.save(); + } + + return token; +} + +async function deleteApiToken(tokenId, userId) { + const token = await ApiToken.findOne({ + where: { id: tokenId, user_id: userId }, + }); + + if (!token) { + return null; + } + + await token.destroy(); + return true; +} + +module.exports = { + createApiToken, + revokeApiToken, + deleteApiToken, + findValidTokenByValue, + serializeApiToken, + TOKEN_PREFIX_LENGTH, +}; diff --git a/backend/services/permissionsService.js b/backend/services/permissionsService.js index 38ed064..4b357b2 100644 --- a/backend/services/permissionsService.js +++ b/backend/services/permissionsService.js @@ -107,40 +107,20 @@ async function ownershipOrPermissionWhere(resourceType, userId) { }); if (user) { userUid = user.uid; - console.log( - `[PERMISSIONS DEBUG] User lookup: ID=${userId}, UID=${userUid}, Email=${user.email}` - ); } } const isUserAdmin = await isAdmin(userUid); - console.log( - `[PERMISSIONS DEBUG] Resource: ${resourceType}, UserId: ${userId}, IsAdmin: ${isUserAdmin}` - ); // Admin users should NOT see all resources automatically // They should only see their own resources and shared resources, like regular users // If admin-level system-wide visibility is needed, it should be via dedicated admin endpoints - // if (isUserAdmin) { - // console.log( - // `[PERMISSIONS DEBUG] User is admin, returning empty where clause (all resources visible)` - // ); - // return {}; // empty where clause = no restriction - // } const sharedUids = await getSharedUidsForUser(resourceType, userId); - console.log( - `[PERMISSIONS DEBUG] Shared ${resourceType} UIDs for user ${userId}:`, - sharedUids - ); // For tasks and notes, also include items from shared projects if (resourceType === 'task' || resourceType === 'note') { const sharedProjectUids = await getSharedUidsForUser('project', userId); - console.log( - `[PERMISSIONS DEBUG] Shared project UIDs for user ${userId}:`, - sharedProjectUids - ); // Get the project IDs for shared projects let sharedProjectIds = []; @@ -151,10 +131,6 @@ async function ownershipOrPermissionWhere(resourceType, userId) { raw: true, }); sharedProjectIds = projects.map((p) => p.id); - console.log( - `[PERMISSIONS DEBUG] Shared project IDs for user ${userId}:`, - sharedProjectIds - ); } const conditions = [ @@ -169,10 +145,6 @@ async function ownershipOrPermissionWhere(resourceType, userId) { conditions.push({ project_id: { [Op.in]: sharedProjectIds } }); // Items in shared projects } - console.log( - `[PERMISSIONS DEBUG] Final where conditions for ${resourceType}:`, - JSON.stringify(conditions) - ); return { [Op.or]: conditions }; } diff --git a/backend/tests/integration/api-tokens.test.js b/backend/tests/integration/api-tokens.test.js new file mode 100644 index 0000000..a7c9dc8 --- /dev/null +++ b/backend/tests/integration/api-tokens.test.js @@ -0,0 +1,544 @@ +const request = require('supertest'); +const app = require('../../app'); +const { User, ApiToken, Task, Project, Note } = require('../../models'); +const { createTestUser } = require('../helpers/testUtils'); + +describe('API Token Authentication', () => { + let user, agent; + + beforeEach(async () => { + user = await createTestUser({ + email: `test_${Date.now()}@example.com`, + }); + + // Create authenticated agent + agent = request.agent(app); + await agent.post('/api/login').send({ + email: user.email, + password: 'password123', + }); + }); + + describe('POST /api/profile/api-keys - Create API Key', () => { + it('should create a new API key', async () => { + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Test API Key', + }); + + expect(response.status).toBe(201); + expect(response.body.token).toBeDefined(); + expect(response.body.token).toMatch(/^tt_[a-zA-Z0-9]{64}$/); + expect(response.body.apiKey).toBeDefined(); + expect(response.body.apiKey.name).toBe('Test API Key'); + expect(response.body.apiKey.token_prefix).toBeDefined(); + expect(response.body.apiKey.created_at).toBeDefined(); + expect(response.body.apiKey).not.toHaveProperty('token_hash'); + + // Save for later tests + rawToken = response.body.token; + }); + + it('should create API key with expiration date', async () => { + const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days from now + + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Expiring Key', + expires_at: expiresAt.toISOString(), + }); + + expect(response.status).toBe(201); + expect(response.body.apiKey.expires_at).toBeDefined(); + expect(new Date(response.body.apiKey.expires_at)).toEqual( + expiresAt + ); + }); + + it('should reject API key creation without name', async () => { + const response = await agent.post('/api/profile/api-keys').send({}); + + expect(response.status).toBe(400); + expect(response.body.error).toBe('API key name is required.'); + }); + + it('should reject invalid expiration date', async () => { + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Test Key', + expires_at: 'invalid-date', + }); + + expect(response.status).toBe(400); + expect(response.body.error).toBe( + 'expires_at must be a valid date.' + ); + }); + + it('should require authentication', async () => { + const response = await request(app) + .post('/api/profile/api-keys') + .send({ + name: 'Test Key', + }); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authentication required'); + }); + }); + + describe('GET /api/profile/api-keys - List API Keys', () => { + beforeEach(async () => { + // Create a few API keys + await agent.post('/api/profile/api-keys').send({ + name: 'Key 1', + }); + await agent.post('/api/profile/api-keys').send({ + name: 'Key 2', + }); + }); + + it('should list all API keys for the user', async () => { + const response = await agent.get('/api/profile/api-keys'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBeGreaterThanOrEqual(2); + expect(response.body[0]).toHaveProperty('name'); + expect(response.body[0]).toHaveProperty('token_prefix'); + expect(response.body[0]).toHaveProperty('created_at'); + expect(response.body[0]).not.toHaveProperty('token_hash'); + }); + + it('should not show other users API keys', async () => { + // Create another user with API key + const otherUser = await createTestUser({ + email: `other_${Date.now()}@example.com`, + }); + const otherAgent = request.agent(app); + await otherAgent.post('/api/login').send({ + email: otherUser.email, + password: 'password123', + }); + await otherAgent.post('/api/profile/api-keys').send({ + name: 'Other User Key', + }); + + // Check that current user doesn't see other user's keys + const response = await agent.get('/api/profile/api-keys'); + const hasOtherUserKey = response.body.some( + (key) => key.name === 'Other User Key' + ); + expect(hasOtherUserKey).toBe(false); + }); + + it('should require authentication', async () => { + const response = await request(app).get('/api/profile/api-keys'); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authentication required'); + }); + }); + + describe('Bearer Token Authentication', () => { + beforeEach(async () => { + // Create an API token + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Test Token', + }); + rawToken = response.body.token; + }); + + describe('Tasks API with Bearer Token', () => { + it('should authenticate GET /api/tasks with Bearer token', async () => { + const response = await request(app) + .get('/api/tasks') + .set('Authorization', `Bearer ${rawToken}`); + + expect(response.status).toBe(200); + expect(response.body.tasks).toBeDefined(); + }); + + it('should authenticate POST /api/task with Bearer token', async () => { + const response = await request(app) + .post('/api/task') + .set('Authorization', `Bearer ${rawToken}`) + .send({ + name: 'Test Task via API', + status: 'pending', + }); + + expect(response.status).toBe(201); + expect(response.body.name).toBe('Test Task via API'); + }); + + it('should reject invalid Bearer token', async () => { + const response = await request(app) + .get('/api/tasks') + .set('Authorization', 'Bearer invalid_token_123'); + + expect(response.status).toBe(401); + }); + + it('should reject request without Bearer token', async () => { + const response = await request(app).get('/api/tasks'); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authentication required'); + }); + }); + + describe('Projects API with Bearer Token', () => { + it('should authenticate GET /api/projects with Bearer token', async () => { + const response = await request(app) + .get('/api/projects') + .set('Authorization', `Bearer ${rawToken}`); + + expect(response.status).toBe(200); + // Response can be either array or object with projects array + expect( + Array.isArray(response.body) || + Array.isArray(response.body.projects) + ).toBe(true); + }); + + it('should authenticate POST /api/project with Bearer token', async () => { + const response = await request(app) + .post('/api/project') + .set('Authorization', `Bearer ${rawToken}`) + .send({ + name: 'Test Project via API', + description: 'Created with API token', + }); + + expect(response.status).toBe(201); + expect(response.body.name).toBe('Test Project via API'); + }); + }); + + describe('Notes API with Bearer Token', () => { + it('should authenticate GET /api/notes with Bearer token', async () => { + const response = await request(app) + .get('/api/notes') + .set('Authorization', `Bearer ${rawToken}`); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + it('should authenticate POST /api/note with Bearer token', async () => { + const response = await request(app) + .post('/api/note') + .set('Authorization', `Bearer ${rawToken}`) + .send({ + title: 'Test Note via API', + content: 'Created with API token', + }); + + expect(response.status).toBe(201); + expect(response.body.title).toBe('Test Note via API'); + }); + }); + + describe('Inbox API with Bearer Token', () => { + it('should authenticate GET /api/inbox with Bearer token', async () => { + const response = await request(app) + .get('/api/inbox') + .set('Authorization', `Bearer ${rawToken}`); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + it('should authenticate POST /api/inbox with Bearer token', async () => { + const response = await request(app) + .post('/api/inbox') + .set('Authorization', `Bearer ${rawToken}`) + .send({ + content: 'Quick capture via API', + }); + + expect(response.status).toBe(201); + expect(response.body.content).toBe('Quick capture via API'); + }); + }); + + describe('Profile API with Bearer Token', () => { + it('should authenticate GET /api/profile with Bearer token', async () => { + const response = await request(app) + .get('/api/profile') + .set('Authorization', `Bearer ${rawToken}`); + + expect(response.status).toBe(200); + expect(response.body.uid).toBe(user.uid); + expect(response.body.email).toBe(user.email); + }); + + it('should authenticate PATCH /api/profile with Bearer token', async () => { + const response = await request(app) + .patch('/api/profile') + .set('Authorization', `Bearer ${rawToken}`) + .send({ + appearance: 'dark', + }); + + expect(response.status).toBe(200); + expect(response.body.appearance).toBe('dark'); + }); + }); + + it('should update last_used_at when using API token', async () => { + // Wait a moment to ensure timestamp difference + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Use the token + await request(app) + .get('/api/tasks') + .set('Authorization', `Bearer ${rawToken}`); + + // Check that last_used_at was updated + const tokensAfter = await agent.get('/api/profile/api-keys'); + const tokenAfter = tokensAfter.body.find( + (t) => t.name === 'Test Token' + ); + + // Verify last_used_at is set and is a recent timestamp + expect(tokenAfter.last_used_at).toBeDefined(); + const lastUsedDate = new Date(tokenAfter.last_used_at); + const now = new Date(); + expect(lastUsedDate.getTime()).toBeLessThanOrEqual(now.getTime()); + // Should be within the last minute + expect(now.getTime() - lastUsedDate.getTime()).toBeLessThan( + 60 * 1000 + ); + }); + }); + + describe('POST /api/profile/api-keys/:id/revoke - Revoke API Key', () => { + let revokeToken, revokeApiToken; + + beforeEach(async () => { + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Key to Revoke', + }); + if (response.status === 201 && response.body.apiKey) { + revokeApiToken = response.body.apiKey; + revokeToken = response.body.token; + } + }); + + it('should revoke an API key', async () => { + const response = await agent.post( + `/api/profile/api-keys/${revokeApiToken.id}/revoke` + ); + + expect(response.status).toBe(200); + expect(response.body.revoked_at).toBeDefined(); + expect(new Date(response.body.revoked_at)).toBeInstanceOf(Date); + }); + + it('should prevent using revoked token', async () => { + // Revoke the token + await agent.post( + `/api/profile/api-keys/${revokeApiToken.id}/revoke` + ); + + // Try to use revoked token + const response = await request(app) + .get('/api/tasks') + .set('Authorization', `Bearer ${revokeToken}`); + + expect(response.status).toBe(401); + expect(response.body.error).toMatch(/revoked|invalid/i); + }); + + it('should return 404 for non-existent API key', async () => { + const response = await agent.post( + '/api/profile/api-keys/99999/revoke' + ); + + expect(response.status).toBe(404); + expect(response.body.error).toBe('API key not found.'); + }); + + it('should not allow revoking other users API keys', async () => { + // Create another user with API key + const otherUser = await createTestUser({ + email: `other_${Date.now()}@example.com`, + }); + const otherAgent = request.agent(app); + await otherAgent.post('/api/login').send({ + email: otherUser.email, + password: 'password123', + }); + const otherKeyResponse = await otherAgent + .post('/api/profile/api-keys') + .send({ + name: 'Other User Key', + }); + + // Try to revoke other user's key + const response = await agent.post( + `/api/profile/api-keys/${otherKeyResponse.body.apiKey.id}/revoke` + ); + + expect(response.status).toBe(404); + }); + + it('should require authentication', async () => { + const response = await request(app).post( + `/api/profile/api-keys/${revokeApiToken.id}/revoke` + ); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authentication required'); + }); + }); + + describe('DELETE /api/profile/api-keys/:id - Delete API Key', () => { + let deleteToken, deleteApiToken; + + beforeEach(async () => { + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Key to Delete', + }); + if (response.status === 201 && response.body.apiKey) { + deleteApiToken = response.body.apiKey; + deleteToken = response.body.token; + } + }); + + it('should delete an API key', async () => { + const response = await agent.delete( + `/api/profile/api-keys/${deleteApiToken.id}` + ); + + expect(response.status).toBe(204); + + // Verify key is deleted + const listResponse = await agent.get('/api/profile/api-keys'); + const deletedKey = listResponse.body.find( + (k) => k.id === deleteApiToken.id + ); + expect(deletedKey).toBeUndefined(); + }); + + it('should prevent using deleted token', async () => { + // Delete the token + await agent.delete(`/api/profile/api-keys/${deleteApiToken.id}`); + + // Try to use deleted token + const response = await request(app) + .get('/api/tasks') + .set('Authorization', `Bearer ${deleteToken}`); + + expect(response.status).toBe(401); + }); + + it('should return 404 for non-existent API key', async () => { + const response = await agent.delete('/api/profile/api-keys/99999'); + + expect(response.status).toBe(404); + expect(response.body.error).toBe('API key not found.'); + }); + + it('should not allow deleting other users API keys', async () => { + // Create another user with API key + const otherUser = await createTestUser({ + email: `other_${Date.now()}@example.com`, + }); + const otherAgent = request.agent(app); + await otherAgent.post('/api/login').send({ + email: otherUser.email, + password: 'password123', + }); + const otherKeyResponse = await otherAgent + .post('/api/profile/api-keys') + .send({ + name: 'Other User Key', + }); + + // Try to delete other user's key + const response = await agent.delete( + `/api/profile/api-keys/${otherKeyResponse.body.apiKey.id}` + ); + + expect(response.status).toBe(404); + }); + + it('should require authentication', async () => { + const response = await request(app).delete( + `/api/profile/api-keys/${deleteApiToken.id}` + ); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authentication required'); + }); + }); + + describe('Expired Token Handling', () => { + let expiredToken; + + beforeEach(async () => { + // Create a token that expires in 1 second + const expiresAt = new Date(Date.now() + 1000); + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Expiring Soon', + expires_at: expiresAt.toISOString(), + }); + expiredToken = response.body.token; + }); + + it('should reject expired token', async () => { + // Wait for token to expire + await new Promise((resolve) => setTimeout(resolve, 1500)); + + const response = await request(app) + .get('/api/tasks') + .set('Authorization', `Bearer ${expiredToken}`); + + expect(response.status).toBe(401); + expect(response.body.error).toMatch(/expired|invalid/i); + }); + }); + + describe('Token Security', () => { + it('should not expose token hash in API responses', async () => { + const createResponse = await agent + .post('/api/profile/api-keys') + .send({ + name: 'Security Test', + }); + + expect(createResponse.body.apiKey).not.toHaveProperty('token_hash'); + + const listResponse = await agent.get('/api/profile/api-keys'); + listResponse.body.forEach((key) => { + expect(key).not.toHaveProperty('token_hash'); + }); + }); + + it('should only return full token once during creation', async () => { + const createResponse = await agent + .post('/api/profile/api-keys') + .send({ + name: 'One Time Token', + }); + + expect(createResponse.body.token).toBeDefined(); + expect(createResponse.body.token).toMatch(/^tt_[a-zA-Z0-9]{64}$/); + + // Token should not appear in list + const listResponse = await agent.get('/api/profile/api-keys'); + listResponse.body.forEach((key) => { + expect(key).not.toHaveProperty('token'); + }); + }); + + it('should use different token prefix than session cookies', async () => { + const response = await agent.post('/api/profile/api-keys').send({ + name: 'Prefix Test', + }); + + expect(response.body.token).toMatch(/^tt_/); + expect(response.body.token).not.toMatch(/^connect\.sid/); + }); + }); +}); diff --git a/backend/utils/migration-utils.js b/backend/utils/migration-utils.js index b57151b..c8547a8 100644 --- a/backend/utils/migration-utils.js +++ b/backend/utils/migration-utils.js @@ -22,7 +22,7 @@ async function safeAddColumns(queryInterface, tableName, columns) { async function safeCreateTable(queryInterface, tableName, tableDefinition) { try { const tableExists = await queryInterface - .showAllTables() + .listTables() .then((tables) => tables.includes(tableName)); if (!tableExists) { diff --git a/backend/utils/request-utils.js b/backend/utils/request-utils.js new file mode 100644 index 0000000..bbf4d22 --- /dev/null +++ b/backend/utils/request-utils.js @@ -0,0 +1,6 @@ +const getAuthenticatedUserId = (req) => + req.currentUser?.id || req.session?.userId; + +module.exports = { + getAuthenticatedUserId, +}; diff --git a/frontend/components/Profile/ProfileSettings.tsx b/frontend/components/Profile/ProfileSettings.tsx index 2324139..ae9c3a5 100644 --- a/frontend/components/Profile/ProfileSettings.tsx +++ b/frontend/components/Profile/ProfileSettings.tsx @@ -23,18 +23,28 @@ import { CheckIcon, SunIcon, MoonIcon, + KeyIcon, + TrashIcon, } from '@heroicons/react/24/outline'; import TelegramIcon from '../Icons/TelegramIcon'; import { useToast } from '../Shared/ToastContext'; import { dispatchTelegramStatusChange } from '../../contexts/TelegramStatusContext'; import LanguageDropdown from '../Shared/LanguageDropdown'; import FirstDayOfWeekDropdown from '../Shared/FirstDayOfWeekDropdown'; +import ConfirmDialog from '../Shared/ConfirmDialog'; import { getLocaleFirstDayOfWeek } from '../../utils/profileService'; import { getTimezonesByRegion, getRegionDisplayName, } from '../../utils/timezoneUtils'; import TimezoneDropdown from '../Shared/TimezoneDropdown'; +import type { ApiKeySummary } from '../../utils/apiKeysService'; +import { + fetchApiKeys, + createApiKey, + revokeApiKey, + deleteApiKey, +} from '../../utils/apiKeysService'; interface ProfileSettingsProps { currentUser: { uid: string; email: string }; @@ -142,11 +152,48 @@ const ProfileSettings: React.FC = ({ >('idle'); const [telegramBotInfo, setTelegramBotInfo] = useState(null); + const [apiKeys, setApiKeys] = useState([]); + const [apiKeysLoading, setApiKeysLoading] = useState(false); + const [apiKeysLoaded, setApiKeysLoaded] = useState(false); + const [newApiKeyName, setNewApiKeyName] = useState(''); + const [newApiKeyExpiration, setNewApiKeyExpiration] = useState(''); + const [isCreatingApiKey, setIsCreatingApiKey] = useState(false); + const [generatedApiToken, setGeneratedApiToken] = useState( + null + ); + const [revokeInFlightId, setRevokeInFlightId] = useState( + null + ); + const [deleteInFlightId, setDeleteInFlightId] = useState( + null + ); + const [apiKeyToDelete, setApiKeyToDelete] = useState( + null + ); const forceUpdate = useCallback(() => { setUpdateKey((prevKey) => prevKey + 1); }, []); + const loadApiKeys = useCallback(async () => { + setApiKeysLoading(true); + try { + const keys = await fetchApiKeys(); + setApiKeys(keys); + } catch (error) { + showErrorToast((error as Error).message); + } finally { + setApiKeysLoading(false); + setApiKeysLoaded(true); + } + }, [showErrorToast]); + + useEffect(() => { + if (activeTab === 'apiKeys' && !apiKeysLoaded) { + loadApiKeys(); + } + }, [activeTab, apiKeysLoaded, loadApiKeys]); + // Password validation const validatePasswordForm = (): { valid: boolean; @@ -257,6 +304,136 @@ const ProfileSettings: React.FC = ({ } }; + const handleCreateApiKey = async () => { + if (!newApiKeyName.trim()) { + showErrorToast( + t('profile.apiKeys.nameRequired', 'API key name is required.') + ); + return; + } + + setIsCreatingApiKey(true); + try { + const payload: { name: string; expires_at?: string } = { + name: newApiKeyName.trim(), + }; + + if (newApiKeyExpiration) { + const parsed = new Date(`${newApiKeyExpiration}T23:59:59.999Z`); + if (Number.isNaN(parsed.getTime())) { + throw new Error( + t( + 'profile.apiKeys.invalidExpiration', + 'Expiration date is invalid.' + ) + ); + } + payload.expires_at = parsed.toISOString(); + } + + const response = await createApiKey(payload); + setGeneratedApiToken(response.token); + setApiKeys((prev) => [response.apiKey, ...prev]); + setNewApiKeyName(''); + setNewApiKeyExpiration(''); + showSuccessToast( + t('profile.apiKeys.created', 'API key created successfully.') + ); + } catch (error) { + showErrorToast((error as Error).message); + } finally { + setIsCreatingApiKey(false); + } + }; + + const handleRevokeApiKey = async (apiKeyId: number) => { + setRevokeInFlightId(apiKeyId); + try { + const updatedKey = await revokeApiKey(apiKeyId); + setApiKeys((prev) => + prev.map((key) => (key.id === apiKeyId ? updatedKey : key)) + ); + showSuccessToast( + t('profile.apiKeys.revokedMessage', 'API key revoked.') + ); + } catch (error) { + showErrorToast((error as Error).message); + } finally { + setRevokeInFlightId(null); + } + }; + + const confirmDeleteApiKey = async () => { + if (!apiKeyToDelete) return; + const apiKeyId = apiKeyToDelete.id; + setDeleteInFlightId(apiKeyId); + try { + await deleteApiKey(apiKeyId); + setApiKeys((prev) => prev.filter((key) => key.id !== apiKeyId)); + showSuccessToast(t('profile.apiKeys.deleted', 'API key deleted.')); + setApiKeyToDelete(null); + } catch (error) { + showErrorToast((error as Error).message); + } finally { + setDeleteInFlightId(null); + } + }; + + const handleCopyGeneratedToken = async () => { + if (!generatedApiToken) return; + + try { + await navigator.clipboard.writeText(generatedApiToken); + showSuccessToast( + t('profile.apiKeys.copied', 'API key copied to clipboard.') + ); + } catch { + showErrorToast( + t( + 'profile.apiKeys.copyFailed', + 'Unable to copy API key to clipboard.' + ) + ); + } + }; + + const closeDeleteDialog = () => { + if (deleteInFlightId) return; + setApiKeyToDelete(null); + }; + + const getApiKeyStatus = (apiKey: ApiKeySummary) => { + if (apiKey.revoked_at) { + return { + label: t('profile.apiKeys.status.revoked', 'Revoked'), + className: 'text-red-600 dark:text-red-400', + }; + } + + if (apiKey.expires_at && new Date(apiKey.expires_at) < new Date()) { + return { + label: t('profile.apiKeys.status.expired', 'Expired'), + className: 'text-yellow-600 dark:text-yellow-400', + }; + } + + return { + label: t('profile.apiKeys.status.active', 'Active'), + className: 'text-green-600 dark:text-green-400', + }; + }; + + const formatDateTime = (value: string | null) => { + if (!value) { + return t('profile.apiKeys.never', 'Never'); + } + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + return value; + } + return parsed.toLocaleString(); + }; + useEffect(() => { const fetchProfile = async () => { try { @@ -781,6 +958,11 @@ const ProfileSettings: React.FC = ({ name: t('profile.tabs.security', 'Security'), icon: 'shield', }, + { + id: 'apiKeys', + name: t('profile.tabs.apiKeys', 'API Keys'), + icon: 'key', + }, { id: 'productivity', name: t('profile.tabs.productivity', 'Productivity'), @@ -810,1113 +992,1451 @@ const ProfileSettings: React.FC = ({ return ; case 'sparkles': return ; + case 'key': + return ; default: return null; } }; return ( -
-

- {t('profile.title')} -

+ <> +
+

+ {t('profile.title')} +

- {/* Navigation Tabs */} -
-
- -
-
- -
- {/* General Tab */} - {activeTab === 'general' && ( -
-

- - {t( - 'profile.accountSettings', - 'Account & Preferences' - )} -

- -
-
- - -
- -
- - -
- -
- -
- - -
-
- -
- - { - // Auto-set first day of week based on language/locale - const localeFirstDay = - getLocaleFirstDayOfWeek( - languageCode - ); - setFormData((prev) => ({ - ...prev, - language: languageCode, - first_day_of_week: localeFirstDay, - })); - }} - /> -
- -
- - - setFormData((prev) => ({ - ...prev, - timezone, - })) - } - timezonesByRegion={timezonesByRegion} - getRegionDisplayName={getRegionDisplayName} - /> -
- -
- - { - setFormData((prev) => ({ - ...prev, - first_day_of_week: value, - })); - }} - /> -
-
-
- )} - - {/* Security Tab */} - {activeTab === 'security' && ( -
-

- - {t('profile.security', 'Security Settings')} -

- - {/* Password Change Section */} -
-

- - {t('profile.changePassword', 'Change Password')} -

- -
-

- - {t( - 'profile.passwordChangeOptional', - 'Leave password fields empty to update other settings without changing your password.' - )} -

-
- -
-
- -
- - -
-
- -
- -
- - -
-
- -
- -
- - -
-
- -
- {t( - 'profile.passwordChangeNote', - 'Password changes will be saved when you click "Save Changes" at the bottom of the form.' - )} -
-
-
-
- )} - - {/* Productivity Tab */} - {activeTab === 'productivity' && ( -
-

- - {t( - 'profile.productivityFeatures', - 'Productivity Features' - )} -

- -
- {/* Pomodoro Timer */} -
-
- -

- {t( - 'profile.pomodoroDescription', - 'Enable the Pomodoro timer in the navigation bar for focused work sessions.' - )} -

-
-
+
+ +
+
+ + + {/* General Tab */} + {activeTab === 'general' && ( +
+

+ + {t( + 'profile.accountSettings', + 'Account & Preferences' + )} +

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ +
+ + { + // Auto-set first day of week based on language/locale + const localeFirstDay = + getLocaleFirstDayOfWeek( + languageCode + ); + setFormData((prev) => ({ + ...prev, + language: languageCode, + first_day_of_week: + localeFirstDay, + })); + }} + /> +
+ +
+ + + setFormData((prev) => ({ + ...prev, + timezone, + })) + } + timezonesByRegion={timezonesByRegion} + getRegionDisplayName={ + getRegionDisplayName + } + /> +
+ +
+ + { + setFormData((prev) => ({ + ...prev, + first_day_of_week: value, + })); + }} + />
-
- )} + )} - {/* Telegram Tab */} - {activeTab === 'telegram' && ( -
-

- - {t( - 'profile.telegramIntegration', - 'Telegram Integration' - )} -

+ {/* Security Tab */} + {activeTab === 'security' && ( +
+

+ + {t('profile.security', 'Security Settings')} +

- {/* Bot Setup Subsection */} -
-

- - {t('profile.botSetup', 'Bot Setup')} -

+ {/* Password Change Section */} +
+

+ + {t( + 'profile.changePassword', + 'Change Password' + )} +

-
-
- -

+

+

+ {t( - 'profile.telegramDescription', - 'Connect your tududi account to a Telegram bot to add items to your inbox via Telegram messages.' + 'profile.passwordChangeOptional', + 'Leave password fields empty to update other settings without changing your password.' )}

-
- - -

- {t( - 'profile.telegramTokenDescription', - 'Create a bot with @BotFather on Telegram and paste the token here.' - )} -

-
- -
- - -
-

+

+
+
- {profile?.telegram_chat_id && ( -
-

+

+ +
+ + +
+
+ +
+ +
+ + +
+
+ +
+ {t( + 'profile.passwordChangeNote', + 'Password changes will be saved when you click "Save Changes" at the bottom of the form.' + )} +
+
+
+
+ )} + + {/* API Keys Tab */} + {activeTab === 'apiKeys' && ( +
+

+ + {t('profile.apiKeys.title', 'API Keys')} +

+ +

+ {t( + 'profile.apiKeys.description', + 'Generate personal access tokens for integrations or CLI usage. You can revoke or delete keys at any time.' + )} +

+ +
+
+ + + setNewApiKeyName(event.target.value) + } + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault(); + handleCreateApiKey(); + } + }} + className="block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + placeholder={t( + 'profile.apiKeys.namePlaceholder', + 'e.g. Personal laptop' + )} + /> +
+
+ + + setNewApiKeyExpiration( + event.target.value + ) + } + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault(); + handleCreateApiKey(); + } + }} + className="block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + /> +
+
+ +
+
+ + {generatedApiToken && ( +
+

+ {t( + 'profile.apiKeys.copyNotice', + 'Copy this token now. It will not be shown again.' + )} +

+
+ + {generatedApiToken} + + +
+
+ )} + +
+ {apiKeysLoading && ( +

+ {t( + 'profile.apiKeys.loading', + 'Loading API keys...' + )} +

+ )} + + {!apiKeysLoading && apiKeys.length === 0 && ( +

+ {t( + 'profile.apiKeys.empty', + 'No API keys yet. Generate one to begin.' + )} +

+ )} + + {!apiKeysLoading && apiKeys.length > 0 && ( +
+ + + + + + + + + + + + + {apiKeys.map((key) => { + const status = + getApiKeyStatus(key); + return ( + + + + + + + + + ); + })} + +
+ {t( + 'profile.apiKeys.table.name', + 'Name' + )} + + {t( + 'profile.apiKeys.table.prefix', + 'Prefix' + )} + + {t( + 'profile.apiKeys.table.status', + 'Status' + )} + + {t( + 'profile.apiKeys.table.lastUsed', + 'Last used' + )} + + {t( + 'profile.apiKeys.table.expires', + 'Expires' + )} + + {t( + 'profile.apiKeys.table.actions', + 'Actions' + )} +
+
+ {key.name} +
+
+ {t( + 'profile.apiKeys.createdAt', + 'Created {{date}}', + { + date: formatDateTime( + key.created_at + ), + } + )} +
+
+ { + key.token_prefix + } + ... + + + { + status.label + } + + + {formatDateTime( + key.last_used_at + )} + + {key.expires_at + ? formatDateTime( + key.expires_at + ) + : t( + 'profile.apiKeys.noExpiry', + 'None' + )} + +
+ + +
+
+
+ )} +
+
+ )} + + {/* Productivity Tab */} + {activeTab === 'productivity' && ( +
+

+ + {t( + 'profile.productivityFeatures', + 'Productivity Features' + )} +

+ +
+ {/* Pomodoro Timer */} +
+
+ +

+ {t( + 'profile.pomodoroDescription', + 'Enable the Pomodoro timer in the navigation bar for focused work sessions.' )}

- )} +
{ + setFormData((prev) => ({ + ...prev, + pomodoro_enabled: + !prev.pomodoro_enabled, + })); + }} + > + +
+
+
+
+ )} - {(telegramBotInfo || - profile?.telegram_bot_token) && ( -
-

+ {/* Telegram Tab */} + {activeTab === 'telegram' && ( +

+

+ + {t( + 'profile.telegramIntegration', + 'Telegram Integration' + )} +

+ + {/* Bot Setup Subsection */} +
+

+ + {t('profile.botSetup', 'Bot Setup')} +

+ +
+
+ +

{t( - 'profile.botConfigured', - 'Bot configured successfully!' + 'profile.telegramDescription', + 'Connect your tududi account to a Telegram bot to add items to your inbox via Telegram messages.' )}

-
- {telegramBotInfo?.first_name && ( -

- - Bot Name:{' '} - - {telegramBotInfo.first_name} -

+
+ +
+
+ +
+ + +
+

+ {t( + 'profile.telegramAllowedUsersDescription', + 'Control who can send messages to your bot. Leave empty to allow all users.' + )} +

+
+

+ {t( + 'profile.examples', + 'Examples:' + )} +

+
    +
  • + + @alice, @bob + + {' - '} {t( - 'profile.botUsername', - 'Bot Username:' - )}{' '} - - @{telegramBotInfo.username} -

    - )} -
    -

    - {t( - 'profile.pollingStatus', - 'Polling Status:' - )}{' '} -

    -
    -
    - - {isPolling - ? t( - 'profile.pollingActive' - ) - : t( - 'profile.pollingInactive' - )} - -
    -

    - {t( - 'profile.pollingNote', - 'Polling periodically checks for new messages from Telegram and adds them to your inbox.' - )} -

    -
    - {isPolling ? ( - - ) : ( - - )} - {telegramBotInfo?.chat_url && ( - - {t( - 'profile.openTelegram', - 'Open in Telegram' - )} - - )} -
    + 'profile.exampleUsernames', + 'Allow specific usernames' + )} +
  • +
  • + + 123456789, 987654321 + + {' - '} + {t( + 'profile.exampleUserIds', + 'Allow specific user IDs' + )} +
  • +
  • + + @alice, 123456789 + + {' - '} + {t( + 'profile.exampleMixed', + 'Mix usernames and user IDs' + )} +
  • +
- )} - - - {/* Status indicator */} - {telegramSetupStatus === 'success' && ( -
- - - - - Bot configured successfully! - -
- )} - - {telegramSetupStatus === 'error' && ( -
- - - - - Setup failed. Please check your - token. - -
- )} -
-
- - {/* Task Summary Notifications Subsection */} -
-

- - {t( - 'profile.taskSummaryNotifications', - 'Task Summary Notifications' - )} -

- -
- -

- {t( - 'profile.taskSummaryDescription', - 'Receive regular summaries of your tasks via Telegram. This feature requires your Telegram integration to be set up.' + {profile?.telegram_chat_id && ( +

+

+ {t( + 'profile.telegramConnected', + 'Your Telegram account is connected! Send messages to your bot to add items to your tududi inbox.' + )} +

+
)} -

-
-
- -
{ - setFormData((prev) => ({ - ...prev, - task_summary_enabled: - !prev.task_summary_enabled, - })); - }} - > - -
-
-
- -
- {[ - '1h', - '2h', - '4h', - '8h', - '12h', - 'daily', - 'weekly', - ].map((frequency) => ( - - ))} -
-

- {t( - 'profile.frequencyHelp', - 'Choose how often you want to receive task summaries.' - )} -

-
- -
- + + {/* Status indicator */} + {telegramSetupStatus === 'success' && ( +
+ + + + + Bot configured successfully! + +
)} - - {(!profile?.telegram_bot_token || - !profile?.telegram_chat_id) && ( -

+ + {telegramSetupStatus === 'error' && ( +

+ + + + + Setup failed. Please check your + token. + +
+ )} +
+
+ + {/* Task Summary Notifications Subsection */} +
+

+ + {t( + 'profile.taskSummaryNotifications', + 'Task Summary Notifications' + )} +

+ +
+ +

{t( - 'profile.telegramRequiredForSummaries', - 'Telegram integration must be set up to use task summaries.' + 'profile.taskSummaryDescription', + 'Receive regular summaries of your tasks via Telegram. This feature requires your Telegram integration to be set up.' )}

- )} +
+ +
+ +
{ + setFormData((prev) => ({ + ...prev, + task_summary_enabled: + !prev.task_summary_enabled, + })); + }} + > + +
+
+ +
+ +
+ {[ + '1h', + '2h', + '4h', + '8h', + '12h', + 'daily', + 'weekly', + ].map((frequency) => ( + + ))} +
+

+ {t( + 'profile.frequencyHelp', + 'Choose how often you want to receive task summaries.' + )} +

+
+ +
+ + {(!profile?.telegram_bot_token || + !profile?.telegram_chat_id) && ( +

+ {t( + 'profile.telegramRequiredForSummaries', + 'Telegram integration must be set up to use task summaries.' + )} +

+ )} +
-
- )} + )} - {/* AI Features Tab */} - {activeTab === 'ai' && ( -
-

- - {t( - 'profile.aiProductivityFeatures', - 'AI & Productivity Features' - )} -

- - {/* Task Intelligence Subsection */} -
-

- + {/* AI Features Tab */} + {activeTab === 'ai' && ( +
+

+ {t( - 'profile.taskIntelligence', - 'Task Intelligence' + 'profile.aiProductivityFeatures', + 'AI & Productivity Features' )} -

+

-
- -

+ {/* Task Intelligence Subsection */} +

+

+ {t( - 'profile.taskIntelligenceDescription', - 'Show popup alerts while typing task names that suggest improvements like "Make it more descriptive!", "Be more specific!", or "Add an action verb!". Disable this if you prefer typing in your own shorthand without suggestions.' + 'profile.taskIntelligence', + 'Task Intelligence' )} -

-

+ -
- -
{ - setFormData((prev) => ({ - ...prev, - task_intelligence_enabled: - !prev.task_intelligence_enabled, - })); - }} - > - + +

+ {t( + 'profile.taskIntelligenceDescription', + 'Show popup alerts while typing task names that suggest improvements like "Make it more descriptive!", "Be more specific!", or "Add an action verb!". Disable this if you prefer typing in your own shorthand without suggestions.' + )} +

+
+ +
+ +
+ onClick={() => { + setFormData((prev) => ({ + ...prev, + task_intelligence_enabled: + !prev.task_intelligence_enabled, + })); + }} + > + +
-
- {/* Auto-Suggest Next Actions Subsection */} -
-

- - {t( - 'profile.autoSuggestNextActions', - 'Auto-Suggest Next Actions' - )} -

- -
- -

+ {/* Auto-Suggest Next Actions Subsection */} +

+

+ {t( - 'profile.autoSuggestNextActionsDescription', - 'When creating a project, automatically prompt for the very next physical action to take.' + 'profile.autoSuggestNextActions', + 'Auto-Suggest Next Actions' )} -

-

+ -
- -
{ - setFormData((prev) => ({ - ...prev, - auto_suggest_next_actions_enabled: - !prev.auto_suggest_next_actions_enabled, - })); - }} - > - + +

+ {t( + 'profile.autoSuggestNextActionsDescription', + 'When creating a project, automatically prompt for the very next physical action to take.' + )} +

+
+ +
+ +
+ onClick={() => { + setFormData((prev) => ({ + ...prev, + auto_suggest_next_actions_enabled: + !prev.auto_suggest_next_actions_enabled, + })); + }} + > + +
-
- {/* Productivity Assistant Subsection */} -
-

- - {t( - 'profile.productivityAssistant', - 'Productivity Assistant' - )} -

- -
- -

+ {/* Productivity Assistant Subsection */} +

+

+ {t( - 'profile.productivityAssistantDescription', - 'Show productivity insights that help identify stalled projects, vague tasks, and workflow improvements on your Today page.' + 'profile.productivityAssistant', + 'Productivity Assistant' )} -

-

+ -
- -
{ - setFormData((prev) => ({ - ...prev, - productivity_assistant_enabled: - !prev.productivity_assistant_enabled, - })); - }} - > - + +

+ {t( + 'profile.productivityAssistantDescription', + 'Show productivity insights that help identify stalled projects, vague tasks, and workflow improvements on your Today page.' + )} +

+
+ +
+ +
+ onClick={() => { + setFormData((prev) => ({ + ...prev, + productivity_assistant_enabled: + !prev.productivity_assistant_enabled, + })); + }} + > + +
-
- {/* Next Task Suggestion Subsection */} -
-

- - {t( - 'profile.nextTaskSuggestion', - 'Next Task Suggestion' - )} -

- -
- -

+ {/* Next Task Suggestion Subsection */} +

+

+ {t( - 'profile.nextTaskSuggestionDescription', - 'Automatically suggest the next best task to work on when you have nothing in progress, prioritizing due today tasks, then suggested tasks, then next actions.' + 'profile.nextTaskSuggestion', + 'Next Task Suggestion' )} -

-

+ -
- -
{ - setFormData((prev) => ({ - ...prev, - next_task_suggestion_enabled: - !prev.next_task_suggestion_enabled, - })); - }} - > - + +

+ {t( + 'profile.nextTaskSuggestionDescription', + 'Automatically suggest the next best task to work on when you have nothing in progress, prioritizing due today tasks, then suggested tasks, then next actions.' + )} +

+
+ +
+ +
+ onClick={() => { + setFormData((prev) => ({ + ...prev, + next_task_suggestion_enabled: + !prev.next_task_suggestion_enabled, + })); + }} + > + +
-
- )} + )} - {/* Save Button */} -
- -
- -
+ {/* Save Button */} +
+ +
+ +
+ {apiKeyToDelete && ( + + )} + ); }; diff --git a/frontend/components/Shared/MarkdownRenderer.tsx b/frontend/components/Shared/MarkdownRenderer.tsx index 1cef9fb..b01cdf0 100644 --- a/frontend/components/Shared/MarkdownRenderer.tsx +++ b/frontend/components/Shared/MarkdownRenderer.tsx @@ -328,10 +328,12 @@ const MarkdownRenderer: React.FC = ({ [Table content hidden in preview] ) : ( - +
+
+ ), thead: ({ ...props }) => summaryMode ? null : ( @@ -343,14 +345,14 @@ const MarkdownRenderer: React.FC = ({ th: ({ ...props }) => summaryMode ? null : (
), td: ({ ...props }) => summaryMode ? null : ( ), diff --git a/frontend/styles/markdown.css b/frontend/styles/markdown.css index 9b82d54..35aa520 100644 --- a/frontend/styles/markdown.css +++ b/frontend/styles/markdown.css @@ -163,6 +163,7 @@ /* Custom markdown styles */ .markdown-content { line-height: 1.6; + overflow-x: auto; } .markdown-content p:last-child { @@ -200,8 +201,75 @@ } .markdown-content table { - border-radius: 0.375rem; + border-collapse: collapse; + border-spacing: 0; + width: 100%; + max-width: 100%; +} + +.markdown-content .markdown-table-wrapper { + border-radius: 0.5rem; + border: 1px solid rgba(15, 23, 42, 0.1); overflow: hidden; + overflow-x: auto; + box-shadow: 0 4px 10px -8px rgba(15, 23, 42, 0.2); + background: #ffffff; + margin-bottom: 1rem; +} + +.dark .markdown-content .markdown-table-wrapper { + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 6px 16px -10px rgba(0, 0, 0, 0.7); + background: rgba(17, 24, 39, 0.9); +} + +.markdown-content .markdown-table-wrapper .markdown-table { + border-radius: 0; + background: transparent; +} + +.markdown-content .markdown-table thead th { + background: rgba(243, 244, 246, 0.95); + color: #111827; + border-bottom: 1px solid rgba(15, 23, 42, 0.08); +} + +.dark .markdown-content .markdown-table thead th { + background: rgba(31, 41, 55, 0.85); + color: #f9fafb; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.markdown-content .markdown-table tbody tr:nth-child(odd) td { + background-color: rgba(249, 250, 251, 0.4); +} + +.dark .markdown-content .markdown-table tbody tr:nth-child(odd) td { + background-color: rgba(15, 23, 42, 0.5); +} + +.markdown-content .markdown-table th, +.markdown-content .markdown-table td { + border-bottom: 1px solid rgba(15, 23, 42, 0.08); +} + +.markdown-content .markdown-table tr:last-child td { + border-bottom: none; +} + +.markdown-content .markdown-table td + td, +.markdown-content .markdown-table th + th { + border-left: 1px solid rgba(15, 23, 42, 0.06); +} + +.dark .markdown-content .markdown-table th, +.dark .markdown-content .markdown-table td { + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.dark .markdown-content .markdown-table td + td, +.dark .markdown-content .markdown-table th + th { + border-left: 1px solid rgba(255, 255, 255, 0.06); } .markdown-content blockquote { @@ -277,19 +345,6 @@ color: black; } -/* Better table responsiveness */ -.markdown-content table { - max-width: 100%; - overflow-x: auto; - display: block; - white-space: nowrap; -} - -.markdown-content tbody { - display: table; - width: 100%; -} - @media (max-width: 768px) { .markdown-content table { font-size: 0.875rem; @@ -299,4 +354,4 @@ .markdown-content td { padding: 0.5rem; } -} \ No newline at end of file +} diff --git a/frontend/utils/apiKeysService.ts b/frontend/utils/apiKeysService.ts new file mode 100644 index 0000000..3b77599 --- /dev/null +++ b/frontend/utils/apiKeysService.ts @@ -0,0 +1,64 @@ +export interface ApiKeySummary { + id: number; + name: string; + token_prefix: string; + created_at: string; + updated_at: string; + last_used_at: string | null; + expires_at: string | null; + revoked_at: string | null; +} + +export interface CreateApiKeyResponse { + token: string; + apiKey: ApiKeySummary; +} + +async function handleResponse(response: Response): Promise { + if (!response.ok) { + const errorBody = await response.json().catch(() => null); + const message = errorBody?.error || 'Request failed'; + throw new Error(message); + } + return (await response.json()) as T; +} + +export async function fetchApiKeys(): Promise { + const response = await fetch('/api/profile/api-keys', { + credentials: 'include', + }); + return handleResponse(response); +} + +export async function createApiKey(payload: { + name: string; + expires_at?: string | null; +}): Promise { + const response = await fetch('/api/profile/api-keys', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify(payload), + }); + return handleResponse(response); +} + +export async function revokeApiKey(id: number): Promise { + const response = await fetch(`/api/profile/api-keys/${id}/revoke`, { + method: 'POST', + credentials: 'include', + }); + return handleResponse(response); +} + +export async function deleteApiKey(id: number): Promise { + const response = await fetch(`/api/profile/api-keys/${id}`, { + method: 'DELETE', + credentials: 'include', + }); + if (!response.ok) { + const errorBody = await response.json().catch(() => null); + const message = errorBody?.error || 'Failed to delete API key'; + throw new Error(message); + } +} diff --git a/index.html b/index.html index 6ede090..14c73bf 100644 --- a/index.html +++ b/index.html @@ -1496,6 +1496,13 @@

Telegram Integration

Create tasks directly through Telegram messages, receive daily digests of your tasks, and quick capture ideas on the go. Never miss important deadlines with automated notifications.

+
+
+ +
+

API Access & Swagger

+

Versioned Swagger docs at /api/v1 and personal access tokens let you build automations or integrations around tududi’s data.

+
diff --git a/package-lock.json b/package-lock.json index a90a741..f839de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tududi", - "version": "v0.86-beta.1", + "version": "v0.86-beta.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tududi", - "version": "v0.86-beta.1", + "version": "v0.86-beta.4", "license": "ISC", "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -24,6 +24,7 @@ "dotenv": "~16.5.0", "eslint-plugin-react": "^7.37.5", "express": "^4.21.2", + "express-rate-limit": "^8.2.1", "express-session": "~1.18.1", "helmet": "~8.1.0", "highlight.js": "^11.11.1", @@ -51,6 +52,8 @@ "sequelize": "~6.37.7", "slugify": "^1.6.6", "sqlite3": "~5.1.7", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "swr": "^2.2.5", "tagify": "^0.1.1", "typescript-eslint": "^8.36.0", @@ -106,9 +109,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", - "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "dev": true, "license": "MIT" }, @@ -125,17 +128,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" } }, "node_modules/@babel/code-frame": { @@ -153,30 +187,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -192,13 +226,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -236,18 +270,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "engines": { @@ -258,14 +292,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -302,14 +336,14 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -329,14 +363,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -427,9 +461,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -445,40 +479,40 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -488,14 +522,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -555,14 +589,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -924,9 +958,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", - "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", "dev": true, "license": "MIT", "dependencies": { @@ -957,13 +991,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -974,9 +1008,9 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", - "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "dev": true, "license": "MIT", "dependencies": { @@ -985,7 +1019,7 @@ "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1012,14 +1046,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1112,9 +1146,9 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", "dev": true, "license": "MIT", "dependencies": { @@ -1211,9 +1245,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", "dev": true, "license": "MIT", "dependencies": { @@ -1277,16 +1311,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1378,9 +1412,9 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", - "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", "dev": true, "license": "MIT", "dependencies": { @@ -1388,7 +1422,7 @@ "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1431,9 +1465,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1584,9 +1618,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz", - "integrity": "sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", "dev": true, "license": "MIT", "dependencies": { @@ -1714,14 +1748,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", - "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" @@ -1801,21 +1835,21 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", - "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.0", + "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", @@ -1824,42 +1858,42 @@ "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.0", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.0", + "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1901,15 +1935,15 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", - "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" @@ -1922,9 +1956,9 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", "dependencies": { @@ -1932,7 +1966,7 @@ "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1942,9 +1976,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1965,17 +1999,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -1983,13 +2017,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2066,9 +2100,9 @@ } }, "node_modules/@emotion/is-prop-valid": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", - "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", "peer": true, "dependencies": { @@ -2097,9 +2131,9 @@ "peer": true }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2115,9 +2149,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2146,22 +2180,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -2177,12 +2195,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -2297,9 +2309,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -2310,9 +2322,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -2348,9 +2360,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -2422,13 +2434,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2457,23 +2462,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/core": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", @@ -2522,21 +2510,17 @@ } } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@jest/core/node_modules/pretty-format": { @@ -2554,19 +2538,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/core/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -2695,23 +2666,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", @@ -2730,9 +2684,9 @@ } }, "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -2829,23 +2783,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -2864,33 +2801,26 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2901,9 +2831,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", - "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { @@ -2912,21 +2842,27 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", @@ -2944,17 +2880,76 @@ "tslib": "2" } }, + "node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", - "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/base64": "^1.1.1", - "@jsonjoy.com/util": "^1.1.2", + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", - "thingies": "^1.20.0" + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" }, "engines": { "node": ">=10.0" @@ -2968,11 +2963,15 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", - "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, "engines": { "node": ">=10.0" }, @@ -3051,9 +3050,9 @@ } }, "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "optional": true, "bin": { @@ -3116,9 +3115,9 @@ "license": "MIT" }, "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3207,6 +3206,13 @@ "node": ">=14.0.0" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3235,15 +3241,15 @@ } }, "node_modules/@swc/core": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.3.tgz", - "integrity": "sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.0.tgz", + "integrity": "sha512-8SnJV+JV0rYbfSiEiUvYOmf62E7QwsEG+aZueqSlKoxFt0pw333+bgZSQXGUV6etXU88nxur0afVMaINujBMSw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.23" + "@swc/types": "^0.1.25" }, "engines": { "node": ">=10" @@ -3253,16 +3259,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.13.3", - "@swc/core-darwin-x64": "1.13.3", - "@swc/core-linux-arm-gnueabihf": "1.13.3", - "@swc/core-linux-arm64-gnu": "1.13.3", - "@swc/core-linux-arm64-musl": "1.13.3", - "@swc/core-linux-x64-gnu": "1.13.3", - "@swc/core-linux-x64-musl": "1.13.3", - "@swc/core-win32-arm64-msvc": "1.13.3", - "@swc/core-win32-ia32-msvc": "1.13.3", - "@swc/core-win32-x64-msvc": "1.13.3" + "@swc/core-darwin-arm64": "1.15.0", + "@swc/core-darwin-x64": "1.15.0", + "@swc/core-linux-arm-gnueabihf": "1.15.0", + "@swc/core-linux-arm64-gnu": "1.15.0", + "@swc/core-linux-arm64-musl": "1.15.0", + "@swc/core-linux-x64-gnu": "1.15.0", + "@swc/core-linux-x64-musl": "1.15.0", + "@swc/core-win32-arm64-msvc": "1.15.0", + "@swc/core-win32-ia32-msvc": "1.15.0", + "@swc/core-win32-x64-msvc": "1.15.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -3274,9 +3280,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.3.tgz", - "integrity": "sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.0.tgz", + "integrity": "sha512-TBKWkbnShnEjlIbO4/gfsrIgAqHBVqgPWLbWmPdZ80bF393yJcLgkrb7bZEnJs6FCbSSuGwZv2rx1jDR2zo6YA==", "cpu": [ "arm64" ], @@ -3291,9 +3297,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.3.tgz", - "integrity": "sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.0.tgz", + "integrity": "sha512-f5JKL1v1H56CIZc1pVn4RGPOfnWqPwmuHdpf4wesvXunF1Bx85YgcspW5YxwqG5J9g3nPU610UFuExJXVUzOiQ==", "cpu": [ "x64" ], @@ -3308,9 +3314,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.3.tgz", - "integrity": "sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.0.tgz", + "integrity": "sha512-duK6nG+WyuunnfsfiTUQdzC9Fk8cyDLqT9zyXvY2i2YgDu5+BH5W6wM5O4mDNCU5MocyB/SuF5YDF7XySnowiQ==", "cpu": [ "arm" ], @@ -3325,9 +3331,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.3.tgz", - "integrity": "sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.0.tgz", + "integrity": "sha512-ITe9iDtTRXM98B91rvyPP6qDVbhUBnmA/j4UxrHlMQ0RlwpqTjfZYZkD0uclOxSZ6qIrOj/X5CaoJlDUuQ0+Cw==", "cpu": [ "arm64" ], @@ -3342,9 +3348,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.3.tgz", - "integrity": "sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.0.tgz", + "integrity": "sha512-Q5ldc2bzriuzYEoAuqJ9Vr3FyZhakk5hiwDbniZ8tlEXpbjBhbOleGf9/gkhLaouDnkNUEazFW9mtqwUTRdh7Q==", "cpu": [ "arm64" ], @@ -3359,9 +3365,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.3.tgz", - "integrity": "sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.0.tgz", + "integrity": "sha512-pY4is+jEpOxlYCSnI+7N8Oxbap9TmTz5YT84tUvRTlOlTBwFAUlWFCX0FRwWJlsfP0TxbqhIe8dNNzlsEmJbXQ==", "cpu": [ "x64" ], @@ -3376,9 +3382,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.3.tgz", - "integrity": "sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.0.tgz", + "integrity": "sha512-zYEt5eT8y8RUpoe7t5pjpoOdGu+/gSTExj8PV86efhj6ugB3bPlj3Y85ogdW3WMVXr4NvwqvzdaYGCZfXzSyVg==", "cpu": [ "x64" ], @@ -3393,9 +3399,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.3.tgz", - "integrity": "sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.0.tgz", + "integrity": "sha512-zC1rmOgFH5v2BCbByOazEqs0aRNpTdLRchDExfcCfgKgeaD+IdpUOqp7i3VG1YzkcnbuZjMlXfM0ugpt+CddoA==", "cpu": [ "arm64" ], @@ -3410,9 +3416,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.3.tgz", - "integrity": "sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.0.tgz", + "integrity": "sha512-7t9U9KwMwQblkdJIH+zX1V4q1o3o41i0HNO+VlnAHT5o+5qHJ963PHKJ/pX3P2UlZnBCY465orJuflAN4rAP9A==", "cpu": [ "ia32" ], @@ -3427,9 +3433,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.3.tgz", - "integrity": "sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.0.tgz", + "integrity": "sha512-VE0Zod5vcs8iMLT64m5QS1DlTMXJFI/qSgtMDRx8rtZrnjt6/9NW8XUaiPJuRu8GluEO1hmHoyf1qlbY19gGSQ==", "cpu": [ "x64" ], @@ -3451,9 +3457,9 @@ "license": "Apache-2.0" }, "node_modules/@swc/types": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz", - "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3461,6 +3467,73 @@ } }, "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", @@ -3480,7 +3553,7 @@ "node": ">=14" } }, - "node_modules/@testing-library/dom/node_modules/aria-query": { + "node_modules/@testing-library/react/node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", @@ -3490,70 +3563,6 @@ "deep-equal": "^2.0.5" } }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@testing-library/jest-dom": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", - "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.21", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/react": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", - "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/@testing-library/user-event": { "version": "14.6.1", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", @@ -3621,13 +3630,13 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/body-parser": { @@ -3673,9 +3682,9 @@ } }, "node_modules/@types/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, "node_modules/@types/d3-color": { @@ -3782,22 +3791,22 @@ } }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", "dev": true, "license": "MIT", "dependencies": { @@ -3848,9 +3857,9 @@ "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { @@ -3946,7 +3955,6 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/mdast": { @@ -3972,18 +3980,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, "node_modules/@types/node-forge": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.13.tgz", - "integrity": "sha512-zePQJSW5QkwSHKRApqWCVKeKoSOt4xvEnLENZPjyvm9Ezdf/EyDeJM7jqLzOwjVICQQzvLZ63T55MKdJB5H6ww==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "dev": true, "license": "MIT", "dependencies": { @@ -4011,9 +4019,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", - "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4061,13 +4069,12 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -4082,15 +4089,26 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/sockjs": { @@ -4124,9 +4142,9 @@ "license": "MIT" }, "node_modules/@types/validator": { - "version": "13.15.2", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.2.tgz", - "integrity": "sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==", + "version": "13.15.4", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.4.tgz", + "integrity": "sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==", "license": "MIT" }, "node_modules/@types/ws": { @@ -4140,9 +4158,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.34", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", + "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", "dev": true, "license": "MIT", "dependencies": { @@ -4157,16 +4175,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", - "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", + "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/type-utils": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/type-utils": "8.46.3", + "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -4180,9 +4198,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.37.0", + "@typescript-eslint/parser": "^8.46.3", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -4195,15 +4213,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", - "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", + "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4" }, "engines": { @@ -4215,17 +4233,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", - "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", + "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.37.0", - "@typescript-eslint/types": "^8.37.0", + "@typescript-eslint/tsconfig-utils": "^8.46.3", + "@typescript-eslint/types": "^8.46.3", "debug": "^4.3.4" }, "engines": { @@ -4236,17 +4254,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", - "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", + "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0" + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4257,9 +4275,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", - "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", + "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4269,18 +4287,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", - "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", + "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -4293,13 +4311,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", - "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", + "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4310,15 +4328,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", - "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", + "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.37.0", - "@typescript-eslint/tsconfig-utils": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/project-service": "8.46.3", + "@typescript-eslint/tsconfig-utils": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4334,7 +4352,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -4362,9 +4380,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4374,15 +4392,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", - "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", + "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0" + "@typescript-eslint/scope-manager": "8.46.3", + "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4393,16 +4411,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", - "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", + "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/types": "8.46.3", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4654,9 +4672,9 @@ "license": "Apache-2.0" }, "node_modules/@yaireo/tagify": { - "version": "4.35.2", - "resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.2.tgz", - "integrity": "sha512-bqGdhIYXoU+bklT0zjGmvMNfjYCtwWDAfeaSwaneEeo+DterNJFmFR6ATbhjA5QUICqZygBfoPy980I5lRIHsw==", + "version": "4.35.5", + "resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.5.tgz", + "integrity": "sha512-IJf1z205bocsDoiJ6syqGwW8bY/oCQttpGfMeaeUipbNwmz4j+5oaCT6Gx+Bbu9ToGqGhd0Qo1yjcbJPArjJqg==", "license": "MIT", "engines": { "node": ">=16.15.0", @@ -4807,16 +4825,15 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { "type": "github", @@ -4841,19 +4858,30 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "ajv": "^8.8.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -4983,13 +5011,13 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "dependencies": { + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -5136,13 +5164,6 @@ "dev": true, "license": "MIT" }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -5244,23 +5265,6 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/babel-loader": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", @@ -5372,9 +5376,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -5395,7 +5399,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -5451,6 +5455,15 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", + "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -5620,9 +5633,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "funding": [ { "type": "opencollective", @@ -5639,10 +5652,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -5867,6 +5881,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5918,9 +5938,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", "funding": [ { "type": "opencollective", @@ -5948,17 +5968,19 @@ } }, "node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/char-regex": { @@ -6175,9 +6197,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -6463,13 +6485,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", - "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", + "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1" + "browserslist": "^4.26.3" }, "funding": { "type": "opencollective", @@ -6477,9 +6499,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.44.0.tgz", - "integrity": "sha512-gvMQAGB4dfVUxpYD0k3Fq8J+n5bB6Ytl15lqlZrOIXFzxOhtPaObfkQGHtMRdyjIf7z2IeNULwi1jEwyS+ltKQ==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.46.0.tgz", + "integrity": "sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6557,23 +6579,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -6663,9 +6668,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -6977,9 +6982,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7035,9 +7040,9 @@ } }, "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -7329,9 +7334,9 @@ } }, "node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, "license": "MIT" }, @@ -7544,9 +7549,9 @@ } }, "node_modules/editorconfig/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -7571,26 +7576,10 @@ "node": ">=12.0.0" } }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { - "version": "1.5.187", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", - "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", + "version": "1.5.248", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.248.tgz", + "integrity": "sha512-zsur2yunphlyAO4gIubdJEXCK6KOVvtpiuDfCIqbM9FjcnMYiyn0ICa3hWfPr0nc41zcLWobgy1iL7VvoOyA2Q==", "license": "ISC" }, "node_modules/emittery": { @@ -7632,29 +7621,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -7665,9 +7631,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, "license": "MIT", "dependencies": { @@ -7702,9 +7668,9 @@ } }, "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.20.0.tgz", + "integrity": "sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg==", "dev": true, "license": "MIT", "bin": { @@ -7722,9 +7688,9 @@ "optional": true }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8081,9 +8047,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", - "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "license": "MIT", "dependencies": { @@ -8200,38 +8166,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8263,12 +8197,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8524,6 +8452,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/express-session": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", @@ -8608,9 +8554,9 @@ "license": "Apache-2.0" }, "node_modules/fast-equals": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", - "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", + "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -8664,9 +8610,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -8740,39 +8686,6 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -8896,9 +8809,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -9142,6 +9055,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9277,6 +9199,23 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -9354,6 +9293,45 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/handlebars/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -9701,9 +9679,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", "dev": true, "license": "MIT", "dependencies": { @@ -10152,9 +10130,9 @@ "license": "ISC" }, "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.6.tgz", + "integrity": "sha512-gtGXVaBdl5mAes3rPcMedEBm12ibjt1kDMFfheul1wUAOVEJW60voNdMVzVkfLN06O7ZaD/rxhfKgtlgtTbMjg==", "license": "MIT" }, "node_modules/internal-slot": { @@ -10191,15 +10169,10 @@ } }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "license": "MIT", - "optional": true, - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } @@ -10472,13 +10445,14 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -10562,9 +10536,9 @@ } }, "node_modules/is-network-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", - "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", "dev": true, "license": "MIT", "engines": { @@ -10895,9 +10869,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -10941,42 +10915,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -11051,21 +10989,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-circus/node_modules/pretty-format": { @@ -11083,19 +11017,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-circus/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11137,23 +11058,6 @@ } } }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", @@ -11200,21 +11104,17 @@ } } }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-config/node_modules/pretty-format": { @@ -11232,19 +11132,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-config/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11268,21 +11155,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-diff/node_modules/pretty-format": { @@ -11300,19 +11183,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-diff/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11350,21 +11220,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-each/node_modules/pretty-format": { @@ -11382,19 +11248,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-each/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11549,21 +11402,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { @@ -11581,19 +11430,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-matcher-utils/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11622,21 +11458,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-message-util/node_modules/pretty-format": { @@ -11654,19 +11486,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-message-util/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11752,23 +11571,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -11802,23 +11604,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -11853,23 +11638,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -11902,21 +11670,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-snapshot/node_modules/pretty-format": { @@ -11934,19 +11698,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-snapshot/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11955,9 +11706,9 @@ "license": "MIT" }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -11985,23 +11736,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -12020,6 +11754,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -12033,23 +11780,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -12065,19 +11795,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -12105,23 +11822,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -12155,13 +11855,13 @@ } }, "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { - "jiti": "bin/jiti.js" + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-beautify": { @@ -12271,13 +11971,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT", - "optional": true - }, "node_modules/jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -12350,10 +12043,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -12375,9 +12067,9 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -12432,14 +12124,14 @@ } }, "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "node_modules/leven": { @@ -12510,13 +12202,17 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -12560,6 +12256,20 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -12573,6 +12283,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -12656,9 +12372,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -13069,20 +12785,19 @@ } }, "node_modules/memfs": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", - "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.50.0.tgz", + "integrity": "sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/json-pack": "^1.0.3", - "@jsonjoy.com/util": "^1.3.0", - "tree-dump": "^1.0.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", "tslib": "^2.0.0" }, - "engines": { - "node": ">= 4.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" @@ -14040,9 +13755,9 @@ } }, "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "funding": [ { "type": "github", @@ -14097,9 +13812,9 @@ } }, "node_modules/node-abi": { - "version": "3.75.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", - "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "version": "3.80.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz", + "integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -14109,9 +13824,9 @@ } }, "node_modules/node-abi/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14267,9 +13982,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "optional": true, "bin": { @@ -14287,9 +14002,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, "node_modules/nodemon": { @@ -14332,9 +14047,9 @@ } }, "node_modules/nodemon/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -14437,9 +14152,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", "dev": true, "license": "MIT" }, @@ -14643,6 +14358,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/optimist": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", @@ -15169,29 +14891,9 @@ } }, "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "dev": true, "funding": [ { @@ -15205,35 +14907,25 @@ ], "license": "MIT", "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" + "camelcase-css": "^2.0.1" }, "engines": { - "node": ">= 14" + "node": "^12 || ^14 || >= 16" }, "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } + "postcss": "^8.4.21" } }, "node_modules/postcss-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", - "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.0.tgz", + "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==", "dev": true, "license": "MIT", "dependencies": { "cosmiconfig": "^9.0.0", - "jiti": "^1.20.0", - "semver": "^7.5.4" + "jiti": "^2.5.1", + "semver": "^7.6.2" }, "engines": { "node": ">= 18.12.0" @@ -15257,9 +14949,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -15513,6 +15205,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -15793,16 +15492,16 @@ } }, "node_modules/react-i18next": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz", - "integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==", + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.4.tgz", + "integrity": "sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { - "i18next": ">= 23.2.3", + "i18next": ">= 23.4.0", "react": ">= 16.8.0", "typescript": "^5" }, @@ -15819,10 +15518,11 @@ } }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT", + "peer": true }, "node_modules/react-markdown": { "version": "10.1.0", @@ -16108,9 +15808,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "license": "MIT", "dependencies": { @@ -16141,18 +15841,18 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -16166,31 +15866,18 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/rehype-highlight": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz", @@ -16326,13 +16013,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -16406,14 +16093,14 @@ } }, "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.0.tgz", + "integrity": "sha512-DxdlA1bdNzkZK7JiNWH+BAx1x4tEJWoTofIopFo6qWUU94jYrFZ0ubY05TqH3nWPJ1nKa1JWVFDINZ3fnrle/A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" + "glob": "^11.0.3", + "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" @@ -16466,9 +16153,9 @@ } }, "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, "license": "ISC", "engines": { @@ -16476,11 +16163,11 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, @@ -16519,9 +16206,9 @@ } }, "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", "engines": { @@ -16655,9 +16342,9 @@ } }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { @@ -16674,6 +16361,43 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -16900,9 +16624,9 @@ } }, "node_modules/sequelize/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17282,9 +17006,9 @@ } }, "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -17354,13 +17078,13 @@ } }, "node_modules/socks": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", - "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "license": "MIT", "optional": true, "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -17384,13 +17108,13 @@ } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -17467,11 +17191,11 @@ } }, "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause", - "optional": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/sqlite3": { "version": "5.1.7", @@ -17826,21 +17550,21 @@ } }, "node_modules/style-to-js": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", - "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.19.tgz", + "integrity": "sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.9" + "style-to-object": "1.0.12" } }, "node_modules/style-to-object": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", - "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.12.tgz", + "integrity": "sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw==", "license": "MIT", "dependencies": { - "inline-style-parser": "0.2.4" + "inline-style-parser": "0.2.6" } }, "node_modules/styled-components": { @@ -17994,21 +17718,21 @@ "license": "MIT" }, "node_modules/superagent": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.2.tgz", - "integrity": "sha512-vWMq11OwWCC84pQaFPzF/VO3BrjkCeewuvJgt1jfV0499Z1QSAWN4EqfMM5WlFDDX9/oP8JjlDKpblrmEoyu4Q==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "dev": true, "license": "MIT", "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0" + "qs": "^6.11.2" }, "engines": { "node": ">=14.18.0" @@ -18028,14 +17752,14 @@ } }, "node_modules/supertest": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.3.tgz", - "integrity": "sha512-ORY0gPa6ojmg/C74P/bDoS21WL6FMXq5I8mawkEz30/zkwdu0gOeqstFy316vHG6OKxqQ+IbGneRemHI8WraEw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, "license": "MIT", "dependencies": { "methods": "^1.1.2", - "superagent": "^10.2.2" + "superagent": "^10.2.3" }, "engines": { "node": ">=14.18.0" @@ -18080,6 +17804,92 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.30.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz", + "integrity": "sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/swc-loader": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", @@ -18095,9 +17905,9 @@ } }, "node_modules/swr": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", - "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", "license": "MIT", "dependencies": { "dequal": "^2.0.3", @@ -18142,9 +17952,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -18156,7 +17966,7 @@ "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.6", + "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", @@ -18165,7 +17975,7 @@ "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", @@ -18179,6 +17989,59 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/tailwindcss/node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", @@ -18193,14 +18056,33 @@ "node": ">=4" } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { @@ -18221,9 +18103,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -18282,14 +18164,14 @@ "license": "ISC" }, "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -18439,14 +18321,18 @@ } }, "node_modules/thingies": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", - "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", "dev": true, - "license": "Unlicense", + "license": "MIT", "engines": { "node": ">=10.18" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, "peerDependencies": { "tslib": "^2" } @@ -18596,9 +18482,9 @@ } }, "node_modules/tree-dump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", - "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -18652,19 +18538,19 @@ "license": "Apache-2.0" }, "node_modules/ts-jest": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", - "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", - "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.2", + "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -18705,9 +18591,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -18731,9 +18617,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", - "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", + "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -18751,27 +18637,10 @@ "webpack": "^5.0.0" } }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/ts-loader/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -18928,9 +18797,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -18941,15 +18810,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", - "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", + "version": "8.46.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.3.tgz", + "integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.37.0", - "@typescript-eslint/parser": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0" + "@typescript-eslint/eslint-plugin": "8.46.3", + "@typescript-eslint/parser": "8.46.3", + "@typescript-eslint/typescript-estree": "8.46.3", + "@typescript-eslint/utils": "8.46.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -18960,7 +18829,21 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" } }, "node_modules/uid-safe": { @@ -19044,9 +18927,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "license": "MIT", "engines": { @@ -19054,9 +18937,9 @@ } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, "license": "MIT", "engines": { @@ -19117,9 +19000,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -19171,9 +19054,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -19204,9 +19087,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "funding": [ { "type": "opencollective", @@ -19254,9 +19137,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -19313,9 +19196,9 @@ } }, "node_modules/validator": { - "version": "13.15.15", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", - "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "version": "13.15.20", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", + "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -19345,9 +19228,9 @@ } }, "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -19447,9 +19330,9 @@ } }, "node_modules/webpack": { - "version": "5.100.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz", - "integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==", + "version": "5.102.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", + "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -19461,9 +19344,9 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.26.3", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.2", + "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -19473,10 +19356,10 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", + "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, "bin": { @@ -19552,15 +19435,15 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", - "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", "dev": true, "license": "MIT", "dependencies": { "colorette": "^2.0.10", - "memfs": "^4.6.0", - "mime-types": "^2.1.31", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" @@ -19581,6 +19464,19 @@ } } }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-dev-server": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", @@ -20055,16 +19951,12 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "dev": true, + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, "engines": { - "node": ">= 14.6" + "node": ">= 6" } }, "node_modules/yargs": { @@ -20108,10 +20000,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/zustand": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.6.tgz", - "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/package.json b/package.json index 7515523..3f7e630 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "dotenv": "~16.5.0", "eslint-plugin-react": "^7.37.5", "express": "^4.21.2", + "express-rate-limit": "^8.2.1", "express-session": "~1.18.1", "helmet": "~8.1.0", "highlight.js": "^11.11.1", @@ -150,6 +151,8 @@ "sequelize": "~6.37.7", "slugify": "^1.6.6", "sqlite3": "~5.1.7", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "swr": "^2.2.5", "tagify": "^0.1.1", "typescript-eslint": "^8.36.0",