From 96e441726c8e591da5a9b48c37dc8d16179cf41f Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 11:32:34 +0100 Subject: [PATCH 1/7] refactor(migrations): overhaul migration system to use schema transformation maps - Removed the BaseSchemaMigration class and replaced it with a more flexible schema migration approach using transformation functions. - Updated global-config, recording, room, and user migrations to utilize the new schema migration map structure. - Introduced runtime migration registry for better management of migration execution. - Enhanced migration service to handle schema migrations more efficiently, including improved error handling and logging. - Added utility functions for generating and validating schema migration names. - Updated migration repository methods to streamline migration status tracking. --- meet-ce/backend/src/migrations/README.md | 125 +- .../src/migrations/api-key-migrations.ts | 30 +- .../backend/src/migrations/base-migration.ts | 124 -- .../migrations/global-config-migrations.ts | 33 +- meet-ce/backend/src/migrations/index.ts | 6 + .../src/migrations/migration-registry.ts | 36 +- .../src/migrations/recording-migrations.ts | 30 +- .../backend/src/migrations/room-migrations.ts | 32 +- .../backend/src/migrations/user-migrations.ts | 30 +- meet-ce/backend/src/models/migration.model.ts | 155 +-- .../mongoose-schemas/migration.schema.ts | 7 +- .../models/mongoose-schemas/room.schema.ts | 43 +- .../src/repositories/migration.repository.ts | 23 +- meet-ce/backend/src/server.ts | 10 +- .../backend/src/services/migration.service.ts | 463 ++++--- pnpm-lock.yaml | 1070 ++++++++++++++++- 16 files changed, 1588 insertions(+), 629 deletions(-) delete mode 100644 meet-ce/backend/src/migrations/base-migration.ts create mode 100644 meet-ce/backend/src/migrations/index.ts diff --git a/meet-ce/backend/src/migrations/README.md b/meet-ce/backend/src/migrations/README.md index fadf16ab..5447feff 100644 --- a/meet-ce/backend/src/migrations/README.md +++ b/meet-ce/backend/src/migrations/README.md @@ -21,7 +21,6 @@ The schema migration system enables safe evolution of MongoDB document structure - ✅ **HA-safe** (distributed locking prevents concurrent migrations) - ✅ **Batch processing** (efficient handling of large collections) - ✅ **Progress tracking** (migrations stored in `MeetMigration` collection) -- ✅ **Version validation** (optional runtime checks in repositories) --- @@ -47,7 +46,6 @@ Each document includes a `schemaVersion` field: ``` src/ ├── migrations/ -│ ├── base-migration.ts # Base class for migrations │ ├── migration-registry.ts # Central registry of all collections │ ├── room-migrations.ts # Room-specific migrations │ ├── recording-migrations.ts # Recording-specific migrations @@ -59,13 +57,24 @@ src/ └── migration.model.ts # Migration types and interfaces ``` -**Note**: All migration types and interfaces (`ISchemaMigration`, `MigrationContext`, `MigrationResult`, `SchemaVersion`, `CollectionMigrationRegistry`) are defined in `src/models/migration.model.ts` for better code organization. - --- ## Adding New Migrations -### Step 1: Update Schema Version in Configuration +### Step 1: Update TypeScript Interface + +Update the domain interface to include new fields or changes: + +```typescript +// typings/src/room.ts +export interface MeetRoom extends MeetRoomOptions { + roomId: string; + // ... existing fields ... + maxParticipants: number; // New field +} +``` + +### Step 2: Update Schema Version in Configuration In `src/config/internal-config.ts`, increment the version constant: @@ -78,89 +87,48 @@ export const INTERNAL_CONFIG = { }; ``` -### Step 2: Create Migration Class +### Step 3: Update Moongose Schema + +Update the Mongoose schema to reflect the changes (new fields, etc.): ```typescript -import { BaseSchemaMigration } from './base-migration.js'; -import { MeetRoomDocument } from '../repositories/schemas/room.schema.js'; -import { MigrationContext } from '../models/migration.model.js'; -import { Model } from 'mongoose'; - -class RoomMigrationV1ToV2 extends BaseSchemaMigration { - fromVersion = 1; - toVersion = 2; - description = 'Add maxParticipants field with default value of 100'; - - protected async transform(document: MeetRoomDocument): Promise> { - // Return fields to update (schemaVersion is handled automatically) - return { - maxParticipants: 100 - }; - } - - // Optional: Add validation before migration runs - async validate(model: Model, context: MigrationContext): Promise { - // Check prerequisites, data integrity, etc. - return true; - } -} -``` - -### Step 3: Register Migration - -Add the migration instance to the migrations array in `room-migrations.ts`: - -```typescript -import { ISchemaMigration } from '../models/migration.model.js'; -import { MeetRoomDocument } from '../repositories/schemas/room.schema.js'; - -export const roomMigrations: ISchemaMigration[] = [ - new RoomMigrationV1ToV2() - // Future migrations will be added here -]; -``` - -### Step 4: Update Schema Definition - -Update the Mongoose schema default version in `internal-config.ts`: - -```typescript -// config/internal-config.ts -export const INTERNAL_CONFIG = { - // ... other config - ROOM_SCHEMA_VERSION: 2 // Updated from 1 - // ... -}; -``` - -If adding new required fields, update the Mongoose schema: - -```typescript -// repositories/schemas/room.schema.ts -import { INTERNAL_CONFIG } from '../../config/internal-config.js'; - +// models/mongoose-schemas/room.schema.ts const MeetRoomSchema = new Schema({ - schemaVersion: { - type: Number, - required: true, - default: INTERNAL_CONFIG.ROOM_SCHEMA_VERSION // Uses config value (2) - }, // ... existing fields ... maxParticipants: { type: Number, required: true, default: 100 } // New field }); ``` -### Step 5: Update TypeScript Interface - -Update the domain interface to include new fields: +### Step 4: Create Migration Definition ```typescript -// typings/src/room.ts -export interface MeetRoom extends MeetRoomOptions { - roomId: string; - // ... existing fields ... - maxParticipants: number; // New field -} +import { SchemaTransform, generateSchemaMigrationName } from '../models/migration.model.js'; +import { MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; + +const roomMigrationV1ToV2Name = generateSchemaMigrationName('MeetRoom', 1, 2); + +const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ + $set: { + maxParticipants: 100 + } +}); +``` + +`transform` must return MongoDB update operators, so it can express any kind of change: + +- `$set` to add/modify values +- `$unset` to remove properties +- `$rename` to rename fields +- Any other supported update operator + +### Step 5: Register Migration + +Add the migration to the map initialization in `room-migrations.ts`: + +```typescript +export const roomMigrations: SchemaMigrationMap = new Map([ + [roomMigrationV1ToV2Name, roomMigrationV1ToV2Transform] +]); ``` ### Step 6: Test Migration @@ -187,7 +155,6 @@ Each migration is tracked in the `MeetMigration` collection: "fromVersion": 1, "toVersion": 2, "migratedCount": 1523, - "skippedCount": 0, "failedCount": 0, "durationMs": 123000 } diff --git a/meet-ce/backend/src/migrations/api-key-migrations.ts b/meet-ce/backend/src/migrations/api-key-migrations.ts index eec68ae6..3b0db16b 100644 --- a/meet-ce/backend/src/migrations/api-key-migrations.ts +++ b/meet-ce/backend/src/migrations/api-key-migrations.ts @@ -1,24 +1,20 @@ -import { ISchemaMigration } from '../models/migration.model.js'; +import { SchemaMigrationMap } from '../models/migration.model.js'; import { MeetApiKeyDocument } from '../models/mongoose-schemas/api-key.schema.js'; /** - * All migrations for the MeetApiKey collection in chronological order. - * Add new migrations to this array as the schema evolves. + * Schema migrations for MeetApiKey. + * Key format: schema_{collection}_v{from}_to_v{to} * - * Example migration (when needed in the future): + * Example: * - * class ApiKeyMigrationV1ToV2 extends BaseSchemaMigration { - * fromVersion = 1; - * toVersion = 2; - * description = 'Add expirationDate field for API key expiration'; + * const apiKeyMigrationV1ToV2Name = generateSchemaMigrationName('MeetApiKey', 1, 2); * - * protected async transform(document: MeetApiKeyDocument): Promise> { - * return { - * expirationDate: undefined // No expiration for existing keys - * }; - * } - * } + * const apiKeyMigrationV1ToV2Transform: SchemaTransform = () => ({ + * $set: { + * expirationDate: undefined + * } + * }); */ -export const apiKeyMigrations: ISchemaMigration[] = [ - // Migrations will be added here as the schema evolves -]; +export const apiKeyMigrations: SchemaMigrationMap = new Map([ + // [apiKeyMigrationV1ToV2Name, apiKeyMigrationV1ToV2Transform] +]); diff --git a/meet-ce/backend/src/migrations/base-migration.ts b/meet-ce/backend/src/migrations/base-migration.ts deleted file mode 100644 index 69794eb4..00000000 --- a/meet-ce/backend/src/migrations/base-migration.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Model } from 'mongoose'; -import { ISchemaMigration, MigrationContext, MigrationResult, SchemaVersion } from '../models/migration.model.js'; - -/** - * Base class for schema migrations providing common functionality. - * Extend this class to implement specific migrations for collections. - */ -export abstract class BaseSchemaMigration implements ISchemaMigration { - abstract fromVersion: SchemaVersion; - abstract toVersion: SchemaVersion; - abstract description: string; - - /** - * Default batch size for processing documents. - * Can be overridden in subclasses for collections with large documents. - */ - protected readonly defaultBatchSize = 50; - - /** - * Executes the migration in batches. - * Processes all documents at fromVersion and upgrades them to toVersion. - */ - async execute(model: Model, context: MigrationContext): Promise { - const startTime = Date.now(); - const batchSize = context.batchSize || this.defaultBatchSize; - let migratedCount = 0; - const skippedCount = 0; - let failedCount = 0; - - context.logger.info( - `Starting schema migration: ${this.description} (v${this.fromVersion} -> v${this.toVersion})` - ); - - try { - // Find all documents at the source version - const totalDocs = await model.countDocuments({ schemaVersion: this.fromVersion }).exec(); - - if (totalDocs === 0) { - context.logger.info('No documents to migrate'); - return { - migratedCount: 0, - skippedCount: 0, - failedCount: 0, - durationMs: Date.now() - startTime - }; - } - - context.logger.info(`Found ${totalDocs} documents to migrate`); - - // Process documents in batches - let processedCount = 0; - - while (processedCount < totalDocs) { - const documents = await model.find({ schemaVersion: this.fromVersion }).limit(batchSize).exec(); - - if (documents.length === 0) { - break; - } - - // Transform and update each document - for (const doc of documents) { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updates = await this.transform(doc as any); - - // Update the document with new fields and version - await model - .updateOne( - { _id: doc._id }, - { - $set: { - ...updates, - schemaVersion: this.toVersion - } - } - ) - .exec(); - - migratedCount++; - } catch (error) { - failedCount++; - context.logger.warn(`Failed to migrate document ${doc._id}:`, error); - } - } - - processedCount += documents.length; - context.logger.debug(`Processed ${processedCount}/${totalDocs} documents`); - } - - const durationMs = Date.now() - startTime; - context.logger.info( - `Migration completed: ${migratedCount} migrated, ${failedCount} failed (${durationMs}ms)` - ); - - return { - migratedCount, - skippedCount, - failedCount, - durationMs - }; - } catch (error) { - context.logger.error('Migration failed:', error); - throw error; - } - } - - /** - * Transform a single document from source version to target version. - * Override this method to implement the specific transformation logic. - * - * @param document - The document to transform - * @returns Object with fields to update (excluding schemaVersion which is handled automatically) - */ - protected abstract transform(document: TDocument): Promise>; - - /** - * Optional validation before running migration. - * Default implementation always returns true. - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async validate(_model: Model, _context: MigrationContext): Promise { - return true; - } -} diff --git a/meet-ce/backend/src/migrations/global-config-migrations.ts b/meet-ce/backend/src/migrations/global-config-migrations.ts index d261d8ef..f474aaec 100644 --- a/meet-ce/backend/src/migrations/global-config-migrations.ts +++ b/meet-ce/backend/src/migrations/global-config-migrations.ts @@ -1,27 +1,20 @@ -import { ISchemaMigration } from '../models/migration.model.js'; +import { SchemaMigrationMap } from '../models/migration.model.js'; import { MeetGlobalConfigDocument } from '../models/mongoose-schemas/global-config.schema.js'; /** - * All migrations for the MeetGlobalConfig collection in chronological order. - * Add new migrations to this array as the schema evolves. + * Schema migrations for MeetGlobalConfig. + * Key format: schema_{collection}_v{from}_to_v{to} * - * Example migration (when needed in the future): + * Example: * - * class GlobalConfigMigrationV1ToV2 extends BaseSchemaMigration { - * fromVersion = 1; - * toVersion = 2; - * description = 'Add new branding configuration section'; + * const globalConfigMigrationV1ToV2Name = generateSchemaMigrationName('MeetGlobalConfig', 1, 2); * - * protected async transform(document: MeetGlobalConfigDocument): Promise> { - * return { - * brandingConfig: { - * logoUrl: '', - * companyName: 'OpenVidu Meet' - * } - * }; - * } - * } + * const globalConfigMigrationV1ToV2Transform: SchemaTransform = () => ({ + * $set: { + * newField: 'default-value' + * } + * }); */ -export const globalConfigMigrations: ISchemaMigration[] = [ - // Migrations will be added here as the schema evolves -]; +export const globalConfigMigrations: SchemaMigrationMap = new Map([ + // [globalConfigMigrationV1ToV2Name, globalConfigMigrationV1ToV2Transform] +]); diff --git a/meet-ce/backend/src/migrations/index.ts b/meet-ce/backend/src/migrations/index.ts new file mode 100644 index 00000000..089f70e5 --- /dev/null +++ b/meet-ce/backend/src/migrations/index.ts @@ -0,0 +1,6 @@ +export * from './api-key-migrations.js'; +export * from './global-config-migrations.js'; +export * from './migration-registry.js'; +export * from './recording-migrations.js'; +export * from './room-migrations.js'; +export * from './user-migrations.js'; diff --git a/meet-ce/backend/src/migrations/migration-registry.ts b/meet-ce/backend/src/migrations/migration-registry.ts index 90dcebc7..302aecae 100644 --- a/meet-ce/backend/src/migrations/migration-registry.ts +++ b/meet-ce/backend/src/migrations/migration-registry.ts @@ -1,13 +1,22 @@ import { INTERNAL_CONFIG } from '../config/internal-config.js'; -import { CollectionMigrationRegistry } from '../models/migration.model.js'; -import { meetApiKeyCollectionName, MeetApiKeyModel } from '../models/mongoose-schemas/api-key.schema.js'; +import { CollectionMigrationRegistry, SchemaMigratableDocument } from '../models/migration.model.js'; +import { + meetApiKeyCollectionName, + MeetApiKeyDocument, + MeetApiKeyModel +} from '../models/mongoose-schemas/api-key.schema.js'; import { meetGlobalConfigCollectionName, + MeetGlobalConfigDocument, MeetGlobalConfigModel } from '../models/mongoose-schemas/global-config.schema.js'; -import { meetRecordingCollectionName, MeetRecordingModel } from '../models/mongoose-schemas/recording.schema.js'; -import { meetRoomCollectionName, MeetRoomModel } from '../models/mongoose-schemas/room.schema.js'; -import { meetUserCollectionName, MeetUserModel } from '../models/mongoose-schemas/user.schema.js'; +import { + meetRecordingCollectionName, + MeetRecordingDocument, + MeetRecordingModel +} from '../models/mongoose-schemas/recording.schema.js'; +import { meetRoomCollectionName, MeetRoomDocument, MeetRoomModel } from '../models/mongoose-schemas/room.schema.js'; +import { meetUserCollectionName, MeetUserDocument, MeetUserModel } from '../models/mongoose-schemas/user.schema.js'; import { apiKeyMigrations } from './api-key-migrations.js'; import { globalConfigMigrations } from './global-config-migrations.js'; import { recordingMigrations } from './recording-migrations.js'; @@ -16,12 +25,18 @@ import { userMigrations } from './user-migrations.js'; /** * Central registry of all collection migrations. - * Defines the current version and migration path for each collection. + * Defines the current version and migration map for each collection. * * Order matters: collections should be listed in dependency order. * For example, if recordings depend on rooms, rooms should come first. */ -export const migrationRegistry: CollectionMigrationRegistry[] = [ +const migrationRegistry: [ + CollectionMigrationRegistry, + CollectionMigrationRegistry, + CollectionMigrationRegistry, + CollectionMigrationRegistry, + CollectionMigrationRegistry +] = [ // GlobalConfig - no dependencies, can run first { collectionName: meetGlobalConfigCollectionName, @@ -59,3 +74,10 @@ export const migrationRegistry: CollectionMigrationRegistry[] = [ migrations: recordingMigrations } ]; + +/** + * Homogeneous runtime view of the migration registry. + * Used by migration execution code that iterates over all collections. + */ +export const runtimeMigrationRegistry = + migrationRegistry as unknown as CollectionMigrationRegistry[]; diff --git a/meet-ce/backend/src/migrations/recording-migrations.ts b/meet-ce/backend/src/migrations/recording-migrations.ts index 8bbd683a..a0f0891d 100644 --- a/meet-ce/backend/src/migrations/recording-migrations.ts +++ b/meet-ce/backend/src/migrations/recording-migrations.ts @@ -1,24 +1,20 @@ -import { ISchemaMigration } from '../models/migration.model.js'; +import { SchemaMigrationMap } from '../models/migration.model.js'; import { MeetRecordingDocument } from '../models/mongoose-schemas/recording.schema.js'; /** - * All migrations for the MeetRecording collection in chronological order. - * Add new migrations to this array as the schema evolves. + * Schema migrations for MeetRecording. + * Key format: schema_{collection}_v{from}_to_v{to} * - * Example migration (when needed in the future): + * Example: * - * class RecordingMigrationV1ToV2 extends BaseSchemaMigration { - * fromVersion = 1; - * toVersion = 2; - * description = 'Add new optional field "quality" for recording quality tracking'; + * const recordingMigrationV1ToV2Name = generateSchemaMigrationName('MeetRecording', 1, 2); * - * protected async transform(document: MeetRecordingDocument): Promise> { - * return { - * quality: 'standard' // Default quality for existing recordings - * }; - * } - * } + * const recordingMigrationV1ToV2Transform: SchemaTransform = () => ({ + * $set: { + * quality: 'standard' + * } + * }); */ -export const recordingMigrations: ISchemaMigration[] = [ - // Migrations will be added here as the schema evolves -]; +export const recordingMigrations: SchemaMigrationMap = new Map([ + // [recordingMigrationV1ToV2Name, recordingMigrationV1ToV2Transform] +]); diff --git a/meet-ce/backend/src/migrations/room-migrations.ts b/meet-ce/backend/src/migrations/room-migrations.ts index b696452b..4690ea2f 100644 --- a/meet-ce/backend/src/migrations/room-migrations.ts +++ b/meet-ce/backend/src/migrations/room-migrations.ts @@ -1,26 +1,20 @@ -import { ISchemaMigration } from '../models/migration.model.js'; +import { SchemaMigrationMap } from '../models/migration.model.js'; import { MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; /** - * All migrations for the MeetRoom collection in chronological order. - * Add new migrations to this array as the schema evolves. + * Schema migrations for MeetRoom. + * Key format: schema_{collection}_v{from}_to_v{to} * - * Example migration (when needed in the future): + * Example: * - * class RoomMigrationV1ToV2 extends BaseSchemaMigration { - * fromVersion = 1; - * toVersion = 2; - * description = 'Add new required field "maxParticipants" with default value'; + * const roomMigrationV1ToV2Name = generateSchemaMigrationName('MeetRoom', 1, 2); * - * protected async transform(document: MeetRoomDocument): Promise> { - * return { - * maxParticipants: 100 // Add default value for existing rooms - * }; - * } - * } + * const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ + * $set: { + * maxParticipants: 100 + * } + * }); */ -export const roomMigrations: ISchemaMigration[] = [ - // Migrations will be added here as the schema evolves - // Example: new RoomMigrationV1ToV2(), - // Example: new RoomMigrationV2ToV3(), -]; +export const roomMigrations: SchemaMigrationMap = new Map([ + // [roomMigrationV1ToV2Name, roomMigrationV1ToV2Transform] +]); diff --git a/meet-ce/backend/src/migrations/user-migrations.ts b/meet-ce/backend/src/migrations/user-migrations.ts index d7449b87..464f87b1 100644 --- a/meet-ce/backend/src/migrations/user-migrations.ts +++ b/meet-ce/backend/src/migrations/user-migrations.ts @@ -1,24 +1,20 @@ -import { ISchemaMigration } from '../models/migration.model.js'; +import { SchemaMigrationMap } from '../models/migration.model.js'; import { MeetUserDocument } from '../models/mongoose-schemas/user.schema.js'; /** - * All migrations for the MeetUser collection in chronological order. - * Add new migrations to this array as the schema evolves. + * Schema migrations for MeetUser. + * Key format: schema_{collection}_v{from}_to_v{to} * - * Example migration (when needed in the future): + * Example: * - * class UserMigrationV1ToV2 extends BaseSchemaMigration { - * fromVersion = 1; - * toVersion = 2; - * description = 'Add email field for user notifications'; + * const userMigrationV1ToV2Name = generateSchemaMigrationName('MeetUser', 1, 2); * - * protected async transform(document: MeetUserDocument): Promise> { - * return { - * email: undefined // Email will be optional initially - * }; - * } - * } + * const userMigrationV1ToV2Transform: SchemaTransform = () => ({ + * $set: { + * email: undefined + * } + * }); */ -export const userMigrations: ISchemaMigration[] = [ - // Migrations will be added here as the schema evolves -]; +export const userMigrations: SchemaMigrationMap = new Map([ + // [userMigrationV1ToV2Name, userMigrationV1ToV2Transform] +]); diff --git a/meet-ce/backend/src/models/migration.model.ts b/meet-ce/backend/src/models/migration.model.ts index 28515487..f22e52b9 100644 --- a/meet-ce/backend/src/models/migration.model.ts +++ b/meet-ce/backend/src/models/migration.model.ts @@ -1,5 +1,4 @@ -import { Model } from 'mongoose'; -import { LoggerService } from '../services/logger.service.js'; +import { Document, Model, UpdateQuery } from 'mongoose'; /** * Interface representing a migration document in MongoDB. @@ -58,19 +57,11 @@ export enum MigrationStatus { } /** - * Enum defining all possible migration names in the system. - * Each migration should have a unique identifier. - * * Schema migrations follow the pattern: schema_{collection}_v{from}_to_v{to} * Example: 'schema_room_v1_to_v2', 'schema_recording_v2_to_v3' */ -export enum MigrationName { - /** - * Migration from legacy storage (S3, ABS, GCS) to MongoDB. - * Includes: GlobalConfig, Users, ApiKeys, Rooms, and Recordings. - */ - LEGACY_STORAGE_TO_MONGODB = 'legacy_storage_to_mongodb' -} +export type SchemaMigrationName = `schema_${string}_v${number}_to_v${number}`; +export type MigrationName = SchemaMigrationName; /** * Generates a migration name for schema version upgrades. @@ -83,12 +74,49 @@ export enum MigrationName { * @example * generateSchemaMigrationName('MeetRoom', 1, 2) // Returns: 'schema_room_v1_to_v2' */ -export function generateSchemaMigrationName(collectionName: string, fromVersion: number, toVersion: number): string { +export function generateSchemaMigrationName( + collectionName: string, + fromVersion: number, + toVersion: number +): SchemaMigrationName { // Convert collection name to lowercase and remove 'Meet' prefix const simpleName = collectionName.replace(/^Meet/, '').toLowerCase(); return `schema_${simpleName}_v${fromVersion}_to_v${toVersion}`; } +/** + * Checks whether a string matches the schema migration naming convention. + */ +export function isSchemaMigrationName(name: string): name is SchemaMigrationName { + return /^schema_[a-z0-9_]+_v\d+_to_v\d+$/.test(name); +} + +/** + * Parses a schema migration name and extracts entity and versions. + */ +export function parseSchemaMigrationName( + name: string +): { collectionName: string; fromVersion: SchemaVersion; toVersion: SchemaVersion } | null { + const match = /^schema_([a-z0-9_]+)_v(\d+)_to_v(\d+)$/.exec(name); + + if (!match) { + return null; + } + + return { + collectionName: match[1], + fromVersion: Number(match[2]), + toVersion: Number(match[3]) + }; +} + +/** + * Base document shape required for schema migrations. + */ +export interface SchemaMigratableDocument extends Document { + schemaVersion?: number; +} + /** * Represents a schema version number. * Versions start at 1 and increment sequentially. @@ -96,14 +124,49 @@ export function generateSchemaMigrationName(collectionName: string, fromVersion: export type SchemaVersion = number; /** - * Context provided to migration functions. - * Contains utilities and services needed during migration. + * MongoDB update operations generated by a migration transform. + * Supports full update operators like $set, $unset, $rename, etc. */ -export interface MigrationContext { - /** Logger service for tracking migration progress */ - logger: LoggerService; - /** Batch size for processing documents (default: 50) */ - batchSize?: number; +export type MigrationUpdate = UpdateQuery; + +/** + * Function that transforms a document and returns a MongoDB update operation. + */ +export type SchemaTransform = ( + document: TDocument +) => MigrationUpdate; + +/** + * Map of schema migration names to transform functions. + */ +export type SchemaMigrationMap = Map< + SchemaMigrationName, + SchemaTransform +>; + +/** + * Resolved migration step ready to be executed. + */ +export interface SchemaMigrationStep { + name: SchemaMigrationName; + fromVersion: SchemaVersion; + toVersion: SchemaVersion; + transform: SchemaTransform; +} + +/** + * Registry entry for a collection's migrations. + * Groups all migrations for a specific collection. + */ +export interface CollectionMigrationRegistry { + /** Name of the collection */ + collectionName: string; + /** Mongoose model for the collection */ + model: Model; + /** Current schema version expected by the application */ + currentVersion: SchemaVersion; + /** Map of migration names to their transform functions */ + migrations: SchemaMigrationMap; } /** @@ -113,60 +176,8 @@ export interface MigrationContext { export interface MigrationResult { /** Number of documents successfully migrated */ migratedCount: number; - /** Number of documents skipped (already at target version) */ - skippedCount: number; /** Number of documents that failed migration */ failedCount: number; /** Total time taken in milliseconds */ durationMs: number; } - -/** - * Interface for a single schema migration handler. - * Each migration transforms documents from one version to the next. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export interface ISchemaMigration { - /** The source schema version this migration upgrades from */ - fromVersion: SchemaVersion; - /** The target schema version this migration upgrades to */ - toVersion: SchemaVersion; - /** Short description of what this migration does */ - description: string; - - /** - * Executes the migration on a batch of documents. - * Should update documents using MongoDB bulk operations for efficiency. - * - * @param model - Mongoose model for the collection - * @param context - Migration context with logger and configuration - * @returns Migration result with statistics - */ - execute(model: Model, context: MigrationContext): Promise; - - /** - * Optional validation to check if migration is safe to run. - * Can verify prerequisites or data integrity before migration starts. - * - * @param model - Mongoose model for the collection - * @param context - Migration context with logger and configuration - * @returns true if migration can proceed, false otherwise - */ - validate?(model: Model, context: MigrationContext): Promise; -} - -/** - * Registry entry for a collection's migrations. - * Groups all migrations for a specific collection. - */ -export interface CollectionMigrationRegistry { - /** Name of the collection */ - collectionName: string; - /** Mongoose model for the collection */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - model: Model; - /** Current schema version expected by the application */ - currentVersion: SchemaVersion; - /** Array of migrations in chronological order */ - migrations: ISchemaMigration[]; -} diff --git a/meet-ce/backend/src/models/mongoose-schemas/migration.schema.ts b/meet-ce/backend/src/models/mongoose-schemas/migration.schema.ts index 89578726..8c15ea28 100644 --- a/meet-ce/backend/src/models/mongoose-schemas/migration.schema.ts +++ b/meet-ce/backend/src/models/mongoose-schemas/migration.schema.ts @@ -1,5 +1,5 @@ import { Document, model, Schema } from 'mongoose'; -import { MeetMigration, MigrationName, MigrationStatus } from '../migration.model.js'; +import { isSchemaMigrationName, MeetMigration, MigrationStatus } from '../migration.model.js'; /** * Mongoose Document interface for MeetMigration. @@ -16,7 +16,10 @@ const MigrationSchema = new Schema( name: { type: String, required: true, - enum: Object.values(MigrationName) + validate: { + validator: (value: string) => isSchemaMigrationName(value), + message: 'Invalid migration name format' + } }, status: { type: String, diff --git a/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts b/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts index 60b3af81..566822bf 100644 --- a/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts +++ b/meet-ce/backend/src/models/mongoose-schemas/room.schema.ts @@ -1,6 +1,5 @@ import { MeetRecordingAccess, - MeetRecordingEncodingPreset, MeetRecordingLayout, MeetRoom, MeetRoomDeletionPolicyWithMeeting, @@ -54,34 +53,16 @@ const MeetRecordingConfigSchema = new Schema( layout: { type: String, enum: Object.values(MeetRecordingLayout), - required: true, - default: MeetRecordingLayout.GRID + required: true }, encoding: { type: Schema.Types.Mixed, - required: false, - encoding: { - type: Schema.Types.Mixed, - required: true, - default: MeetRecordingEncodingPreset.H264_720P_30, - validate: { - validator: (value: any) => { - if (!value) return true; - - if (typeof value === 'string') return true; - - if (typeof value === 'object') return value.video || value.audio; - - return false; - }, - message: 'Encoding must be a preset string or options object' - } - } + required: true }, allowAccessTo: { type: String, enum: Object.values(MeetRecordingAccess), - required: false + required: true } }, { _id: false } @@ -120,8 +101,7 @@ const MeetE2EEConfigSchema = new Schema( { enabled: { type: Boolean, - required: true, - default: false + required: true } }, { _id: false } @@ -134,8 +114,7 @@ const MeetCaptionsConfigSchema = new Schema( { enabled: { type: Boolean, - required: true, - default: true + required: true } }, { _id: false } @@ -215,13 +194,11 @@ const MeetRoomConfigSchema = new Schema( }, e2ee: { type: MeetE2EEConfigSchema, - required: true, - default: { enabled: false } + required: true }, captions: { type: MeetCaptionsConfigSchema, - required: true, - default: { enabled: false } + required: true } }, { _id: false } @@ -273,14 +250,12 @@ const MeetRoomSchema = new Schema( status: { type: String, enum: Object.values(MeetRoomStatus), - required: true, - default: MeetRoomStatus.OPEN + required: true }, meetingEndAction: { type: String, enum: Object.values(MeetingEndAction), - required: true, - default: MeetingEndAction.NONE + required: true } }, { diff --git a/meet-ce/backend/src/repositories/migration.repository.ts b/meet-ce/backend/src/repositories/migration.repository.ts index 22e6144f..80f88801 100644 --- a/meet-ce/backend/src/repositories/migration.repository.ts +++ b/meet-ce/backend/src/repositories/migration.repository.ts @@ -95,7 +95,7 @@ export class MigrationRepository extends BaseRepository { - const documents = await this.findAll(); - return documents; - } - - /** - * Get a specific migration by name. - * - * @param name - The name of the migration - * @returns The migration document or null if not found - */ - async getMigration(name: MigrationName): Promise { - const document = await this.findOne({ name }); - return document ? this.toDomain(document) : null; - } - /** * Check if a migration has been completed successfully. * diff --git a/meet-ce/backend/src/server.ts b/meet-ce/backend/src/server.ts index 5e6f2da5..1fefb73b 100644 --- a/meet-ce/backend/src/server.ts +++ b/meet-ce/backend/src/server.ts @@ -85,7 +85,9 @@ const createApp = () => { if (process.env.NODE_ENV === 'development') { // Serve internal API docs only in development mode appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) => - res.type('html').send(getOpenApiHtmlWithBasePath(internalApiHtmlFilePath, INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1)) + res + .type('html') + .send(getOpenApiHtmlWithBasePath(internalApiHtmlFilePath, INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1)) ); } @@ -137,7 +139,9 @@ const startServer = (app: express.Application) => { console.log( 'REST API Docs: ', - chalk.cyanBright(`http://localhost:${MEET_ENV.SERVER_PORT}${basePathDisplay}${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`) + chalk.cyanBright( + `http://localhost:${MEET_ENV.SERVER_PORT}${basePathDisplay}${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs` + ) ); logEnvVars(); }); @@ -164,8 +168,8 @@ const isMainModule = (): boolean => { if (isMainModule()) { registerDependencies(); const app = createApp(); - startServer(app); await initializeEagerServices(); + startServer(app); } export { createApp, registerDependencies }; diff --git a/meet-ce/backend/src/services/migration.service.ts b/meet-ce/backend/src/services/migration.service.ts index f0a8a780..a0be8e75 100644 --- a/meet-ce/backend/src/services/migration.service.ts +++ b/meet-ce/backend/src/services/migration.service.ts @@ -2,13 +2,15 @@ import { inject, injectable } from 'inversify'; import { Model } from 'mongoose'; import ms from 'ms'; import { MeetLock } from '../helpers/redis.helper.js'; -import { migrationRegistry } from '../migrations/migration-registry.js'; +import { runtimeMigrationRegistry } from '../migrations/migration-registry.js'; import { CollectionMigrationRegistry, generateSchemaMigrationName, - ISchemaMigration, - MigrationContext, - MigrationName + MigrationResult, + MigrationUpdate, + SchemaMigratableDocument, + SchemaMigrationStep, + SchemaVersion } from '../models/migration.model.js'; import { ApiKeyRepository } from '../repositories/api-key.repository.js'; import { GlobalConfigRepository } from '../repositories/global-config.repository.js'; @@ -73,133 +75,19 @@ export class MigrationService { /** * Runs all schema migrations to upgrade document structures to the latest version. * Processes each collection in the registry and executes pending migrations. - * - * Schema migrations run after data migrations and upgrade existing documents - * to match the current schema version expected by the application. */ protected async runSchemaMigrations(): Promise { this.logger.info('Running schema migrations...'); try { let totalMigrated = 0; - let totalSkipped = 0; // Process each collection in the registry - for (const registry of migrationRegistry) { - this.logger.info(`Checking schema version for collection: ${registry.collectionName}`); - - // Get the current version of documents in the collection - const currentVersionInDb = await this.getCurrentSchemaVersion(registry.model); - - if (currentVersionInDb === null) { - this.logger.info(`No documents found in ${registry.collectionName}, skipping migration`); - continue; - } - - if (currentVersionInDb === registry.currentVersion) { - this.logger.info( - `Collection ${registry.collectionName} is already at version ${registry.currentVersion}` - ); - continue; - } - - if (currentVersionInDb > registry.currentVersion) { - this.logger.warn( - `Collection ${registry.collectionName} has version ${currentVersionInDb} ` + - `but application expects ${registry.currentVersion}. ` + - `This may indicate a downgrade or inconsistent deployment.` - ); - continue; - } - - // Find migrations needed to upgrade from current to target version - const neededMigrations = this.findNeededMigrations( - registry, - currentVersionInDb, - registry.currentVersion - ); - - if (neededMigrations.length === 0) { - this.logger.info(`No migrations needed for ${registry.collectionName}`); - continue; - } - - this.logger.info( - `Found ${neededMigrations.length} migrations for ${registry.collectionName} ` + - `(v${currentVersionInDb} -> v${registry.currentVersion})` - ); - - // Execute each migration in sequence - for (const migration of neededMigrations) { - const migrationName = generateSchemaMigrationName( - registry.collectionName, - migration.fromVersion, - migration.toVersion - ); - - // Check if this specific migration was already completed - const isCompleted = await this.migrationRepository.isCompleted(migrationName as MigrationName); - - if (isCompleted) { - this.logger.info(`Migration ${migrationName} already completed, skipping`); - continue; - } - - // Mark migration as started - await this.migrationRepository.markAsStarted(migrationName as MigrationName); - - try { - const migrationContext: MigrationContext = { - logger: this.logger, - batchSize: 50 // Default batch size - }; - - // Validate migration if validation method is provided - if (migration.validate) { - const isValid = await migration.validate(registry.model, migrationContext); - - if (!isValid) { - throw new Error(`Validation failed for migration ${migrationName}`); - } - } - - // Execute the migration - this.logger.info(`Executing migration: ${migration.description}`); - const result = await migration.execute(registry.model, migrationContext); - - // Track statistics - totalMigrated += result.migratedCount; - totalSkipped += result.skippedCount; - - // Mark migration as completed with metadata - const metadata: Record = { - collectionName: registry.collectionName, - fromVersion: migration.fromVersion, - toVersion: migration.toVersion, - migratedCount: result.migratedCount, - skippedCount: result.skippedCount, - failedCount: result.failedCount, - durationMs: result.durationMs - }; - - await this.migrationRepository.markAsCompleted(migrationName as MigrationName, metadata); - - this.logger.info( - `Migration ${migrationName} completed: ${result.migratedCount} migrated, ` + - `${result.failedCount} failed (${result.durationMs}ms)` - ); - } catch (error) { - // Mark migration as failed - const errorMessage = error instanceof Error ? error.message : String(error); - await this.migrationRepository.markAsFailed(migrationName as MigrationName, errorMessage); - throw error; - } - } + for (const registry of runtimeMigrationRegistry) { + totalMigrated += await this.migrateCollectionSchemas(registry); } - this.logger.info( - `Schema migrations completed successfully: ${totalMigrated} documents migrated, ${totalSkipped} skipped` - ); + this.logger.info(`Schema migrations completed successfully: ${totalMigrated} documents migrated`); } catch (error) { this.logger.error('Error running schema migrations:', error); throw error; @@ -207,17 +95,238 @@ export class MigrationService { } /** - * Gets the current schema version of documents in a collection. - * Samples the database to determine the version. + * Migrates documents in a collection through all required schema versions to reach the current version. + * + * @param registry - The collection migration registry containing migration steps and model + * @returns Number of documents migrated in this collection + */ + protected async migrateCollectionSchemas( + registry: CollectionMigrationRegistry + ): Promise { + this.logger.info(`Checking schema version for collection: ${registry.collectionName}`); + + const minVersionInDb = await this.getMinSchemaVersion(registry.model); + + if (minVersionInDb === null) { + this.logger.info(`No documents found in ${registry.collectionName}, skipping migration`); + return 0; + } + + const maxVersionInDb = await this.getMaxSchemaVersion(registry.model); + + if (maxVersionInDb && maxVersionInDb > registry.currentVersion) { + throw new Error( + `Collection ${registry.collectionName} has schemaVersion ${maxVersionInDb}, ` + + `which is higher than expected ${registry.currentVersion}. ` + + 'Startup aborted to prevent inconsistent schema handling.' + ); + } + + if (minVersionInDb === registry.currentVersion) { + this.logger.info(`Collection ${registry.collectionName} is already at version ${registry.currentVersion}`); + return 0; + } + + const migrationSteps = this.getRequiredMigrationSteps(registry, minVersionInDb); + let collectionMigrated = 0; + + for (const migrationStep of migrationSteps) { + collectionMigrated += await this.executeCollectionMigrationStep(registry, migrationStep); + } + + return collectionMigrated; + } + + /** + * Gets the required migration steps to upgrade from the current version in the database to the target version. + * Validates that there are no missing migration steps in the chain. + * + * @param registry - The collection migration registry + * @param minVersionInDb - The minimum schema version currently present in the database + * @returns Array of migration steps that need to be executed in order + */ + protected getRequiredMigrationSteps( + registry: CollectionMigrationRegistry, + minVersionInDb: SchemaVersion + ): SchemaMigrationStep[] { + const migrationSteps = this.findSchemaMigrationSteps(registry, minVersionInDb, registry.currentVersion); + + if (migrationSteps.length === 0) { + throw new Error( + `No migration steps found for ${registry.collectionName} ` + + `(v${minVersionInDb} -> v${registry.currentVersion}). Startup aborted.` + ); + } + + this.logger.info( + `Found ${migrationSteps.length} migration steps for ${registry.collectionName} ` + + `(v${minVersionInDb} -> v${registry.currentVersion})` + ); + + return migrationSteps; + } + + /** + * Executes a single migration step for a collection, applying the transform to all documents at the fromVersion. + * Handles marking the migration as started, completed, or failed in the migration repository. + * + * @param registry - The collection migration registry + * @param migrationStep - The specific migration step to execute + * @returns Number of documents migrated in this step + */ + protected async executeCollectionMigrationStep( + registry: CollectionMigrationRegistry, + migrationStep: SchemaMigrationStep + ): Promise { + const pendingBefore = await this.countDocumentsAtSchemaVersion(registry.model, migrationStep.fromVersion); + + if (pendingBefore === 0) { + this.logger.info(`Migration ${migrationStep.name} has no pending documents, skipping execution`); + return 0; + } + + const isCompleted = await this.migrationRepository.isCompleted(migrationStep.name); + + if (isCompleted) { + this.logger.warn( + `Migration ${migrationStep.name} is marked as completed but still has ${pendingBefore} pending ` + + `documents at schemaVersion ${migrationStep.fromVersion}. Re-running migration step.` + ); + } + + await this.migrationRepository.markAsStarted(migrationStep.name); + + try { + this.logger.info(`Executing migration: ${migrationStep.name}`); + const result = await this.runSchemaMigrationStep(migrationStep, registry.model); + const pendingAfter = await this.countDocumentsAtSchemaVersion(registry.model, migrationStep.fromVersion); + + const metadata: Record = { + collectionName: registry.collectionName, + fromVersion: migrationStep.fromVersion, + toVersion: migrationStep.toVersion, + migratedCount: result.migratedCount, + failedCount: result.failedCount, + pendingBefore, + pendingAfter, + durationMs: result.durationMs + }; + + if (result.failedCount > 0 || pendingAfter > 0) { + const failureReason = + `Migration ${migrationStep.name} did not complete successfully. ` + + `failedCount=${result.failedCount}, pendingAfter=${pendingAfter}`; + + await this.migrationRepository.markAsFailed(migrationStep.name, failureReason); + throw new Error(failureReason); + } + + await this.migrationRepository.markAsCompleted(migrationStep.name, metadata); + + this.logger.info( + `Migration ${migrationStep.name} completed: ${result.migratedCount} documents migrated (${result.durationMs}ms)` + ); + + return result.migratedCount; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + await this.migrationRepository.markAsFailed(migrationStep.name, errorMessage); + throw error; + } + } + + /** + * Executes a single schema migration step on all documents that match the fromVersion. + * Applies the transform function to each document and updates it to the toVersion. + * + * @param migrationStep - The migration step to execute + * @param model - Mongoose model for the collection being migrated + * @param batchSize - Number of documents to process in each batch. Default is 50. + * @returns Migration result with statistics about the execution + */ + protected async runSchemaMigrationStep( + migrationStep: SchemaMigrationStep, + model: Model, + batchSize = 50 + ): Promise { + const startTime = Date.now(); + let migratedCount = 0; + let failedCount = 0; + + const versionFilter = { schemaVersion: migrationStep.fromVersion }; + const totalDocs = await model.countDocuments(versionFilter).exec(); + + if (totalDocs === 0) { + return { + migratedCount, + failedCount, + durationMs: Date.now() - startTime + }; + } + + let processedCount = 0; + let lastProcessedId: TDocument['_id'] | null = null; + let hasMoreDocuments = true; + + while (hasMoreDocuments) { + const batchFilter = + lastProcessedId === null + ? versionFilter + : { + ...versionFilter, + _id: { $gt: lastProcessedId } + }; + const documents = await model.find(batchFilter).sort({ _id: 1 }).limit(batchSize).exec(); + + if (documents.length === 0) { + break; + } + + const batchResults = await Promise.allSettled( + documents.map(async (doc) => { + const transformedUpdate = migrationStep.transform(doc); + const update = this.appendSchemaVersionUpdate(transformedUpdate, migrationStep.toVersion); + await model.updateOne({ _id: doc._id }, update).exec(); + return String(doc._id); + }) + ); + + for (let i = 0; i < batchResults.length; i++) { + const batchResult = batchResults[i]; + + if (batchResult.status === 'fulfilled') { + migratedCount++; + continue; + } + + failedCount++; + this.logger.warn(`Failed to migrate document ${String(documents[i]._id)}:`, batchResult.reason); + } + + processedCount += documents.length; + lastProcessedId = documents[documents.length - 1]._id; + hasMoreDocuments = documents.length === batchSize; + this.logger.debug(`Processed ${processedCount}/${totalDocs} documents`); + } + + return { + migratedCount, + failedCount, + durationMs: Date.now() - startTime + }; + } + + /** + * Gets the minimum schema version present in the collection to detect the oldest pending version of documents. * * @param model - Mongoose model for the collection * @returns Current version or null if collection is empty */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected async getCurrentSchemaVersion(model: Model): Promise { + protected async getMinSchemaVersion( + model: Model + ): Promise { try { - // Get a sample document to check its version - const sampleDoc = await model.findOne({}).select('schemaVersion').exec(); + const sampleDoc = await model.findOne({}).sort({ schemaVersion: 1 }).select('schemaVersion').exec(); if (!sampleDoc) { return null; // Collection is empty @@ -232,39 +341,107 @@ export class MigrationService { } /** - * Finds the migrations needed to upgrade from one version to another. + * Gets the maximum schema version present in the collection to detect if there are any documents above expected version. + * + * @param model - Mongoose model for the collection + * @returns Maximum version or null if collection is empty + */ + protected async getMaxSchemaVersion( + model: Model + ): Promise { + try { + const sampleDoc = await model.findOne({}).sort({ schemaVersion: -1 }).select('schemaVersion').exec(); + + if (!sampleDoc) { + return null; // Collection is empty + } + + return sampleDoc.schemaVersion ?? 1; + } catch (error) { + this.logger.error('Error getting max schema version:', error); + throw error; + } + } + + /** + * Counts how many documents are at a specific schema version. + * + * @param model - Mongoose model for the collection + * @param schemaVersion - Schema version to count + * @returns Number of documents at the specified schema version + */ + protected async countDocumentsAtSchemaVersion( + model: Model, + schemaVersion: SchemaVersion + ): Promise { + return model.countDocuments({ schemaVersion }).exec(); + } + + /** + * Finds the schema migration steps needed to upgrade from one version to another. * Returns migrations in the correct order to apply. * * @param registry - Collection migration registry * @param fromVersion - Current version in database * @param toVersion - Target version from application - * @returns Array of migrations to execute in order + * @returns Array of schema migration steps to execute in order */ - protected findNeededMigrations( - registry: CollectionMigrationRegistry, - fromVersion: number, - toVersion: number - ): ISchemaMigration[] { - const needed: ISchemaMigration[] = []; + protected findSchemaMigrationSteps( + registry: CollectionMigrationRegistry, + fromVersion: SchemaVersion, + toVersion: SchemaVersion + ): SchemaMigrationStep[] { + const needed: SchemaMigrationStep[] = []; // Build a chain of migrations from fromVersion to toVersion let currentVersion = fromVersion; while (currentVersion < toVersion) { - const nextMigration = registry.migrations.find((m) => m.fromVersion === currentVersion); + const nextVersion = currentVersion + 1; + const expectedMigrationName = generateSchemaMigrationName( + registry.collectionName, + currentVersion, + nextVersion + ); + const transform = registry.migrations.get(expectedMigrationName); - if (!nextMigration) { - this.logger.warn( - `No migration found from version ${currentVersion} for ${registry.collectionName}. ` + - `Migration chain is incomplete.` + if (!transform) { + throw new Error( + `No migration found from version ${currentVersion} to ${nextVersion} for ` + + `${registry.collectionName}. Migration chain is incomplete.` ); - break; } - needed.push(nextMigration); - currentVersion = nextMigration.toVersion; + needed.push({ + name: expectedMigrationName, + fromVersion: currentVersion, + toVersion: nextVersion, + transform + }); + currentVersion = nextVersion; } return needed; } + + /** + * Appends a schemaVersion update to the migration update operation. + * Ensures that migrated documents are marked with the new version. + * + * @param update - Original migration update operation + * @param toVersion - Target schema version to set + * @returns Updated migration operation with schemaVersion set + */ + protected appendSchemaVersionUpdate( + update: MigrationUpdate, + toVersion: SchemaVersion + ): MigrationUpdate { + return { + ...update, + $set: { + ...(update.$set ?? {}), + schemaVersion: toVersion + } + }; + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e533e13..76bdc1bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -306,7 +306,7 @@ importers: devDependencies: '@angular-builders/custom-webpack': specifier: 20.0.0 - version: 20.0.0(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) + version: 20.0.0(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(less@4.5.1)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) '@angular-devkit/build-angular': specifier: 20.3.13 version: 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(tsx@4.20.6)(typescript@5.9.2) @@ -504,6 +504,359 @@ importers: specifier: 5.9.3 version: 5.9.3 + meet-pro/backend: + dependencies: + '@aws-sdk/client-s3': + specifier: 3.846.0 + version: 3.846.0 + '@azure/storage-blob': + specifier: 12.27.0 + version: 12.27.0 + '@google-cloud/storage': + specifier: 7.17.1 + version: 7.17.1(encoding@0.1.13) + '@openvidu-meet-pro/typings': + specifier: workspace:* + version: link:../typings + '@openvidu-meet/backend': + specifier: workspace:* + version: link:../../meet-ce/backend + '@sesamecare-oss/redlock': + specifier: 1.4.0 + version: 1.4.0(ioredis@5.6.1) + archiver: + specifier: 7.0.1 + version: 7.0.1 + bcrypt: + specifier: 5.1.1 + version: 5.1.1(encoding@0.1.13) + body-parser: + specifier: 2.2.0 + version: 2.2.0 + chalk: + specifier: 5.6.2 + version: 5.6.2 + cookie-parser: + specifier: 1.4.7 + version: 1.4.7 + cors: + specifier: 2.8.5 + version: 2.8.5 + cron: + specifier: 4.3.3 + version: 4.3.3 + dotenv: + specifier: 16.6.1 + version: 16.6.1 + express: + specifier: 4.21.2 + version: 4.21.2 + express-rate-limit: + specifier: 7.5.1 + version: 7.5.1(express@4.21.2) + inversify: + specifier: 6.2.2 + version: 6.2.2(reflect-metadata@0.2.2) + ioredis: + specifier: 5.6.1 + version: 5.6.1 + jwt-decode: + specifier: 4.0.0 + version: 4.0.0 + livekit-server-sdk: + specifier: 2.13.1 + version: 2.13.1 + ms: + specifier: 2.1.3 + version: 2.1.3 + uid: + specifier: 2.0.2 + version: 2.0.2 + winston: + specifier: 3.18.3 + version: 3.18.3 + yamljs: + specifier: 0.3.0 + version: 0.3.0 + zod: + specifier: 3.25.76 + version: 3.25.76 + devDependencies: + '@types/archiver': + specifier: 6.0.3 + version: 6.0.3 + '@types/bcrypt': + specifier: 5.0.2 + version: 5.0.2 + '@types/cookie-parser': + specifier: 1.4.9 + version: 1.4.9(@types/express@4.17.23) + '@types/cors': + specifier: 2.8.19 + version: 2.8.19 + '@types/express': + specifier: 4.17.23 + version: 4.17.23 + '@types/jest': + specifier: 29.5.14 + version: 29.5.14 + '@types/ms': + specifier: 2.1.0 + version: 2.1.0 + '@types/node': + specifier: 22.16.4 + version: 22.16.4 + '@types/supertest': + specifier: 6.0.3 + version: 6.0.3 + '@types/unzipper': + specifier: 0.10.11 + version: 0.10.11 + '@types/validator': + specifier: 13.15.2 + version: 13.15.2 + '@types/yamljs': + specifier: 0.2.34 + version: 0.2.34 + '@typescript-eslint/eslint-plugin': + specifier: 6.21.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': + specifier: 6.21.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.2) + cross-env: + specifier: 7.0.3 + version: 7.0.3 + eslint: + specifier: 8.57.1 + version: 8.57.1 + eslint-config-prettier: + specifier: 9.1.0 + version: 9.1.0(eslint@8.57.1) + jest: + specifier: 29.7.0 + version: 29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + jest-fetch-mock: + specifier: 3.0.3 + version: 3.0.3(encoding@0.1.13) + jest-junit: + specifier: 16.0.0 + version: 16.0.0 + nodemon: + specifier: 3.1.10 + version: 3.1.10 + openapi-generate-html: + specifier: 0.5.3 + version: 0.5.3(@types/node@22.16.4) + prettier: + specifier: 3.6.2 + version: 3.6.2 + supertest: + specifier: 7.1.3 + version: 7.1.3 + ts-jest: + specifier: 29.4.0 + version: 29.4.0(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)))(typescript@5.9.2) + ts-jest-resolver: + specifier: 2.0.1 + version: 2.0.1 + tsx: + specifier: 4.20.3 + version: 4.20.3 + typescript: + specifier: 5.9.2 + version: 5.9.2 + unzipper: + specifier: 0.12.3 + version: 0.12.3 + + meet-pro/frontend: + dependencies: + '@angular/animations': + specifier: 20.3.15 + version: 20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/cdk': + specifier: 20.2.14 + version: 20.2.14(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/common': + specifier: 20.3.15 + version: 20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: 20.3.15 + version: 20.3.15 + '@angular/core': + specifier: 20.3.15 + version: 20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/forms': + specifier: 20.3.15 + version: 20.3.15(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/material': + specifier: 20.2.14 + version: 20.2.14(2a2c27b5f9f3f8b334e74e3c717f4ace) + '@angular/platform-browser': + specifier: 20.3.15 + version: 20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/platform-browser-dynamic': + specifier: 20.3.15 + version: 20.3.15(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))) + '@angular/router': + specifier: 20.3.15 + version: 20.3.15(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@livekit/track-processors': + specifier: 0.7.0 + version: 0.7.0(@types/dom-mediacapture-transform@0.1.11)(livekit-client@2.16.1(@types/dom-mediacapture-record@1.0.22)) + '@openvidu-meet/shared-components': + specifier: workspace:* + version: link:../../meet-ce/frontend/projects/shared-meet-components + '@openvidu-meet/typings': + specifier: workspace:* + version: link:../../meet-ce/typings + autolinker: + specifier: 4.1.5 + version: 4.1.5 + core-js: + specifier: 3.45.1 + version: 3.45.1 + jwt-decode: + specifier: 4.0.0 + version: 4.0.0 + livekit-client: + specifier: 2.16.1 + version: 2.16.1(@types/dom-mediacapture-record@1.0.22) + openvidu-components-angular: + specifier: workspace:* + version: link:../../../openvidu/openvidu-components-angular/projects/openvidu-components-angular + rxjs: + specifier: 7.8.2 + version: 7.8.2 + tslib: + specifier: 2.8.1 + version: 2.8.1 + unique-names-generator: + specifier: 4.7.1 + version: 4.7.1 + zone.js: + specifier: 0.15.1 + version: 0.15.1 + devDependencies: + '@angular-builders/custom-webpack': + specifier: 20.0.0 + version: 20.0.0(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.43.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) + '@angular-devkit/build-angular': + specifier: 20.3.13 + version: 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(tsx@4.20.6)(typescript@5.9.2) + '@angular-eslint/builder': + specifier: 20.3.0 + version: 20.3.0(chokidar@4.0.3)(eslint@8.57.1)(typescript@5.9.2) + '@angular-eslint/eslint-plugin': + specifier: 20.3.0 + version: 20.3.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@angular-eslint/eslint-plugin-template': + specifier: 20.3.0 + version: 20.3.0(@angular-eslint/template-parser@20.3.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@angular-eslint/schematics': + specifier: 20.3.0 + version: 20.3.0(@angular-eslint/template-parser@20.3.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(chokidar@4.0.3)(eslint@8.57.1)(typescript@5.9.2) + '@angular-eslint/template-parser': + specifier: 20.3.0 + version: 20.3.0(eslint@8.57.1)(typescript@5.9.2) + '@angular/cli': + specifier: 20.3.13 + version: 20.3.13(@types/node@22.18.13)(chokidar@4.0.3) + '@angular/compiler-cli': + specifier: 20.3.15 + version: 20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2) + '@types/chai': + specifier: 4.3.20 + version: 4.3.20 + '@types/fluent-ffmpeg': + specifier: 2.1.27 + version: 2.1.27 + '@types/jasmine': + specifier: 5.1.9 + version: 5.1.9 + '@types/mocha': + specifier: 9.1.1 + version: 9.1.1 + '@types/node': + specifier: 22.18.13 + version: 22.18.13 + '@types/pixelmatch': + specifier: 5.2.6 + version: 5.2.6 + '@types/pngjs': + specifier: 6.0.5 + version: 6.0.5 + '@typescript-eslint/eslint-plugin': + specifier: 8.46.4 + version: 8.46.4(@typescript-eslint/parser@8.46.4(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': + specifier: 8.46.4 + version: 8.46.4(eslint@8.57.1)(typescript@5.9.2) + chai: + specifier: 4.5.0 + version: 4.5.0 + chromedriver: + specifier: 141.0.6 + version: 141.0.6 + cross-env: + specifier: 7.0.3 + version: 7.0.3 + eslint: + specifier: 8.57.1 + version: 8.57.1 + eslint-config-prettier: + specifier: 9.1.2 + version: 9.1.2(eslint@8.57.1) + fluent-ffmpeg: + specifier: 2.1.3 + version: 2.1.3 + jasmine-core: + specifier: 5.6.0 + version: 5.6.0 + jasmine-spec-reporter: + specifier: 7.0.0 + version: 7.0.0 + karma: + specifier: 6.4.4 + version: 6.4.4 + karma-chrome-launcher: + specifier: 3.2.0 + version: 3.2.0 + karma-coverage: + specifier: 2.2.1 + version: 2.2.1 + karma-jasmine: + specifier: 5.1.0 + version: 5.1.0(karma@6.4.4) + karma-jasmine-html-reporter: + specifier: 2.1.0 + version: 2.1.0(jasmine-core@5.6.0)(karma-jasmine@5.1.0(karma@6.4.4))(karma@6.4.4) + mocha: + specifier: 10.7.3 + version: 10.7.3 + ng-packagr: + specifier: 20.3.2 + version: 20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2) + prettier: + specifier: 3.3.3 + version: 3.3.3 + ts-node: + specifier: 10.9.2 + version: 10.9.2(@types/node@22.18.13)(typescript@5.9.2) + typescript: + specifier: 5.9.2 + version: 5.9.2 + + meet-pro/typings: + devDependencies: + '@openvidu-meet/typings': + specifier: workspace:* + version: link:../../meet-ce/typings + typescript: + specifier: 5.9.2 + version: 5.9.2 + testapp: dependencies: '@openvidu-meet/typings': @@ -696,15 +1049,33 @@ packages: resolution: {integrity: sha512-hdMKY4rUTko8xqeWYGnwwDYDomkeOoLsYsP6SdaHWK7hpGvzWsT6Q/aIv8J8NrCYkLu+M+5nLiKOooweUZu3GQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-eslint/builder@20.3.0': + resolution: {integrity: sha512-3XpWLdh+/K4+r0ChkKW00SXWyBA7ShMpE+Pt1XUmIu4srJgGRnt8e+kC4Syi+s2t5QS7PjlwRaelB1KfSMXZ5A==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + '@angular-eslint/builder@20.7.0': resolution: {integrity: sha512-qgf4Cfs1z0VsVpzF/OnxDRvBp60OIzeCsp4mzlckWYVniKo19EPIN6kFDol5eTAIOMPgiBQlMIwgQMHgocXEig==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '*' + '@angular-eslint/bundled-angular-compiler@20.3.0': + resolution: {integrity: sha512-QwuNnmRNr/uNj89TxknPbGcs5snX1w7RoJJPNAsfb2QGcHzUTQovS8hqm9kaDZdpUJDPP7jt7B6F0+EjrPAXRA==} + '@angular-eslint/bundled-angular-compiler@20.7.0': resolution: {integrity: sha512-9KPz24YoiL0SvTtTX6sd1zmysU5cKOCcmpEiXkCoO3L2oYZGlVxmMT4hfSaHMt8qmfvV2KzQMoR6DZM84BwRzQ==} + '@angular-eslint/eslint-plugin-template@20.3.0': + resolution: {integrity: sha512-WMJDJfybOLCiN4QrOyrLl+Zt5F+A/xoDYMWTdn+LgACheLs2tguVQiwf+oCgHnHGcsTsulPYlRHldKBGZMgs4w==} + peerDependencies: + '@angular-eslint/template-parser': 20.3.0 + '@typescript-eslint/types': ^7.11.0 || ^8.0.0 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + '@angular-eslint/eslint-plugin-template@20.7.0': resolution: {integrity: sha512-WFmvW2vBR6ExsSKEaActQTteyw6ikWyuJau9XmWEPFd+2eusEt/+wO21ybjDn3uc5FTp1IcdhfYy+U5OdDjH5w==} peerDependencies: @@ -714,6 +1085,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '*' + '@angular-eslint/eslint-plugin@20.3.0': + resolution: {integrity: sha512-7ghzGTiExrgTetDQ6IPP5uXSa94Xhtzp2VHCIa58EcUb7oMv06HWZ1Uss3xgFmACsLpN+vayKJIdFiboqaGVRA==} + peerDependencies: + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + '@angular-eslint/eslint-plugin@20.7.0': resolution: {integrity: sha512-aHH2YTiaonojsKN+y2z4IMugCwdsH/dYIjYBig6kfoSPyf9rGK4zx+gnNGq/pGRjF3bOYrmFgIviYpQVb80inQ==} peerDependencies: @@ -721,15 +1099,31 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '*' + '@angular-eslint/schematics@20.3.0': + resolution: {integrity: sha512-4n92tHKIJm1PP+FjhnmO7AMpvKdRIoF+YgF38oUU7aMJqfZ3RXIhazMMxw2u3VU1MisKH766KSll++c4LgarVA==} + '@angular-eslint/schematics@20.7.0': resolution: {integrity: sha512-S0onfRipDUIL6gFGTFjiWwUDhi42XYrBoi3kJ3wBbKBeIgYv9SP1ppTKDD4ZoDaDU9cQE8nToX7iPn9ifMw6eQ==} + '@angular-eslint/template-parser@20.3.0': + resolution: {integrity: sha512-gB564h/kZ7siWvgHDETU++sk5e25qFfVaizLaa6KoBEYFP6dOCiedz15LTcA0TsXp0rGu6Z6zkl291iSM1qzDA==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + '@angular-eslint/template-parser@20.7.0': resolution: {integrity: sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '*' + '@angular-eslint/utils@20.3.0': + resolution: {integrity: sha512-7XOQeNXgyhznDwoP1TwPrCMq/uXKJHQgCVPFREkJGKbNf/jzNldB7iV1eqpBzUQIPEQFgfcDG67dexpMAq3N4g==} + peerDependencies: + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + '@angular-eslint/utils@20.7.0': resolution: {integrity: sha512-B6EJHbsk2W/lnS3kS/gm56VGvX735419z/DzgbRDcOvqMGMLwD1ILzv5OTEcL1rzpnB0AHW+IxOu6y/aCzSNUA==} peerDependencies: @@ -2052,6 +2446,10 @@ packages: resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==} engines: {node: '>=14'} + '@google-cloud/storage@7.17.1': + resolution: {integrity: sha512-2FMQbpU7qK+OtBPaegC6n+XevgZksobUGo6mGKnXNmeZpvLiAo1gTAE3oTKsrMGDV4VtL8Zzpono0YsK/Q7Iqg==} + engines: {node: '>=14'} + '@google-cloud/storage@7.17.3': resolution: {integrity: sha512-gOnCAbFgAYKRozywLsxagdevTF7Gm+2Ncz5u5CQAuOv/2VCa0rdGJWvJFDOftPx1tc+q8TXiC2pEJfFKu+yeMQ==} engines: {node: '>=14'} @@ -3351,6 +3749,9 @@ packages: resolution: {integrity: sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==} engines: {node: ^18.17.0 || >=20.5.0} + '@types/archiver@6.0.3': + resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==} + '@types/archiver@6.0.4': resolution: {integrity: sha512-ULdQpARQ3sz9WH4nb98mJDYA0ft2A8C4f4fovvUcFwINa1cgGjY36JCAYuP5YypRq4mco1lJp1/7jEMS2oR0Hg==} @@ -3425,12 +3826,18 @@ packages: '@types/express-serve-static-core@5.1.0': resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} '@types/express@5.0.6': resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/fluent-ffmpeg@2.1.27': + resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==} + '@types/fluent-ffmpeg@2.1.28': resolution: {integrity: sha512-5ovxsDwBcPfJ+eYs1I/ZpcYCnkce7pvH9AHSvrZllAp1ZPpTRDZAFjF3TRFbukxSgIYTTNYePbS0rKUmaxVbXw==} @@ -3455,6 +3862,9 @@ packages: '@types/jasmine@5.1.13': resolution: {integrity: sha512-MYCcDkruFc92LeYZux5BC0dmqo2jk+M5UIZ4/oFnAPCXN9mCcQhLyj7F3/Za7rocVyt5YRr1MmqJqFlvQ9LVcg==} + '@types/jasmine@5.1.9': + resolution: {integrity: sha512-8t4HtkW4wxiPVedMpeZ63n3vlWxEIquo/zc1Tm8ElU+SqVV7+D3Na2PWaJUp179AzTragMWVwkMv7mvty0NfyQ==} + '@types/jest@29.5.14': resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} @@ -3497,6 +3907,9 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} + '@types/node@22.16.4': + resolution: {integrity: sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g==} + '@types/node@22.16.5': resolution: {integrity: sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==} @@ -4606,6 +5019,9 @@ packages: core-js-compat@3.47.0: resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + core-js@3.45.1: + resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==} + core-js@3.47.0: resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} @@ -4642,6 +5058,10 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cron@4.3.3: + resolution: {integrity: sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==} + engines: {node: '>=18.x'} + cron@4.3.5: resolution: {integrity: sha512-hKPP7fq1+OfyCqoePkKfVq7tNAdFwiQORr4lZUHwrf0tebC65fYEeWgOrXOL6prn1/fegGOdTfrM6e34PJfksg==} engines: {node: '>=18.x'} @@ -5160,6 +5580,12 @@ packages: peerDependencies: eslint: '>=6.0.0' + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-config-prettier@9.1.2: resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} hasBin: true @@ -5235,6 +5661,10 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.0.0: resolution: {integrity: sha512-+Yh0LeQKq+mW/tQArNj67tljR3L1HajDTQPuZOEwC00oBdoIDQrr89yBgjAlzAwRrY/5zDkM3v99iGHwz9y0dw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -5646,16 +6076,17 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -6584,6 +7015,10 @@ packages: peerDependencies: '@types/dom-mediacapture-record': ^1 + livekit-server-sdk@2.13.1: + resolution: {integrity: sha512-k4qFvqjHUR0s9lMMueZ1CMDLw/IngOmL/wsh/zq0+6bIg3rMzns9s3ECOf7XuT56esEuu8LGlrw0+inL86QiqQ==} + engines: {node: '>=18'} + livekit-server-sdk@2.13.3: resolution: {integrity: sha512-ItSQ2gE1oz/Ev9mfBRdAw+P05rt/BaYRkldggKz0+3rh/Yt0ag0BLID3VrgCVFVRAQ2YEJKcJJyj5p4epIJ8QA==} engines: {node: '>=18'} @@ -8428,10 +8863,12 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.2: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tcp-port-used@1.0.2: resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==} @@ -8651,6 +9088,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + tsx@4.20.6: resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} engines: {node: '>=18.0.0'} @@ -9298,13 +9740,66 @@ snapshots: - chokidar - typescript - '@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2)': + '@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.43.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2)': dependencies: '@angular-builders/common': 4.0.0(@types/node@22.18.13)(chokidar@4.0.3)(typescript@5.9.2) '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) '@angular-devkit/build-angular': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(tsx@4.20.6)(typescript@5.9.2) '@angular-devkit/core': 20.3.13(chokidar@4.0.3) - '@angular/build': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) + '@angular/build': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.43.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) + '@angular/compiler-cli': 20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2) + lodash: 4.17.21 + webpack-merge: 6.0.1 + transitivePeerDependencies: + - '@angular/compiler' + - '@angular/core' + - '@angular/localize' + - '@angular/platform-browser' + - '@angular/platform-server' + - '@angular/service-worker' + - '@angular/ssr' + - '@rspack/core' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - '@web/test-runner' + - browser-sync + - bufferutil + - chokidar + - debug + - html-webpack-plugin + - jest + - jest-environment-jsdom + - jiti + - karma + - less + - lightningcss + - ng-packagr + - node-sass + - postcss + - protractor + - sass-embedded + - stylus + - sugarss + - supports-color + - tailwindcss + - terser + - tslib + - tsx + - typescript + - uglify-js + - utf-8-validate + - vitest + - webpack-cli + - yaml + + '@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(less@4.5.1)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2)': + dependencies: + '@angular-builders/common': 4.0.0(@types/node@22.18.13)(chokidar@4.0.3)(typescript@5.9.2) + '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) + '@angular-devkit/build-angular': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(browser-sync@3.0.4)(chokidar@4.0.3)(jest-environment-jsdom@29.7.0)(jest@29.7.0(@types/node@22.18.13)(ts-node@10.9.2(@types/node@22.18.13)(typescript@5.9.2)))(jiti@1.21.7)(karma@6.4.4)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(tsx@4.20.6)(typescript@5.9.2) + '@angular-devkit/core': 20.3.13(chokidar@4.0.3) + '@angular/build': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.5.1)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) '@angular/compiler-cli': 20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2) lodash: 4.17.21 webpack-merge: 6.0.1 @@ -9362,7 +9857,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.2003.13(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2))(webpack@5.101.2) + '@angular-devkit/build-webpack': 0.2003.13(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)))(webpack@5.101.2(esbuild@0.25.9)) '@angular-devkit/core': 20.3.13(chokidar@4.0.3) '@angular/build': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.43.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2) '@angular/compiler-cli': 20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2) @@ -9376,13 +9871,13 @@ snapshots: '@babel/preset-env': 7.28.3(@babel/core@7.28.3) '@babel/runtime': 7.28.3 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.101.2) + '@ngtools/webpack': 20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.101.2(esbuild@0.25.9)) ansi-colors: 4.1.3 autoprefixer: 10.4.21(postcss@8.5.6) - babel-loader: 10.0.0(@babel/core@7.28.3)(webpack@5.101.2) + babel-loader: 10.0.0(@babel/core@7.28.3)(webpack@5.101.2(esbuild@0.25.9)) browserslist: 4.28.1 - copy-webpack-plugin: 13.0.1(webpack@5.101.2) - css-loader: 7.1.2(webpack@5.101.2) + copy-webpack-plugin: 13.0.1(webpack@5.101.2(esbuild@0.25.9)) + css-loader: 7.1.2(webpack@5.101.2(esbuild@0.25.9)) esbuild-wasm: 0.25.9 fast-glob: 3.3.3 http-proxy-middleware: 3.0.5 @@ -9390,32 +9885,32 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.4.0 - less-loader: 12.3.0(less@4.4.0)(webpack@5.101.2) - license-webpack-plugin: 4.0.2(webpack@5.101.2) + less-loader: 12.3.0(less@4.4.0)(webpack@5.101.2(esbuild@0.25.9)) + license-webpack-plugin: 4.0.2(webpack@5.101.2(esbuild@0.25.9)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.9.4(webpack@5.101.2) + mini-css-extract-plugin: 2.9.4(webpack@5.101.2(esbuild@0.25.9)) open: 10.2.0 ora: 8.2.0 picomatch: 4.0.3 piscina: 5.1.3 postcss: 8.5.6 - postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.2) + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.2(esbuild@0.25.9)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 sass: 1.90.0 - sass-loader: 16.0.5(sass@1.90.0)(webpack@5.101.2) + sass-loader: 16.0.5(sass@1.90.0)(webpack@5.101.2(esbuild@0.25.9)) semver: 7.7.2 - source-map-loader: 5.0.0(webpack@5.101.2) + source-map-loader: 5.0.0(webpack@5.101.2(esbuild@0.25.9)) source-map-support: 0.5.21 terser: 5.43.1 tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.9.2 webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-middleware: 7.4.2(webpack@5.101.2) - webpack-dev-server: 5.2.2(webpack@5.101.2) + webpack-dev-middleware: 7.4.2(webpack@5.101.2(esbuild@0.25.9)) + webpack-dev-server: 5.2.2(webpack@5.101.2(esbuild@0.25.9)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.101.2) + webpack-subresource-integrity: 5.1.0(webpack@5.101.2(esbuild@0.25.9)) optionalDependencies: '@angular/core': 20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)) @@ -9448,12 +9943,12 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.2003.13(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2))(webpack@5.101.2)': + '@angular-devkit/build-webpack@0.2003.13(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)))(webpack@5.101.2(esbuild@0.25.9))': dependencies: '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) rxjs: 7.8.2 webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-server: 5.2.2(webpack@5.101.2) + webpack-dev-server: 5.2.2(webpack@5.101.2(esbuild@0.25.9)) transitivePeerDependencies: - chokidar @@ -9478,6 +9973,15 @@ snapshots: transitivePeerDependencies: - chokidar + '@angular-eslint/builder@20.3.0(chokidar@4.0.3)(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) + '@angular-devkit/core': 20.3.13(chokidar@4.0.3) + eslint: 8.57.1 + typescript: 5.9.2 + transitivePeerDependencies: + - chokidar + '@angular-eslint/builder@20.7.0(chokidar@4.0.3)(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) @@ -9487,8 +9991,22 @@ snapshots: transitivePeerDependencies: - chokidar + '@angular-eslint/bundled-angular-compiler@20.3.0': {} + '@angular-eslint/bundled-angular-compiler@20.7.0': {} + '@angular-eslint/eslint-plugin-template@20.3.0(@angular-eslint/template-parser@20.3.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 20.3.0 + '@angular-eslint/template-parser': 20.3.0(eslint@8.57.1)(typescript@5.9.2) + '@angular-eslint/utils': 20.3.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/utils': 8.50.0(eslint@8.57.1)(typescript@5.9.2) + aria-query: 5.3.2 + axobject-query: 4.1.0 + eslint: 8.57.1 + typescript: 5.9.2 + '@angular-eslint/eslint-plugin-template@20.7.0(@angular-eslint/template-parser@20.7.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 @@ -9501,6 +10019,15 @@ snapshots: eslint: 8.57.1 typescript: 5.9.2 + '@angular-eslint/eslint-plugin@20.3.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 20.3.0 + '@angular-eslint/utils': 20.3.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.50.0(eslint@8.57.1)(typescript@5.9.2) + eslint: 8.57.1 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + '@angular-eslint/eslint-plugin@20.7.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 @@ -9510,6 +10037,23 @@ snapshots: ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 + '@angular-eslint/schematics@20.3.0(@angular-eslint/template-parser@20.3.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(chokidar@4.0.3)(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@angular-devkit/core': 20.3.13(chokidar@4.0.3) + '@angular-devkit/schematics': 20.3.13(chokidar@4.0.3) + '@angular-eslint/eslint-plugin': 20.3.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@angular-eslint/eslint-plugin-template': 20.3.0(@angular-eslint/template-parser@20.3.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + ignore: 7.0.5 + semver: 7.7.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@angular-eslint/template-parser' + - '@typescript-eslint/types' + - '@typescript-eslint/utils' + - chokidar + - eslint + - typescript + '@angular-eslint/schematics@20.7.0(@angular-eslint/template-parser@20.7.0(eslint@8.57.1)(typescript@5.9.2))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(chokidar@4.0.3)(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@angular-devkit/core': 20.3.13(chokidar@4.0.3) @@ -9527,6 +10071,13 @@ snapshots: - eslint - typescript + '@angular-eslint/template-parser@20.3.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 20.3.0 + eslint: 8.57.1 + eslint-scope: 8.4.0 + typescript: 5.9.2 + '@angular-eslint/template-parser@20.7.0(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 @@ -9534,6 +10085,13 @@ snapshots: eslint-scope: 9.0.0 typescript: 5.9.2 + '@angular-eslint/utils@20.3.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 20.3.0 + '@typescript-eslint/utils': 8.50.0(eslint@8.57.1)(typescript@5.9.2) + eslint: 8.57.1 + typescript: 5.9.2 + '@angular-eslint/utils@20.7.0(@typescript-eslint/utils@8.50.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': dependencies: '@angular-eslint/bundled-angular-compiler': 20.7.0 @@ -9556,7 +10114,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.14(@types/node@22.18.13) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6)) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.6)) beasties: 0.3.5 browserslist: 4.28.1 esbuild: 0.25.9 @@ -9599,7 +10157,7 @@ snapshots: - tsx - yaml - '@angular/build@20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.4.0)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2)': + '@angular/build@20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(@angular/compiler@20.3.15)(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@22.18.13)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.5.1)(ng-packagr@20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2))(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(tsx@4.20.6)(typescript@5.9.2)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.13(chokidar@4.0.3) @@ -9609,7 +10167,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.14(@types/node@22.18.13) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6)) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.5.1)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6)) beasties: 0.3.5 browserslist: 4.28.1 esbuild: 0.25.9 @@ -9629,13 +10187,13 @@ snapshots: tinyglobby: 0.2.14 tslib: 2.8.1 typescript: 5.9.2 - vite: 7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6) + vite: 7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.5.1)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6) watchpack: 2.4.4 optionalDependencies: '@angular/core': 20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1) '@angular/platform-browser': 20.3.15(@angular/animations@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)) karma: 6.4.4 - less: 4.4.0 + less: 4.5.1 lmdb: 3.4.2 ng-packagr: 20.3.2(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(tslib@2.8.1)(typescript@5.9.2) postcss: 8.5.6 @@ -10344,7 +10902,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) '@babel/helpers': 7.28.4 @@ -11332,6 +11890,27 @@ snapshots: '@google-cloud/promisify@4.0.0': {} + '@google-cloud/storage@7.17.1(encoding@0.1.13)': + dependencies: + '@google-cloud/paginator': 5.0.2 + '@google-cloud/projectify': 4.0.0 + '@google-cloud/promisify': 4.0.0 + abort-controller: 3.0.0 + async-retry: 1.3.3 + duplexify: 4.1.3 + fast-xml-parser: 4.5.3 + gaxios: 6.7.1(encoding@0.1.13) + google-auth-library: 9.15.1(encoding@0.1.13) + html-entities: 2.6.0 + mime: 3.0.0 + p-limit: 3.1.0 + retry-request: 7.0.2(encoding@0.1.13) + teeny-request: 9.0.0(encoding@0.1.13) + uuid: 8.3.2 + transitivePeerDependencies: + - encoding + - supports-color + '@google-cloud/storage@7.17.3(encoding@0.1.13)': dependencies: '@google-cloud/paginator': 5.0.2 @@ -11383,6 +11962,16 @@ snapshots: '@inquirer/ansi@1.0.2': {} + '@inquirer/checkbox@4.3.2(@types/node@22.16.4)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.16.4) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/checkbox@4.3.2(@types/node@22.16.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -11410,6 +11999,13 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/confirm@5.1.21(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/confirm@5.1.21(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11424,6 +12020,19 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/core@10.3.2(@types/node@22.16.4)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.16.4) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/core@10.3.2(@types/node@22.16.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -11450,6 +12059,14 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/editor@4.2.23(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/external-editor': 1.0.3(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/editor@4.2.23(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11466,6 +12083,14 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/expand@4.0.23(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/expand@4.0.23(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11482,6 +12107,13 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/external-editor@1.0.3(@types/node@22.16.4)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.1 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/external-editor@1.0.3(@types/node@22.16.5)': dependencies: chardet: 2.1.1 @@ -11498,6 +12130,13 @@ snapshots: '@inquirer/figures@1.0.15': {} + '@inquirer/input@4.3.1(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/input@4.3.1(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11512,6 +12151,13 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/number@3.0.23(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/number@3.0.23(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11526,6 +12172,14 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/password@4.0.23(@types/node@22.16.4)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/password@4.0.23(@types/node@22.16.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -11542,6 +12196,21 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/prompts@7.10.1(@types/node@22.16.4)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.16.4) + '@inquirer/confirm': 5.1.21(@types/node@22.16.4) + '@inquirer/editor': 4.2.23(@types/node@22.16.4) + '@inquirer/expand': 4.0.23(@types/node@22.16.4) + '@inquirer/input': 4.3.1(@types/node@22.16.4) + '@inquirer/number': 3.0.23(@types/node@22.16.4) + '@inquirer/password': 4.0.23(@types/node@22.16.4) + '@inquirer/rawlist': 4.1.11(@types/node@22.16.4) + '@inquirer/search': 3.2.2(@types/node@22.16.4) + '@inquirer/select': 4.4.2(@types/node@22.16.4) + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/prompts@7.10.1(@types/node@22.16.5)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@22.16.5) @@ -11572,6 +12241,14 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/rawlist@4.1.11(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/rawlist@4.1.11(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11588,6 +12265,15 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/search@3.2.2(@types/node@22.16.4)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.16.4) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/search@3.2.2(@types/node@22.16.5)': dependencies: '@inquirer/core': 10.3.2(@types/node@22.16.5) @@ -11606,6 +12292,16 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/select@4.4.2(@types/node@22.16.4)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.16.4) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/select@4.4.2(@types/node@22.16.5)': dependencies: '@inquirer/ansi': 1.0.2 @@ -11626,6 +12322,10 @@ snapshots: optionalDependencies: '@types/node': 22.18.13 + '@inquirer/type@3.0.10(@types/node@22.16.4)': + optionalDependencies: + '@types/node': 22.16.4 + '@inquirer/type@3.0.10(@types/node@22.16.5)': optionalDependencies: '@types/node': 22.16.5 @@ -11687,6 +12387,41 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.3 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.19.3)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2))': dependencies: '@jest/console': 29.7.0 @@ -12155,7 +12890,7 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true - '@ngtools/webpack@20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.101.2)': + '@ngtools/webpack@20.3.13(@angular/compiler-cli@20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.101.2(esbuild@0.25.9))': dependencies: '@angular/compiler-cli': 20.3.15(@angular/compiler@20.3.15)(typescript@5.9.2) typescript: 5.9.2 @@ -12914,6 +13649,10 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 + '@types/archiver@6.0.3': + dependencies: + '@types/readdir-glob': 1.1.5 + '@types/archiver@6.0.4': dependencies: '@types/readdir-glob': 1.1.5 @@ -12958,13 +13697,17 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 4.19.7 + '@types/express-serve-static-core': 5.1.0 '@types/node': 22.19.3 '@types/connect@3.4.38': dependencies: '@types/node': 22.19.3 + '@types/cookie-parser@1.4.9(@types/express@4.17.23)': + dependencies: + '@types/express': 4.17.23 + '@types/cookie-parser@1.4.9(@types/express@4.17.25)': dependencies: '@types/express': 4.17.25 @@ -13011,6 +13754,13 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 1.2.1 + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.7 + '@types/qs': 6.14.0 + '@types/serve-static': 2.2.0 + '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 @@ -13024,6 +13774,10 @@ snapshots: '@types/express-serve-static-core': 5.1.0 '@types/serve-static': 2.2.0 + '@types/fluent-ffmpeg@2.1.27': + dependencies: + '@types/node': 22.19.3 + '@types/fluent-ffmpeg@2.1.28': dependencies: '@types/node': 22.19.3 @@ -13050,6 +13804,8 @@ snapshots: '@types/jasmine@5.1.13': {} + '@types/jasmine@5.1.9': {} + '@types/jest@29.5.14': dependencies: expect: 29.7.0 @@ -13091,6 +13847,10 @@ snapshots: dependencies: '@types/node': 22.19.3 + '@types/node@22.16.4': + dependencies: + undici-types: 6.21.0 + '@types/node@22.16.5': dependencies: undici-types: 6.21.0 @@ -13542,9 +14302,13 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.6))': dependencies: - vite: 7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6) + vite: 7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.43.1)(tsx@4.20.6) + + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.5.1)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6))': + dependencies: + vite: 7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.5.1)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6) '@webassemblyjs/ast@1.14.1': dependencies: @@ -13918,7 +14682,7 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@10.0.0(@babel/core@7.28.3)(webpack@5.101.2): + babel-loader@10.0.0(@babel/core@7.28.3)(webpack@5.101.2(esbuild@0.25.9)): dependencies: '@babel/core': 7.28.3 find-up: 5.0.0 @@ -14524,7 +15288,7 @@ snapshots: dependencies: is-what: 3.14.1 - copy-webpack-plugin@13.0.1(webpack@5.101.2): + copy-webpack-plugin@13.0.1(webpack@5.101.2(esbuild@0.25.9)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 @@ -14537,6 +15301,8 @@ snapshots: dependencies: browserslist: 4.28.1 + core-js@3.45.1: {} + core-js@3.47.0: {} core-util-is@1.0.3: {} @@ -14562,6 +15328,21 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.7.0 + create-jest@29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@22.16.5)(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)): dependencies: '@jest/types': 29.6.3 @@ -14610,6 +15391,11 @@ snapshots: create-require@1.1.1: {} + cron@4.3.3: + dependencies: + '@types/luxon': 3.7.1 + luxon: 3.7.2 + cron@4.3.5: dependencies: '@types/luxon': 3.7.1 @@ -14635,7 +15421,7 @@ snapshots: dependencies: postcss: 8.5.6 - css-loader@7.1.2(webpack@5.101.2): + css-loader@7.1.2(webpack@5.101.2(esbuild@0.25.9)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -14644,7 +15430,7 @@ snapshots: postcss-modules-scope: 3.2.1(postcss@8.5.6) postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 - semver: 7.7.2 + semver: 7.7.3 optionalDependencies: webpack: 5.101.2(esbuild@0.25.9) @@ -15227,6 +16013,10 @@ snapshots: eslint: 8.57.1 semver: 7.7.3 + eslint-config-prettier@9.1.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-config-prettier@9.1.2(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -15321,6 +16111,11 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-scope@9.0.0: dependencies: esrecurse: 4.3.0 @@ -16179,6 +16974,18 @@ snapshots: dependencies: tslib: 2.8.1 + inquirer@12.11.1(@types/node@22.16.4): + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.16.4) + '@inquirer/prompts': 7.10.1(@types/node@22.16.4) + '@inquirer/type': 3.0.10(@types/node@22.16.4) + mute-stream: 2.0.0 + run-async: 4.0.6 + rxjs: 7.8.2 + optionalDependencies: + '@types/node': 22.16.4 + inquirer@12.11.1(@types/node@22.16.5): dependencies: '@inquirer/ansi': 1.0.2 @@ -16439,11 +17246,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.3 + '@babel/core': 7.28.5 '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -16518,6 +17325,25 @@ snapshots: - babel-plugin-macros - supports-color + jest-cli@29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@22.16.5)(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)) @@ -16576,6 +17402,37 @@ snapshots: - supports-color - ts-node + jest-config@29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)): + dependencies: + '@babel/core': 7.28.5 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.5) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.16.4 + ts-node: 10.9.2(@types/node@22.16.4)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@29.7.0(@types/node@22.16.5)(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)): dependencies: '@babel/core': 7.28.5 @@ -16639,6 +17496,37 @@ snapshots: - supports-color optional: true + jest-config@29.7.0(@types/node@22.19.3)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)): + dependencies: + '@babel/core': 7.28.5 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.5) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.19.3 + ts-node: 10.9.2(@types/node@22.16.4)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@29.7.0(@types/node@22.19.3)(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)): dependencies: '@babel/core': 7.28.5 @@ -16983,6 +17871,18 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 + jest@29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@29.7.0(@types/node@22.16.5)(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)) @@ -17218,7 +18118,7 @@ snapshots: dependencies: readable-stream: 2.3.8 - less-loader@12.3.0(less@4.4.0)(webpack@5.101.2): + less-loader@12.3.0(less@4.4.0)(webpack@5.101.2(esbuild@0.25.9)): dependencies: less: 4.4.0 optionalDependencies: @@ -17259,7 +18159,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - license-webpack-plugin@4.0.2(webpack@5.101.2): + license-webpack-plugin@4.0.2(webpack@5.101.2(esbuild@0.25.9)): dependencies: webpack-sources: 3.3.3 optionalDependencies: @@ -17294,6 +18194,13 @@ snapshots: typed-emitter: 2.1.0 webrtc-adapter: 9.0.3 + livekit-server-sdk@2.13.1: + dependencies: + '@bufbuild/protobuf': 1.10.1 + '@livekit/protocol': 1.43.4 + camelcase-keys: 9.1.3 + jose: 5.10.0 + livekit-server-sdk@2.13.3: dependencies: '@bufbuild/protobuf': 1.10.1 @@ -17511,7 +18418,7 @@ snapshots: mimic-function@5.0.1: {} - mini-css-extract-plugin@2.9.4(webpack@5.101.2): + mini-css-extract-plugin@2.9.4(webpack@5.101.2(esbuild@0.25.9)): dependencies: schema-utils: 4.3.3 tapable: 2.3.0 @@ -17830,7 +18737,7 @@ snapshots: dependencies: hosted-git-info: 9.0.2 proc-log: 5.0.0 - semver: 7.7.2 + semver: 7.7.3 validate-npm-package-name: 6.0.2 npm-packlist@10.0.3: @@ -17945,6 +18852,17 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 + openapi-generate-html@0.5.3(@types/node@22.16.4): + dependencies: + '@apidevtools/json-schema-ref-parser': 13.0.5 + commander: 12.1.0 + ejs: 3.1.10 + fast-safe-stringify: 2.1.1 + inquirer: 12.11.1(@types/node@22.16.4) + js-yaml: 4.1.1 + transitivePeerDependencies: + - '@types/node' + openapi-generate-html@0.5.3(@types/node@22.16.5): dependencies: '@apidevtools/json-schema-ref-parser': 13.0.5 @@ -18221,12 +19139,12 @@ snapshots: postcss: 8.5.6 ts-node: 10.9.2(@types/node@22.19.3)(typescript@5.9.3) - postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.2): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@5.101.2(esbuild@0.25.9)): dependencies: cosmiconfig: 9.0.0(typescript@5.9.2) jiti: 1.21.7 postcss: 8.5.6 - semver: 7.7.2 + semver: 7.7.3 optionalDependencies: webpack: 5.101.2(esbuild@0.25.9) transitivePeerDependencies: @@ -18813,7 +19731,7 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@16.0.5(sass@1.90.0)(webpack@5.101.2): + sass-loader@16.0.5(sass@1.90.0)(webpack@5.101.2(esbuild@0.25.9)): dependencies: neo-async: 2.6.2 optionalDependencies: @@ -19136,7 +20054,7 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.101.2): + source-map-loader@5.0.0(webpack@5.101.2(esbuild@0.25.9)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 @@ -19435,7 +20353,7 @@ snapshots: - encoding - supports-color - terser-webpack-plugin@5.3.16(esbuild@0.25.9)(webpack@5.101.2): + terser-webpack-plugin@5.3.16(esbuild@0.25.9)(webpack@5.101.2(esbuild@0.25.9)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 @@ -19566,6 +20484,26 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.28.5) + ts-jest@29.4.0(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)))(typescript@5.9.2): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@22.16.4)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.3 + type-fest: 4.41.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.28.5 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.5) + jest-util: 29.7.0 + ts-jest@29.4.0(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.16.5)(ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2)))(typescript@5.9.2): dependencies: bs-logger: 0.2.6 @@ -19604,6 +20542,25 @@ snapshots: - '@swc/wasm' - '@types/node' + ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.16.4 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-node@10.9.2(@types/node@22.16.5)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -19681,6 +20638,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.20.3: + dependencies: + esbuild: 0.25.12 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + tsx@4.20.6: dependencies: esbuild: 0.25.12 @@ -19877,7 +20841,7 @@ snapshots: terser: 5.43.1 tsx: 4.20.6 - vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.4.0)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6): + vite@7.1.11(@types/node@22.18.13)(jiti@1.21.7)(less@4.5.1)(sass@1.90.0)(terser@5.44.1)(tsx@4.20.6): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -19889,7 +20853,7 @@ snapshots: '@types/node': 22.18.13 fsevents: 2.3.3 jiti: 1.21.7 - less: 4.4.0 + less: 4.5.1 sass: 1.90.0 terser: 5.44.1 tsx: 4.20.6 @@ -19930,7 +20894,7 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@7.4.2(webpack@5.101.2): + webpack-dev-middleware@7.4.2(webpack@5.101.2(esbuild@0.25.9)): dependencies: colorette: 2.0.20 memfs: 4.51.1 @@ -19941,7 +20905,7 @@ snapshots: optionalDependencies: webpack: 5.101.2(esbuild@0.25.9) - webpack-dev-server@5.2.2(webpack@5.101.2): + webpack-dev-server@5.2.2(webpack@5.101.2(esbuild@0.25.9)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -19969,7 +20933,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.101.2) + webpack-dev-middleware: 7.4.2(webpack@5.101.2(esbuild@0.25.9)) ws: 8.18.3 optionalDependencies: webpack: 5.101.2(esbuild@0.25.9) @@ -19987,7 +20951,7 @@ snapshots: webpack-sources@3.3.3: {} - webpack-subresource-integrity@5.1.0(webpack@5.101.2): + webpack-subresource-integrity@5.1.0(webpack@5.101.2(esbuild@0.25.9)): dependencies: typed-assert: 1.0.9 webpack: 5.101.2(esbuild@0.25.9) @@ -20016,7 +20980,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(esbuild@0.25.9)(webpack@5.101.2) + terser-webpack-plugin: 5.3.16(esbuild@0.25.9)(webpack@5.101.2(esbuild@0.25.9)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: From 396c23aa3c64423ea6b84ba3c65f5725ddee74a5 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 12:30:26 +0100 Subject: [PATCH 2/7] backend: remove unused repository injections from MigrationService --- meet-ce/backend/src/services/migration.service.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/meet-ce/backend/src/services/migration.service.ts b/meet-ce/backend/src/services/migration.service.ts index a0be8e75..5b192f4c 100644 --- a/meet-ce/backend/src/services/migration.service.ts +++ b/meet-ce/backend/src/services/migration.service.ts @@ -12,12 +12,7 @@ import { SchemaMigrationStep, SchemaVersion } from '../models/migration.model.js'; -import { ApiKeyRepository } from '../repositories/api-key.repository.js'; -import { GlobalConfigRepository } from '../repositories/global-config.repository.js'; import { MigrationRepository } from '../repositories/migration.repository.js'; -import { RecordingRepository } from '../repositories/recording.repository.js'; -import { RoomRepository } from '../repositories/room.repository.js'; -import { UserRepository } from '../repositories/user.repository.js'; import { LoggerService } from './logger.service.js'; import { MutexService } from './mutex.service.js'; @@ -26,11 +21,6 @@ export class MigrationService { constructor( @inject(LoggerService) protected logger: LoggerService, @inject(MutexService) protected mutexService: MutexService, - @inject(GlobalConfigRepository) protected configRepository: GlobalConfigRepository, - @inject(UserRepository) protected userRepository: UserRepository, - @inject(ApiKeyRepository) protected apiKeyRepository: ApiKeyRepository, - @inject(RoomRepository) protected roomRepository: RoomRepository, - @inject(RecordingRepository) protected recordingRepository: RecordingRepository, @inject(MigrationRepository) protected migrationRepository: MigrationRepository ) {} From 2a1575768f3bc07526dfb38d8a668aa439a57080 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 12:32:01 +0100 Subject: [PATCH 3/7] backend: use collection names in schema migration name generation --- meet-ce/backend/src/migrations/README.md | 5 ++--- meet-ce/backend/src/migrations/api-key-migrations.ts | 3 +-- meet-ce/backend/src/migrations/global-config-migrations.ts | 3 +-- meet-ce/backend/src/migrations/user-migrations.ts | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/meet-ce/backend/src/migrations/README.md b/meet-ce/backend/src/migrations/README.md index 5447feff..4580e468 100644 --- a/meet-ce/backend/src/migrations/README.md +++ b/meet-ce/backend/src/migrations/README.md @@ -103,10 +103,9 @@ const MeetRoomSchema = new Schema({ ```typescript import { SchemaTransform, generateSchemaMigrationName } from '../models/migration.model.js'; -import { MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; - -const roomMigrationV1ToV2Name = generateSchemaMigrationName('MeetRoom', 1, 2); +import { meetRoomCollectionName, MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; +const roomMigrationV1ToV2Name = generateSchemaMigrationName(meetRoomCollectionName, 1, 2); const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ $set: { maxParticipants: 100 diff --git a/meet-ce/backend/src/migrations/api-key-migrations.ts b/meet-ce/backend/src/migrations/api-key-migrations.ts index 3b0db16b..de798080 100644 --- a/meet-ce/backend/src/migrations/api-key-migrations.ts +++ b/meet-ce/backend/src/migrations/api-key-migrations.ts @@ -7,8 +7,7 @@ import { MeetApiKeyDocument } from '../models/mongoose-schemas/api-key.schema.js * * Example: * - * const apiKeyMigrationV1ToV2Name = generateSchemaMigrationName('MeetApiKey', 1, 2); - * + * const apiKeyMigrationV1ToV2Name = generateSchemaMigrationName(meetApiKeyCollectionName, 1, 2); * const apiKeyMigrationV1ToV2Transform: SchemaTransform = () => ({ * $set: { * expirationDate: undefined diff --git a/meet-ce/backend/src/migrations/global-config-migrations.ts b/meet-ce/backend/src/migrations/global-config-migrations.ts index f474aaec..39199bfb 100644 --- a/meet-ce/backend/src/migrations/global-config-migrations.ts +++ b/meet-ce/backend/src/migrations/global-config-migrations.ts @@ -7,8 +7,7 @@ import { MeetGlobalConfigDocument } from '../models/mongoose-schemas/global-conf * * Example: * - * const globalConfigMigrationV1ToV2Name = generateSchemaMigrationName('MeetGlobalConfig', 1, 2); - * + * const globalConfigMigrationV1ToV2Name = generateSchemaMigrationName(meetGlobalConfigCollectionName, 1, 2); * const globalConfigMigrationV1ToV2Transform: SchemaTransform = () => ({ * $set: { * newField: 'default-value' diff --git a/meet-ce/backend/src/migrations/user-migrations.ts b/meet-ce/backend/src/migrations/user-migrations.ts index 464f87b1..4ea51a6c 100644 --- a/meet-ce/backend/src/migrations/user-migrations.ts +++ b/meet-ce/backend/src/migrations/user-migrations.ts @@ -7,8 +7,7 @@ import { MeetUserDocument } from '../models/mongoose-schemas/user.schema.js'; * * Example: * - * const userMigrationV1ToV2Name = generateSchemaMigrationName('MeetUser', 1, 2); - * + * const userMigrationV1ToV2Name = generateSchemaMigrationName(meetUserCollectionName, 1, 2); * const userMigrationV1ToV2Transform: SchemaTransform = () => ({ * $set: { * email: undefined From 3142f9fe79453f79de64b2e62ea7766ec5539961 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 12:46:38 +0100 Subject: [PATCH 4/7] backend: update migration README to clarify schema versioning and MIGRATION_REV timestamp requirements --- meet-ce/backend/src/config/internal-config.ts | 12 +++++++----- meet-ce/backend/src/migrations/README.md | 12 ++++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/meet-ce/backend/src/config/internal-config.ts b/meet-ce/backend/src/config/internal-config.ts index 818e147d..15e867aa 100644 --- a/meet-ce/backend/src/config/internal-config.ts +++ b/meet-ce/backend/src/config/internal-config.ts @@ -54,11 +54,13 @@ export const INTERNAL_CONFIG = { // MongoDB Schema Versions // These define the current schema version for each collection // Increment when making breaking changes to the schema structure - GLOBAL_CONFIG_SCHEMA_VERSION: 1 as SchemaVersion, - USER_SCHEMA_VERSION: 1 as SchemaVersion, - API_KEY_SCHEMA_VERSION: 1 as SchemaVersion, - ROOM_SCHEMA_VERSION: 1 as SchemaVersion, - RECORDING_SCHEMA_VERSION: 1 as SchemaVersion + // IMPORTANT: whenever you increment a schema version, update the MIGRATION_REV timestamp too. + // This helps surface merge conflicts when multiple branches create schema migrations concurrently. + GLOBAL_CONFIG_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 + USER_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 + API_KEY_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 + ROOM_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 + RECORDING_SCHEMA_VERSION: 1 as SchemaVersion // MIGRATION_REV: 1771328577054 }; // This function is used to set private configuration values for testing purposes. diff --git a/meet-ce/backend/src/migrations/README.md b/meet-ce/backend/src/migrations/README.md index 4580e468..d0f96962 100644 --- a/meet-ce/backend/src/migrations/README.md +++ b/meet-ce/backend/src/migrations/README.md @@ -76,17 +76,25 @@ export interface MeetRoom extends MeetRoomOptions { ### Step 2: Update Schema Version in Configuration -In `src/config/internal-config.ts`, increment the version constant: +In `src/config/internal-config.ts`, increment the version constant and update the `MIGRATION_REV` timestamp comment on the same line: ```typescript // internal-config.ts export const INTERNAL_CONFIG = { // ... other config - ROOM_SCHEMA_VERSION: 2 // Was 1 + ROOM_SCHEMA_VERSION: 2 as SchemaVersion // MIGRATION_REV: 1771328577054 // ... }; ``` +`MIGRATION_REV` is a unique marker (current timestamp in milliseconds) used to make concurrent schema-version bumps more visible during Git merges. + +If a merge conflict appears in that line, it means multiple migrations were created in parallel; resolve it by: + +1. Keeping all migration code changes. +2. Re-evaluating the final schema version number. +3. Updating `MIGRATION_REV` again with a new timestamp. + ### Step 3: Update Moongose Schema Update the Mongoose schema to reflect the changes (new fields, etc.): From 7378a8f53e38fca9ca697fcc8393d713ffb97a80 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 12:47:31 +0100 Subject: [PATCH 5/7] backend: create schema migrations for room and recording from v1 to v2 version --- meet-ce/backend/src/config/internal-config.ts | 4 +-- .../src/migrations/recording-migrations.ts | 25 +++++++++--------- .../backend/src/migrations/room-migrations.ts | 26 +++++++++---------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/meet-ce/backend/src/config/internal-config.ts b/meet-ce/backend/src/config/internal-config.ts index 15e867aa..de7baa18 100644 --- a/meet-ce/backend/src/config/internal-config.ts +++ b/meet-ce/backend/src/config/internal-config.ts @@ -59,8 +59,8 @@ export const INTERNAL_CONFIG = { GLOBAL_CONFIG_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 USER_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 API_KEY_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 - ROOM_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054 - RECORDING_SCHEMA_VERSION: 1 as SchemaVersion // MIGRATION_REV: 1771328577054 + ROOM_SCHEMA_VERSION: 2 as SchemaVersion, // MIGRATION_REV: 1771328577054 + RECORDING_SCHEMA_VERSION: 2 as SchemaVersion // MIGRATION_REV: 1771328577054 }; // This function is used to set private configuration values for testing purposes. diff --git a/meet-ce/backend/src/migrations/recording-migrations.ts b/meet-ce/backend/src/migrations/recording-migrations.ts index a0f0891d..a18195bf 100644 --- a/meet-ce/backend/src/migrations/recording-migrations.ts +++ b/meet-ce/backend/src/migrations/recording-migrations.ts @@ -1,20 +1,19 @@ -import { SchemaMigrationMap } from '../models/migration.model.js'; -import { MeetRecordingDocument } from '../models/mongoose-schemas/recording.schema.js'; +import { MeetRecordingEncodingPreset, MeetRecordingLayout } from '@openvidu-meet/typings'; +import { generateSchemaMigrationName, SchemaMigrationMap, SchemaTransform } from '../models/migration.model.js'; +import { meetRecordingCollectionName, MeetRecordingDocument } from '../models/mongoose-schemas/recording.schema.js'; + +const recordingMigrationV1ToV2Name = generateSchemaMigrationName(meetRecordingCollectionName, 1, 2); +const recordingMigrationV1ToV2Transform: SchemaTransform = () => ({ + $set: { + layout: MeetRecordingLayout.GRID, + encoding: MeetRecordingEncodingPreset.H264_720P_30 + } +}); /** * Schema migrations for MeetRecording. * Key format: schema_{collection}_v{from}_to_v{to} - * - * Example: - * - * const recordingMigrationV1ToV2Name = generateSchemaMigrationName('MeetRecording', 1, 2); - * - * const recordingMigrationV1ToV2Transform: SchemaTransform = () => ({ - * $set: { - * quality: 'standard' - * } - * }); */ export const recordingMigrations: SchemaMigrationMap = new Map([ - // [recordingMigrationV1ToV2Name, recordingMigrationV1ToV2Transform] + [recordingMigrationV1ToV2Name, recordingMigrationV1ToV2Transform] ]); diff --git a/meet-ce/backend/src/migrations/room-migrations.ts b/meet-ce/backend/src/migrations/room-migrations.ts index 4690ea2f..9fd70edb 100644 --- a/meet-ce/backend/src/migrations/room-migrations.ts +++ b/meet-ce/backend/src/migrations/room-migrations.ts @@ -1,20 +1,20 @@ -import { SchemaMigrationMap } from '../models/migration.model.js'; -import { MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; +import { MeetRecordingEncodingPreset, MeetRecordingLayout } from '@openvidu-meet/typings'; +import { generateSchemaMigrationName, SchemaMigrationMap, SchemaTransform } from '../models/migration.model.js'; +import { meetRoomCollectionName, MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; + +const roomMigrationV1ToV2Name = generateSchemaMigrationName(meetRoomCollectionName, 1, 2); +const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ + $set: { + 'config.captions': { enabled: true }, + 'config.recording.layout': MeetRecordingLayout.GRID, + 'config.recording.encoding': MeetRecordingEncodingPreset.H264_720P_30 + } +}); /** * Schema migrations for MeetRoom. * Key format: schema_{collection}_v{from}_to_v{to} - * - * Example: - * - * const roomMigrationV1ToV2Name = generateSchemaMigrationName('MeetRoom', 1, 2); - * - * const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ - * $set: { - * maxParticipants: 100 - * } - * }); */ export const roomMigrations: SchemaMigrationMap = new Map([ - // [roomMigrationV1ToV2Name, roomMigrationV1ToV2Transform] + [roomMigrationV1ToV2Name, roomMigrationV1ToV2Transform] ]); From 90a1c6fde98d667943164cca28a7e274ffc149ac Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 16:03:05 +0100 Subject: [PATCH 6/7] backend: refactor migration transforms to return updated document instances and improve MigrationService to execute all transforms sequantilly for each document --- meet-ce/backend/src/migrations/README.md | 18 +- .../src/migrations/api-key-migrations.ts | 10 +- .../migrations/global-config-migrations.ts | 10 +- .../src/migrations/recording-migrations.ts | 12 +- .../backend/src/migrations/room-migrations.ts | 14 +- .../backend/src/migrations/user-migrations.ts | 10 +- meet-ce/backend/src/models/migration.model.ts | 14 +- .../backend/src/services/migration.service.ts | 213 ++++++++++-------- 8 files changed, 160 insertions(+), 141 deletions(-) diff --git a/meet-ce/backend/src/migrations/README.md b/meet-ce/backend/src/migrations/README.md index d0f96962..ada2cdd3 100644 --- a/meet-ce/backend/src/migrations/README.md +++ b/meet-ce/backend/src/migrations/README.md @@ -114,19 +114,15 @@ import { SchemaTransform, generateSchemaMigrationName } from '../models/migratio import { meetRoomCollectionName, MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; const roomMigrationV1ToV2Name = generateSchemaMigrationName(meetRoomCollectionName, 1, 2); -const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ - $set: { - maxParticipants: 100 - } -}); + +const roomMigrationV1ToV2Transform: SchemaTransform = (room) => { + room.maxParticipants = 100; + return room; +}; ``` -`transform` must return MongoDB update operators, so it can express any kind of change: - -- `$set` to add/modify values -- `$unset` to remove properties -- `$rename` to rename fields -- Any other supported update operator +`transform` must return the updated document instance. +It can mutate the received document by adding, removing, or modifying fields as needed to conform to the new schema version. ### Step 5: Register Migration diff --git a/meet-ce/backend/src/migrations/api-key-migrations.ts b/meet-ce/backend/src/migrations/api-key-migrations.ts index de798080..4b16ffef 100644 --- a/meet-ce/backend/src/migrations/api-key-migrations.ts +++ b/meet-ce/backend/src/migrations/api-key-migrations.ts @@ -8,11 +8,11 @@ import { MeetApiKeyDocument } from '../models/mongoose-schemas/api-key.schema.js * Example: * * const apiKeyMigrationV1ToV2Name = generateSchemaMigrationName(meetApiKeyCollectionName, 1, 2); - * const apiKeyMigrationV1ToV2Transform: SchemaTransform = () => ({ - * $set: { - * expirationDate: undefined - * } - * }); + * + * const apiKeyMigrationV1ToV2Transform: SchemaTransform = (apiKey) => { + * apiKey.expirationDate = undefined; + * return apiKey; + * }; */ export const apiKeyMigrations: SchemaMigrationMap = new Map([ // [apiKeyMigrationV1ToV2Name, apiKeyMigrationV1ToV2Transform] diff --git a/meet-ce/backend/src/migrations/global-config-migrations.ts b/meet-ce/backend/src/migrations/global-config-migrations.ts index 39199bfb..a24ebd60 100644 --- a/meet-ce/backend/src/migrations/global-config-migrations.ts +++ b/meet-ce/backend/src/migrations/global-config-migrations.ts @@ -8,11 +8,11 @@ import { MeetGlobalConfigDocument } from '../models/mongoose-schemas/global-conf * Example: * * const globalConfigMigrationV1ToV2Name = generateSchemaMigrationName(meetGlobalConfigCollectionName, 1, 2); - * const globalConfigMigrationV1ToV2Transform: SchemaTransform = () => ({ - * $set: { - * newField: 'default-value' - * } - * }); + * + * const globalConfigMigrationV1ToV2Transform: SchemaTransform = (globalConfig) => { + * globalConfig.newField = 'defaultValue'; + * return globalConfig; + * }; */ export const globalConfigMigrations: SchemaMigrationMap = new Map([ // [globalConfigMigrationV1ToV2Name, globalConfigMigrationV1ToV2Transform] diff --git a/meet-ce/backend/src/migrations/recording-migrations.ts b/meet-ce/backend/src/migrations/recording-migrations.ts index a18195bf..ac8eb7e6 100644 --- a/meet-ce/backend/src/migrations/recording-migrations.ts +++ b/meet-ce/backend/src/migrations/recording-migrations.ts @@ -3,12 +3,12 @@ import { generateSchemaMigrationName, SchemaMigrationMap, SchemaTransform } from import { meetRecordingCollectionName, MeetRecordingDocument } from '../models/mongoose-schemas/recording.schema.js'; const recordingMigrationV1ToV2Name = generateSchemaMigrationName(meetRecordingCollectionName, 1, 2); -const recordingMigrationV1ToV2Transform: SchemaTransform = () => ({ - $set: { - layout: MeetRecordingLayout.GRID, - encoding: MeetRecordingEncodingPreset.H264_720P_30 - } -}); + +const recordingMigrationV1ToV2Transform: SchemaTransform = (recording) => { + recording.layout = MeetRecordingLayout.GRID; + recording.encoding = MeetRecordingEncodingPreset.H264_720P_30; + return recording; +}; /** * Schema migrations for MeetRecording. diff --git a/meet-ce/backend/src/migrations/room-migrations.ts b/meet-ce/backend/src/migrations/room-migrations.ts index 9fd70edb..a6e00779 100644 --- a/meet-ce/backend/src/migrations/room-migrations.ts +++ b/meet-ce/backend/src/migrations/room-migrations.ts @@ -3,13 +3,13 @@ import { generateSchemaMigrationName, SchemaMigrationMap, SchemaTransform } from import { meetRoomCollectionName, MeetRoomDocument } from '../models/mongoose-schemas/room.schema.js'; const roomMigrationV1ToV2Name = generateSchemaMigrationName(meetRoomCollectionName, 1, 2); -const roomMigrationV1ToV2Transform: SchemaTransform = () => ({ - $set: { - 'config.captions': { enabled: true }, - 'config.recording.layout': MeetRecordingLayout.GRID, - 'config.recording.encoding': MeetRecordingEncodingPreset.H264_720P_30 - } -}); + +const roomMigrationV1ToV2Transform: SchemaTransform = (room) => { + room.config.captions = { enabled: true }; + room.config.recording.layout = MeetRecordingLayout.GRID; + room.config.recording.encoding = MeetRecordingEncodingPreset.H264_720P_30; + return room; +}; /** * Schema migrations for MeetRoom. diff --git a/meet-ce/backend/src/migrations/user-migrations.ts b/meet-ce/backend/src/migrations/user-migrations.ts index 4ea51a6c..3af4a400 100644 --- a/meet-ce/backend/src/migrations/user-migrations.ts +++ b/meet-ce/backend/src/migrations/user-migrations.ts @@ -8,11 +8,11 @@ import { MeetUserDocument } from '../models/mongoose-schemas/user.schema.js'; * Example: * * const userMigrationV1ToV2Name = generateSchemaMigrationName(meetUserCollectionName, 1, 2); - * const userMigrationV1ToV2Transform: SchemaTransform = () => ({ - * $set: { - * email: undefined - * } - * }); + * + * const userMigrationV1ToV2Transform: SchemaTransform = (user) => { + * user.newField = 'defaultValue'; + * return user; + * }; */ export const userMigrations: SchemaMigrationMap = new Map([ // [userMigrationV1ToV2Name, userMigrationV1ToV2Transform] diff --git a/meet-ce/backend/src/models/migration.model.ts b/meet-ce/backend/src/models/migration.model.ts index f22e52b9..9bf3d03f 100644 --- a/meet-ce/backend/src/models/migration.model.ts +++ b/meet-ce/backend/src/models/migration.model.ts @@ -1,4 +1,4 @@ -import { Document, Model, UpdateQuery } from 'mongoose'; +import { Document, Model } from 'mongoose'; /** * Interface representing a migration document in MongoDB. @@ -124,17 +124,9 @@ export interface SchemaMigratableDocument extends Document { export type SchemaVersion = number; /** - * MongoDB update operations generated by a migration transform. - * Supports full update operators like $set, $unset, $rename, etc. + * Function that transforms a document and returns the updated document. */ -export type MigrationUpdate = UpdateQuery; - -/** - * Function that transforms a document and returns a MongoDB update operation. - */ -export type SchemaTransform = ( - document: TDocument -) => MigrationUpdate; +export type SchemaTransform = (document: TDocument) => TDocument; /** * Map of schema migration names to transform functions. diff --git a/meet-ce/backend/src/services/migration.service.ts b/meet-ce/backend/src/services/migration.service.ts index 5b192f4c..b64fc48b 100644 --- a/meet-ce/backend/src/services/migration.service.ts +++ b/meet-ce/backend/src/services/migration.service.ts @@ -7,7 +7,6 @@ import { CollectionMigrationRegistry, generateSchemaMigrationName, MigrationResult, - MigrationUpdate, SchemaMigratableDocument, SchemaMigrationStep, SchemaVersion @@ -95,36 +94,46 @@ export class MigrationService { ): Promise { this.logger.info(`Checking schema version for collection: ${registry.collectionName}`); - const minVersionInDb = await this.getMinSchemaVersion(registry.model); + const oldestSchemaVersionInDb = await this.getMinSchemaVersion(registry.model); - if (minVersionInDb === null) { + if (oldestSchemaVersionInDb === null) { this.logger.info(`No documents found in ${registry.collectionName}, skipping migration`); return 0; } - const maxVersionInDb = await this.getMaxSchemaVersion(registry.model); + const latestSchemaVersionInDb = await this.getMaxSchemaVersion(registry.model); - if (maxVersionInDb && maxVersionInDb > registry.currentVersion) { + if (latestSchemaVersionInDb && latestSchemaVersionInDb > registry.currentVersion) { throw new Error( - `Collection ${registry.collectionName} has schemaVersion ${maxVersionInDb}, ` + + `Collection ${registry.collectionName} has schemaVersion ${latestSchemaVersionInDb}, ` + `which is higher than expected ${registry.currentVersion}. ` + 'Startup aborted to prevent inconsistent schema handling.' ); } - if (minVersionInDb === registry.currentVersion) { - this.logger.info(`Collection ${registry.collectionName} is already at version ${registry.currentVersion}`); + if (oldestSchemaVersionInDb === registry.currentVersion) { + this.logger.info( + `Collection ${registry.collectionName} is already at version ${registry.currentVersion}, skipping migration` + ); return 0; } - const migrationSteps = this.getRequiredMigrationSteps(registry, minVersionInDb); - let collectionMigrated = 0; + let migratedDocumentsInCollection = 0; - for (const migrationStep of migrationSteps) { - collectionMigrated += await this.executeCollectionMigrationStep(registry, migrationStep); + for ( + let sourceSchemaVersion = oldestSchemaVersionInDb; + sourceSchemaVersion < registry.currentVersion; + sourceSchemaVersion++ + ) { + const migrationChain = this.getRequiredMigrationSteps(registry, sourceSchemaVersion); + migratedDocumentsInCollection += await this.executeMigrationChainForVersion( + registry, + sourceSchemaVersion, + migrationChain + ); } - return collectionMigrated; + return migratedDocumentsInCollection; } /** @@ -132,121 +141,139 @@ export class MigrationService { * Validates that there are no missing migration steps in the chain. * * @param registry - The collection migration registry - * @param minVersionInDb - The minimum schema version currently present in the database + * @param sourceSchemaVersion - Source schema version whose migration chain must be executed * @returns Array of migration steps that need to be executed in order */ protected getRequiredMigrationSteps( registry: CollectionMigrationRegistry, - minVersionInDb: SchemaVersion + sourceSchemaVersion: SchemaVersion ): SchemaMigrationStep[] { - const migrationSteps = this.findSchemaMigrationSteps(registry, minVersionInDb, registry.currentVersion); + const migrationSteps = this.findSchemaMigrationSteps(registry, sourceSchemaVersion, registry.currentVersion); if (migrationSteps.length === 0) { throw new Error( `No migration steps found for ${registry.collectionName} ` + - `(v${minVersionInDb} -> v${registry.currentVersion}). Startup aborted.` + `(v${sourceSchemaVersion} -> v${registry.currentVersion}). Startup aborted.` ); } this.logger.info( `Found ${migrationSteps.length} migration steps for ${registry.collectionName} ` + - `(v${minVersionInDb} -> v${registry.currentVersion})` + `(v${sourceSchemaVersion} -> v${registry.currentVersion})` ); return migrationSteps; } /** - * Executes a single migration step for a collection, applying the transform to all documents at the fromVersion. - * Handles marking the migration as started, completed, or failed in the migration repository. + * Executes the migration chain for all documents currently at a specific source schema version. + * Handles marking the chain as started, completed, or failed in the migration repository. * * @param registry - The collection migration registry - * @param migrationStep - The specific migration step to execute - * @returns Number of documents migrated in this step + * @param sourceSchemaVersion - Source schema version to migrate from + * @param migrationChain - Ordered migration steps from source to current version + * @returns Number of migrated documents for this source version */ - protected async executeCollectionMigrationStep( + protected async executeMigrationChainForVersion( registry: CollectionMigrationRegistry, - migrationStep: SchemaMigrationStep + sourceSchemaVersion: SchemaVersion, + migrationChain: SchemaMigrationStep[] ): Promise { - const pendingBefore = await this.countDocumentsAtSchemaVersion(registry.model, migrationStep.fromVersion); + const pendingDocumentsBefore = await this.countDocumentsAtSchemaVersion(registry.model, sourceSchemaVersion); + const migrationChainExecutionName = generateSchemaMigrationName( + registry.collectionName, + sourceSchemaVersion, + registry.currentVersion + ); - if (pendingBefore === 0) { - this.logger.info(`Migration ${migrationStep.name} has no pending documents, skipping execution`); + if (pendingDocumentsBefore === 0) { + this.logger.info(`Migration ${migrationChainExecutionName} has no pending documents, skipping execution`); return 0; } - const isCompleted = await this.migrationRepository.isCompleted(migrationStep.name); + const isCompleted = await this.migrationRepository.isCompleted(migrationChainExecutionName); if (isCompleted) { this.logger.warn( - `Migration ${migrationStep.name} is marked as completed but still has ${pendingBefore} pending ` + - `documents at schemaVersion ${migrationStep.fromVersion}. Re-running migration step.` + `Migration ${migrationChainExecutionName} is marked as completed but still has ${pendingDocumentsBefore} pending ` + + `documents at schemaVersion ${sourceSchemaVersion}. Re-running migration chain.` ); } - await this.migrationRepository.markAsStarted(migrationStep.name); + await this.migrationRepository.markAsStarted(migrationChainExecutionName); try { - this.logger.info(`Executing migration: ${migrationStep.name}`); - const result = await this.runSchemaMigrationStep(migrationStep, registry.model); - const pendingAfter = await this.countDocumentsAtSchemaVersion(registry.model, migrationStep.fromVersion); + this.logger.info(`Executing migration: ${migrationChainExecutionName}`); + const result = await this.migrateDocumentsForSourceVersion( + registry.model, + sourceSchemaVersion, + registry.currentVersion, + migrationChain + ); + const pendingDocumentsAfter = await this.countDocumentsAtSchemaVersion(registry.model, sourceSchemaVersion); - const metadata: Record = { + const metadata = { collectionName: registry.collectionName, - fromVersion: migrationStep.fromVersion, - toVersion: migrationStep.toVersion, + fromVersion: sourceSchemaVersion, + toVersion: registry.currentVersion, + chainLength: migrationChain.length, + chainStepNames: migrationChain.map((step) => step.name), migratedCount: result.migratedCount, failedCount: result.failedCount, - pendingBefore, - pendingAfter, + pendingBefore: pendingDocumentsBefore, + pendingAfter: pendingDocumentsAfter, durationMs: result.durationMs }; - if (result.failedCount > 0 || pendingAfter > 0) { + if (result.failedCount > 0 || pendingDocumentsAfter > 0) { const failureReason = - `Migration ${migrationStep.name} did not complete successfully. ` + - `failedCount=${result.failedCount}, pendingAfter=${pendingAfter}`; + `Migration ${migrationChainExecutionName} did not complete successfully. ` + + `failedCount=${result.failedCount}, pendingAfter=${pendingDocumentsAfter}`; - await this.migrationRepository.markAsFailed(migrationStep.name, failureReason); + await this.migrationRepository.markAsFailed(migrationChainExecutionName, failureReason); throw new Error(failureReason); } - await this.migrationRepository.markAsCompleted(migrationStep.name, metadata); + await this.migrationRepository.markAsCompleted(migrationChainExecutionName, metadata); this.logger.info( - `Migration ${migrationStep.name} completed: ${result.migratedCount} documents migrated (${result.durationMs}ms)` + `Migration ${migrationChainExecutionName} completed: ${result.migratedCount} documents migrated (${result.durationMs}ms)` ); return result.migratedCount; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - await this.migrationRepository.markAsFailed(migrationStep.name, errorMessage); + await this.migrationRepository.markAsFailed(migrationChainExecutionName, errorMessage); throw error; } } /** - * Executes a single schema migration step on all documents that match the fromVersion. - * Applies the transform function to each document and updates it to the toVersion. + * Executes a migration chain on all documents that match the source version. + * Applies all transforms sequentially and saves each document once at the target version. * - * @param migrationStep - The migration step to execute * @param model - Mongoose model for the collection being migrated + * @param sourceSchemaVersion - The schema version to migrate from + * @param targetVersion - The schema version to migrate to + * @param migrationChain - Array of migration steps to apply in order * @param batchSize - Number of documents to process in each batch. Default is 50. * @returns Migration result with statistics about the execution */ - protected async runSchemaMigrationStep( - migrationStep: SchemaMigrationStep, + protected async migrateDocumentsForSourceVersion( model: Model, + sourceSchemaVersion: SchemaVersion, + targetVersion: SchemaVersion, + migrationChain: SchemaMigrationStep[], batchSize = 50 ): Promise { const startTime = Date.now(); let migratedCount = 0; let failedCount = 0; - const versionFilter = { schemaVersion: migrationStep.fromVersion }; - const totalDocs = await model.countDocuments(versionFilter).exec(); + const sourceVersionFilter = { schemaVersion: sourceSchemaVersion }; + const totalSourceVersionDocuments = await model.countDocuments(sourceVersionFilter).exec(); - if (totalDocs === 0) { + if (totalSourceVersionDocuments === 0) { return { migratedCount, failedCount, @@ -254,17 +281,17 @@ export class MigrationService { }; } - let processedCount = 0; - let lastProcessedId: TDocument['_id'] | null = null; - let hasMoreDocuments = true; + let processedDocumentsCount = 0; + let lastProcessedDocumentId: TDocument['_id'] | null = null; + let hasMoreBatches = true; - while (hasMoreDocuments) { + while (hasMoreBatches) { const batchFilter = - lastProcessedId === null - ? versionFilter + lastProcessedDocumentId === null + ? sourceVersionFilter : { - ...versionFilter, - _id: { $gt: lastProcessedId } + ...sourceVersionFilter, + _id: { $gt: lastProcessedDocumentId } }; const documents = await model.find(batchFilter).sort({ _id: 1 }).limit(batchSize).exec(); @@ -274,9 +301,8 @@ export class MigrationService { const batchResults = await Promise.allSettled( documents.map(async (doc) => { - const transformedUpdate = migrationStep.transform(doc); - const update = this.appendSchemaVersionUpdate(transformedUpdate, migrationStep.toVersion); - await model.updateOne({ _id: doc._id }, update).exec(); + const migratedDocument = this.applyTransformChain(doc, migrationChain, targetVersion); + await migratedDocument.save(); return String(doc._id); }) ); @@ -293,10 +319,10 @@ export class MigrationService { this.logger.warn(`Failed to migrate document ${String(documents[i]._id)}:`, batchResult.reason); } - processedCount += documents.length; - lastProcessedId = documents[documents.length - 1]._id; - hasMoreDocuments = documents.length === batchSize; - this.logger.debug(`Processed ${processedCount}/${totalDocs} documents`); + processedDocumentsCount += documents.length; + lastProcessedDocumentId = documents[documents.length - 1]._id; + hasMoreBatches = documents.length === batchSize; + this.logger.debug(`Processed ${processedDocumentsCount}/${totalSourceVersionDocuments} documents`); } return { @@ -307,7 +333,8 @@ export class MigrationService { } /** - * Gets the minimum schema version present in the collection to detect the oldest pending version of documents. + * Gets the minimum schema version present in the collection. + * This is used to detect the oldest pending version of documents. * * @param model - Mongoose model for the collection * @returns Current version or null if collection is empty @@ -331,7 +358,8 @@ export class MigrationService { } /** - * Gets the maximum schema version present in the collection to detect if there are any documents above expected version. + * Gets the maximum schema version present in the collection. + * This is used to detect if there are any documents above expected version. * * @param model - Mongoose model for the collection * @returns Maximum version or null if collection is empty @@ -381,9 +409,9 @@ export class MigrationService { fromVersion: SchemaVersion, toVersion: SchemaVersion ): SchemaMigrationStep[] { - const needed: SchemaMigrationStep[] = []; + const steps: SchemaMigrationStep[] = []; - // Build a chain of migrations from fromVersion to toVersion + // Build a chain of migration steps from fromVersion to toVersion let currentVersion = fromVersion; while (currentVersion < toVersion) { @@ -402,7 +430,7 @@ export class MigrationService { ); } - needed.push({ + steps.push({ name: expectedMigrationName, fromVersion: currentVersion, toVersion: nextVersion, @@ -411,27 +439,30 @@ export class MigrationService { currentVersion = nextVersion; } - return needed; + return steps; } /** - * Appends a schemaVersion update to the migration update operation. - * Ensures that migrated documents are marked with the new version. + * Applies a chain of migration transforms to a document sequentially. + * Updates the document's schemaVersion to the target version after applying all transforms. * - * @param update - Original migration update operation - * @param toVersion - Target schema version to set - * @returns Updated migration operation with schemaVersion set + * @param document - The document to transform + * @param migrationChain - Array of migration steps to apply in order + * @param targetVersion - The final schema version after applying the chain + * @returns The transformed document with updated schemaVersion */ - protected appendSchemaVersionUpdate( - update: MigrationUpdate, - toVersion: SchemaVersion - ): MigrationUpdate { - return { - ...update, - $set: { - ...(update.$set ?? {}), - schemaVersion: toVersion - } - }; + protected applyTransformChain( + document: TDocument, + migrationChain: SchemaMigrationStep[], + targetVersion: SchemaVersion + ): TDocument { + let transformedDocument = document; + + for (const migrationStep of migrationChain) { + transformedDocument = migrationStep.transform(transformedDocument); + } + + transformedDocument.schemaVersion = targetVersion; + return transformedDocument; } } From ac3a728591d41eea9316bdbeb5885e62bedb1adf Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 17 Feb 2026 17:40:50 +0100 Subject: [PATCH 7/7] Revert "frontend: Refactor user management components and update routes" This reverts commit f677b18879bb13acf063de6a3366059a3a49d3ed. --- .../src/lib/domains/console/pages/index.ts | 2 +- .../users-permissions.component.html} | 61 ++++++++++++++++--- .../users-permissions.component.scss} | 0 .../users-permissions.component.ts} | 8 +-- .../domains/console/routes/console.routes.ts | 27 ++++---- .../src/assets/styles/_tokens-core.scss | 2 +- .../src/assets/styles/_utilities.scss | 4 +- 7 files changed, 74 insertions(+), 30 deletions(-) rename meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/{users/users.component.html => users-permissions/users-permissions.component.html} (77%) rename meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/{users/users.component.scss => users-permissions/users-permissions.component.scss} (100%) rename meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/{users/users.component.ts => users-permissions/users-permissions.component.ts} (97%) diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/index.ts index 4e9bf571..99a2f7ee 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/index.ts @@ -3,5 +3,5 @@ export * from './config/config.component'; export * from './console/console.component'; export * from './embedded/embedded.component'; export * from './overview/overview.component'; -export * from './users/users.component'; +export * from './users-permissions/users-permissions.component'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.html b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.html similarity index 77% rename from meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.html rename to meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.html index 35e323aa..96784f2f 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.html +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.html @@ -1,10 +1,12 @@
@if (isLoading()) { @@ -78,8 +80,8 @@

Administrator Password

- Update your administrator password to keep your account secure. We recommend using a - strong, unique password. + Update your administrator password to keep your account secure. + We recommend using a strong, unique password.

@@ -137,10 +139,7 @@ > {{ showNewPassword() ? 'visibility_off' : 'visibility' }} - Minimum 5 characters. We recommend using letters, numbers, and - symbols + Minimum 5 characters. We recommend using letters, numbers, and symbols @if (getNewPasswordError()) { {{ getNewPasswordError() }} } @@ -188,6 +187,50 @@ + + + + +
} diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.scss b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.scss similarity index 100% rename from meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.scss rename to meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.scss diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.ts similarity index 97% rename from meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.ts rename to meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.ts index 70bf6d62..d7f244a2 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users/users.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/users-permissions/users-permissions.component.ts @@ -24,7 +24,7 @@ import { NotificationService } from '../../../../shared/services/notification.se import { AuthService } from '../../../auth/services/auth.service'; @Component({ - selector: 'ov-users', + selector: 'ov-users-permissions', imports: [ MatCardModule, MatButtonModule, @@ -38,10 +38,10 @@ import { AuthService } from '../../../auth/services/auth.service'; ReactiveFormsModule // ProFeatureBadgeComponent ], - templateUrl: './users.component.html', - styleUrl: './users.component.scss' + templateUrl: './users-permissions.component.html', + styleUrl: './users-permissions.component.scss' }) -export class UsersComponent implements OnInit { +export class UsersPermissionsComponent implements OnInit { isLoading = signal(true); showCurrentPassword = signal(false); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/routes/console.routes.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/routes/console.routes.ts index dd390b39..cd7ff610 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/routes/console.routes.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/routes/console.routes.ts @@ -23,19 +23,6 @@ export const consoleChildRoutes: DomainRouteConfig[] = [ }, ...roomsConsoleRoutes, ...recordingsConsoleRoutes, - { - route: { - path: 'users', - loadComponent: () => import('../pages/users/users.component').then((m) => m.UsersComponent) - }, - navMetadata: { - label: 'Users', - route: 'users', - icon: 'group', - iconClass: 'ov-users material-symbols-outlined', - order: 4 - } - }, { route: { path: 'embedded', @@ -46,6 +33,20 @@ export const consoleChildRoutes: DomainRouteConfig[] = [ route: 'embedded', icon: 'code_blocks', iconClass: 'material-symbols-outlined ov-developer-icon', + order: 4 + } + }, + { + route: { + path: 'users-permissions', + loadComponent: () => + import('../pages/users-permissions/users-permissions.component').then((m) => m.UsersPermissionsComponent) + }, + navMetadata: { + label: 'Users & Permissions', + route: 'users-permissions', + icon: 'passkey', + iconClass: 'ov-users-permissions material-symbols-outlined', order: 5 } }, diff --git a/meet-ce/frontend/src/assets/styles/_tokens-core.scss b/meet-ce/frontend/src/assets/styles/_tokens-core.scss index f33312b0..561dd0c9 100644 --- a/meet-ce/frontend/src/assets/styles/_tokens-core.scss +++ b/meet-ce/frontend/src/assets/styles/_tokens-core.scss @@ -24,7 +24,7 @@ --ov-meet-icon-rooms: var(--ov-meet-color-primary); // video_chat, meeting_room --ov-meet-icon-recordings: var(--ov-meet-color-accent); // video_library, play_circle --ov-meet-icon-developer: var(--ov-meet-color-warning); // code, api - --ov-meet-icon-users: #37b953; // group, manage_accounts + --ov-meet-icon-users-permissions: #e05200; // group, manage_accounts --ov-meet-icon-settings: var(--ov-meet-color-secondary); // settings, tune // --ov-meet-icon-about: var(--ov-meet-color-info); // info, security, help diff --git a/meet-ce/frontend/src/assets/styles/_utilities.scss b/meet-ce/frontend/src/assets/styles/_utilities.scss index f7581a46..739792f6 100644 --- a/meet-ce/frontend/src/assets/styles/_utilities.scss +++ b/meet-ce/frontend/src/assets/styles/_utilities.scss @@ -107,8 +107,8 @@ @include layout.ov-theme-transition; } -.ov-users { - color: var(--ov-meet-icon-users) !important; +.ov-users-permissions { + color: var(--ov-meet-icon-users-permissions) !important; overflow: none; @include layout.ov-theme-transition; }