5.3 KiB
DataMigrationService
File:
src/EchoHub.Server/Setup/DataMigrationService.cs
Kind: class
Figure: How DataMigrationService works.
%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%%
flowchart TB
Start["DataMigrationService.RunAsync(IServiceProvider)"]
Scope["Create scope and resolve services"]
GetServices["Get EchoHubDbContext, IConfiguration and ILogger"]
EnsureDefault["Call EnsureDefaultChannelsPublicAsync(db, logger)"]
CheckDefault{"Channel named HubConstants.DefaultChannel exists and IsPublic == false?"}
UpdateDefault["Set Channel.IsPublic = true; await db.SaveChangesAsync(); logger.LogInformation"]
SkipDefault["No change"]
AfterDefault["Continue to next migration"]
MigrateAnsi["Call MigrateAnsiMessagesAsync(db, logger)"]
LoadImages["Load EchoHubDbContext.Messages where Type == MessageType.Image"]
FilterAnsi["Filter messages where Content contains ESC (0x1B) -> toMigrate list"]
AnsiEmpty{"toMigrate.Count == 0?"}
AnsiProcess["For each message: converted = AnsiToColorTags(Content); if changed set Content and increment modified"]
AnsiSave{"modified > 0?"}
AnsiSaved["await db.SaveChangesAsync(); logger.LogInformation of migrated count"]
MigrateEmbed["Call MigrateEmbedJsonToArrayAsync(db, logger) - convert legacy embed JSON to EmbedDto array where needed"]
MigrateAttachments["Call MigrateLegacyAttachmentsAsync(db, logger) - migrate Attachment entities to new AttachmentKind/format"]
EnsureAdmins["Call EnsureConfiguredAdminsAsync(db, config, logger) - ensure ServerRole admin users per config"]
End["RunAsync complete"]
Start --> Scope --> GetServices --> EnsureDefault --> CheckDefault
CheckDefault -- "yes" --> UpdateDefault --> AfterDefault
CheckDefault -- "no" --> SkipDefault --> AfterDefault
AfterDefault --> MigrateAnsi --> LoadImages --> FilterAnsi --> AnsiEmpty
AnsiEmpty -- "yes" --> MigrateEmbed
AnsiEmpty -- "no" --> AnsiProcess --> AnsiSave
AnsiSave -- "yes" --> AnsiSaved --> MigrateEmbed
AnsiSave -- "no" --> MigrateEmbed
MigrateEmbed --> MigrateAttachments --> EnsureAdmins --> End
public static partial class DataMigrationService
Performs application data migrations that should run at startup. Call RunAsync(IServiceProvider) once (for example during application startup) to perform a series of idempotent migrations against the EchoHubDbContext: make the default channel public, convert legacy ANSI color escape sequences to printable color tags, migrate embed JSON to the newer array form, fold legacy single-row attachments into the Attachments table, and ensure configured admin users exist.
Remarks
DataMigrationService centralizes small, targeted transformations that evolve persisted chat data between versions. Each migration method (for example, EnsureDefaultChannelsPublicAsync, MigrateAnsiMessagesAsync, MigrateEmbedJsonToArrayAsync, MigrateLegacyAttachmentsAsync, and EnsureConfiguredAdminsAsync) is written to be safe to run repeatedly: migrated rows are detected and skipped if already-upgraded so the service can be invoked on every startup without duplicating work. The service resolves a scoped EchoHubDbContext (and IConfiguration/ILoggerFactory) from the provided IServiceProvider, performs database changes, and logs what changed.
The ANSI conversion helper AnsiToColorTags is exposed for reuse and relies on a generated regex (AnsiColorRegex) to efficiently match 24-bit foreground (38;2;R;G;B) and background (48;2;R;G;B) color sequences and the reset code (0). Matches are transformed to {F:RRGGBB}, {B:RRGGBB}, and {X} respectively.
Notes
RunAsyncresolvesEchoHubDbContext,IConfiguration, andILoggerFactoryfrom the providedIServiceProvider; ensure those services are registered in DI before callingRunAsync.MigrateAnsiMessagesAsyncassumesMessage.Contentis populated (the code callsm.Content.Contains('\x1b')). IfMessage.Contentcan be null in your schema, the migration may throw aNullReferenceException— validate non-null constraints or add a null-check before running this migration.AnsiToColorTagsonly converts the specific 24-bit RGB sequences (38;2and48;2) and the reset code (0). Other ANSI sequences are left unchanged by design; if older clients used different ANSI sequences they will not be translated by this helper.