From eff4f7ba5c088b11dcbc51f48048762263995135 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Wed, 1 Apr 2026 17:26:27 +0200 Subject: [PATCH] Add seed subscriptionplan + wip migration controller --- .../Controllers/MigrationController.cs | 803 +++++++++ ManagerService/Data/MyInfoMateDbContext.cs | 25 + ...01152545_SeedSubscriptionPlans.Designer.cs | 1505 +++++++++++++++++ .../20260401152545_SeedSubscriptionPlans.cs | 45 + .../MyInfoMateDbContextModelSnapshot.cs | 23 + 5 files changed, 2401 insertions(+) create mode 100644 ManagerService/Controllers/MigrationController.cs create mode 100644 ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.Designer.cs create mode 100644 ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.cs diff --git a/ManagerService/Controllers/MigrationController.cs b/ManagerService/Controllers/MigrationController.cs new file mode 100644 index 0000000..2c1ebdf --- /dev/null +++ b/ManagerService/Controllers/MigrationController.cs @@ -0,0 +1,803 @@ +using Manager.DTOs; +using Manager.Services; +using ManagerService.Data; +using ManagerService.Data.SubSection; +using ManagerService.DTOs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NetTopologySuite.Geometries; +using NSwag.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace ManagerService.Controllers +{ + [Authorize(Policy = ManagerService.Service.Security.Policies.SuperAdmin)] + [ApiController, Route("api/[controller]")] + [OpenApiTag("Migration", Description = "MongoDB → PostgreSQL data migration")] + public class MigrationController : ControllerBase + { + private readonly MyInfoMateDbContext _db; + private readonly InstanceDatabaseService _instanceSvc; + private readonly ConfigurationDatabaseService _configSvc; + private readonly SectionDatabaseService _sectionSvc; + private readonly ResourceDatabaseService _resourceSvc; + private readonly UserDatabaseService _userSvc; + private readonly DeviceDatabaseService _deviceSvc; + + public MigrationController( + MyInfoMateDbContext db, + InstanceDatabaseService instanceSvc, + ConfigurationDatabaseService configSvc, + SectionDatabaseService sectionSvc, + ResourceDatabaseService resourceSvc, + UserDatabaseService userSvc, + DeviceDatabaseService deviceSvc) + { + _db = db; + _instanceSvc = instanceSvc; + _configSvc = configSvc; + _sectionSvc = sectionSvc; + _resourceSvc = resourceSvc; + _userSvc = userSvc; + _deviceSvc = deviceSvc; + } + + /// + /// Run (or simulate) the MongoDB → PostgreSQL migration. + /// Use dryRun=true to validate without writing anything to the database. + /// Use instanceId to restrict to a single instance. + /// + [HttpPost("run")] + [ProducesResponseType(typeof(MigrationReportDTO), 200)] + [ProducesResponseType(typeof(string), 500)] + public async Task Run( + [FromQuery] bool dryRun = false, + [FromQuery] string instanceId = null) + { + var report = new MigrationReportDTO { DryRun = dryRun }; + + try + { + await MigrateInstancesAsync(report, dryRun, instanceId); + await MigrateResourcesAsync(report, dryRun, instanceId); + await MigrateUsersAsync(report, dryRun, instanceId); + await MigrateConfigurationsAsync(report, dryRun, instanceId); + await MigrateApplicationInstancesAsync(report, dryRun, instanceId); + await MigrateSectionsAsync(report, dryRun, instanceId); + await MigrateDevicesAsync(report, dryRun, instanceId); + await LinkMenuSectionsAsync(report, dryRun); + await EnrichPdfResourcesAsync(report, dryRun); + } + catch (Exception ex) + { + report.FatalError = ex.Message; + } + + return new OkObjectResult(report); + } + + // ─── Instances ──────────────────────────────────────────────────────── + + private async Task MigrateInstancesAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var source = _instanceSvc.GetAll(); + if (filterInstanceId != null) + source = source.Where(i => i.Id == filterInstanceId).ToList(); + + foreach (var old in source) + { + try + { + if (await _db.Instances.AnyAsync(x => x.Id == old.Id)) + { + report.Skipped.Add($"Instance {old.Id} ({old.Name}) — already exists"); + continue; + } + + var entity = new Instance + { + Id = old.Id, + Name = old.Name, + DateCreation = old.DateCreation, + PinCode = old.PinCode?.ToString(), + }; + + if (!dryRun) + _db.Instances.Add(entity); + + report.Migrated.Instances++; + report.Details.Add($"Instance {old.Id} ({old.Name})"); + } + catch (Exception ex) + { + report.Errors.Add($"Instance {old.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Resources ──────────────────────────────────────────────────────── + + private async Task MigrateResourcesAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var instanceIds = filterInstanceId != null + ? new List { filterInstanceId } + : _instanceSvc.GetAll().Select(i => i.Id).ToList(); + var source = instanceIds.SelectMany(id => _resourceSvc.GetAll(id)).ToList(); + + foreach (var old in source) + { + try + { + if (await _db.Resources.AnyAsync(x => x.Id == old.Id)) + { + report.Skipped.Add($"Resource {old.Id} ({old.Label}) — already exists"); + continue; + } + + var entity = new Resource + { + Id = old.Id, + Type = old.Type, + Label = old.Label, + DateCreation = old.DateCreation, + InstanceId = old.InstanceId, + Url = old.Url, // NOTE: MongoDB field is "URL", mapped to Url in OldResource + SizeBytes = 0, + }; + + if (!dryRun) + _db.Resources.Add(entity); + + report.Migrated.Resources++; + } + catch (Exception ex) + { + report.Errors.Add($"Resource {old.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Users ──────────────────────────────────────────────────────────── + + private async Task MigrateUsersAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var source = _userSvc.GetAll(); + if (filterInstanceId != null) + source = source.Where(u => u.InstanceId == filterInstanceId).ToList(); + + foreach (var old in source) + { + try + { + if (await _db.Users.AnyAsync(x => x.Id == old.Id)) + { + report.Skipped.Add($"User {old.Id} ({old.Email}) — already exists"); + continue; + } + + var entity = new User + { + Id = old.Id, + Email = old.Email, + Password = old.Password, + FirstName = old.FirstName, + LastName = old.LastName, + Token = old.Token, + DateCreation = old.DateCreation, + InstanceId = old.InstanceId, + Role = UserRole.ContentEditor, + }; + + if (!dryRun) + _db.Users.Add(entity); + + report.Migrated.Users++; + } + catch (Exception ex) + { + report.Errors.Add($"User {old.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Configurations ─────────────────────────────────────────────────── + + private async Task MigrateConfigurationsAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var instanceIds = filterInstanceId != null + ? new List { filterInstanceId } + : _instanceSvc.GetAll().Select(i => i.Id).ToList(); + var source = instanceIds.SelectMany(id => _configSvc.GetAll(id)).ToList(); + + foreach (var old in source) + { + try + { + if (await _db.Configurations.AnyAsync(x => x.Id == old.Id)) + { + report.Skipped.Add($"Configuration {old.Id} ({old.Label}) — already exists"); + continue; + } + + var entity = new Configuration + { + Id = old.Id, + InstanceId = old.InstanceId, + Label = old.Label, + Title = old.Title ?? new List(), + ImageId = old.ImageId, + ImageSource = old.ImageSource, + PrimaryColor = old.PrimaryColor, + SecondaryColor = old.SecondaryColor, + Languages = old.Languages ?? new List(), + DateCreation = old.DateCreation, + IsOffline = old.IsOffline, + LoaderImageId = old.LoaderImageId, + LoaderImageUrl = old.LoaderImageUrl, + IsQRCode = false, + IsSearchText = false, + IsSearchNumber = false, + }; + + if (!dryRun) + _db.Configurations.Add(entity); + + report.Migrated.Configurations++; + } + catch (Exception ex) + { + report.Errors.Add($"Configuration {old.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── ApplicationInstances + AppConfigurationLinks ──────────────────── + + private async Task MigrateApplicationInstancesAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var instanceIds = filterInstanceId != null + ? new List { filterInstanceId } + : _instanceSvc.GetAll().Select(i => i.Id).ToList(); + + foreach (var iid in instanceIds) + { + var configs = _configSvc.GetAll(iid); + + foreach (var appType in new[] { AppType.Tablet, AppType.Mobile }) + { + var relevant = appType == AppType.Tablet + ? configs.Where(c => c.IsTablet).ToList() + : configs.Where(c => c.IsMobile).ToList(); + + if (!relevant.Any()) continue; + + var appInstanceId = $"migration-{appType.ToString().ToLower()}-{iid}"; + + try + { + if (!await _db.ApplicationInstances.AnyAsync(x => x.Id == appInstanceId)) + { + var firstConfig = relevant.First(); + var allLanguages = relevant + .Where(c => c.Languages != null) + .SelectMany(c => c.Languages) + .Distinct() + .ToList(); + + var appInstance = new ApplicationInstance + { + Id = appInstanceId, + InstanceId = iid, + AppType = appType, + Languages = allLanguages, + LoaderImageId = firstConfig.LoaderImageId, + LoaderImageUrl = firstConfig.LoaderImageUrl, + PrimaryColor = firstConfig.PrimaryColor, + SecondaryColor = firstConfig.SecondaryColor, + Configurations = new List(), + }; + if (!dryRun) _db.ApplicationInstances.Add(appInstance); + report.Migrated.ApplicationInstances++; + report.Details.Add($"ApplicationInstance {appType} for instance {iid}"); + } + + foreach (var config in relevant) + { + var linkId = $"migration-{appType.ToString().ToLower()}-link-{config.Id}"; + if (await _db.AppConfigurationLinks.AnyAsync(x => x.Id == linkId)) + { + report.Skipped.Add($"AppConfigurationLink {linkId} — already exists"); + continue; + } + + var link = new AppConfigurationLink + { + Id = linkId, + ConfigurationId = config.Id, + ApplicationInstanceId = appInstanceId, + IsActive = true, + IsDate = appType == AppType.Tablet && config.IsDate, + IsHour = appType == AppType.Tablet && config.IsHour, + IsSectionImageBackground = appType == AppType.Tablet && config.IsSectionImageBackground, + RoundedValue = appType == AppType.Tablet ? config.RoundedValue : null, + ScreenPercentageSectionsMainPage = appType == AppType.Tablet ? config.ScreenPercentageSectionsMainPage : null, + LoaderImageId = config.LoaderImageId, + LoaderImageUrl = config.LoaderImageUrl, + PrimaryColor = config.PrimaryColor, + SecondaryColor = config.SecondaryColor, + }; + if (!dryRun) _db.AppConfigurationLinks.Add(link); + report.Migrated.AppConfigurationLinks++; + } + } + catch (Exception ex) + { + report.Errors.Add($"ApplicationInstance {appType} {iid}: {ex.Message}"); + } + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Sections ───────────────────────────────────────────────────────── + + private async Task MigrateSectionsAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var instanceIds = filterInstanceId != null + ? new List { filterInstanceId } + : _instanceSvc.GetAll().Select(i => i.Id).ToList(); + + // GetAllFromConfigurationEvenSubsection inclut sections ET sous-sections + var configIds = instanceIds + .SelectMany(id => _configSvc.GetAll(id)) + .Select(c => c.Id) + .ToList(); + + var source = configIds + .SelectMany(cid => _sectionSvc.GetAllFromConfigurationEvenSubsection(cid)) + .GroupBy(s => s.Id).Select(g => g.First()) + .ToList(); + + var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + + foreach (var old in source) + { + try + { + if (await _db.Sections.AnyAsync(x => x.Id == old.Id)) + { + report.Skipped.Add($"Section {old.Id} ({old.Label}, type {old.Type}) — already exists"); + continue; + } + + Section entity = BuildSection(old, opts, report); + if (entity == null) continue; + + if (!dryRun) + _db.Sections.Add(entity); + + report.Migrated.Sections++; + } + catch (Exception ex) + { + report.Errors.Add($"Section {old.Id} ({old.Label}, type {old.Type}): {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + private Section BuildSection(OldSection old, JsonSerializerOptions opts, MigrationReportDTO report) + { + // Champs communs à tous les sous-types + void FillBase(Section s) + { + s.Id = old.Id; + s.Label = old.Label ?? ""; + s.Title = old.Title ?? new List(); + s.Description = old.Description ?? new List(); + s.Order = old.Order; + s.ConfigurationId = old.ConfigurationId; + s.ImageId = old.ImageId; + s.ImageSource = old.ImageSource; + s.Type = old.Type; + s.IsSubSection = old.IsSubSection; + s.ParentId = old.ParentId; + s.DateCreation = old.DateCreation; + s.InstanceId = old.InstanceId; + s.IsBeacon = old.IsBeacon; + s.BeaconId = old.BeaconId; + s.Latitude = old.Latitude; + s.Longitude = old.Longitude; + s.MeterZoneGPS = old.MeterZoneGPS; + s.IsActive = true; + } + + switch (old.Type) + { + case SectionType.Map: + { + var dto = ParseData(old, opts, report); + var s = new SectionMap + { + MapZoom = dto?.zoom ?? 18, + MapMapType = dto?.mapType, + MapTypeMapbox = dto?.mapTypeMapbox, + MapMapProvider = dto?.mapProvider, + MapResourceId = dto?.iconResourceId, + MapCenterLatitude = dto?.latitude, + MapCenterLongitude = dto?.longitude, + MapCategories = dto?.categories?.Select(c => new CategorieDTO + { + id = c.id, + label = c.label, + icon = c.icon, + order = c.order, + }).ToList() ?? new List(), + MapPoints = dto?.points?.Select(p => BuildGeoPoint(p, old.Id)).ToList() + ?? new List(), + }; + FillBase(s); + return s; + } + + case SectionType.Slider: + { + var dto = ParseData(old, opts, report); + var s = new SectionSlider + { + SliderContents = dto?.contents ?? new List(), + }; + FillBase(s); + return s; + } + + case SectionType.Video: + { + var dto = ParseData(old, opts, report); + var s = new SectionVideo { VideoSource = dto?.source ?? "" }; + FillBase(s); + return s; + } + + case SectionType.Web: + { + var dto = ParseData(old, opts, report); + var s = new SectionWeb { WebSource = dto?.source ?? "" }; + FillBase(s); + return s; + } + + case SectionType.Menu: + { + // Les relations MenuSections sont gérées dans LinkMenuSectionsAsync + var s = new SectionMenu { MenuSections = new List
() }; + FillBase(s); + return s; + } + + case SectionType.Quiz: + { + var dto = ParseData(old, opts, report); + var s = new SectionQuiz + { + QuizQuestions = new List(), + QuizBadLevel = dto?.bad_level?.label ?? new List(), + QuizMediumLevel = dto?.medium_level?.label ?? new List(), + QuizGoodLevel = dto?.good_level?.label ?? new List(), + QuizGreatLevel = dto?.great_level?.label ?? new List(), + }; + FillBase(s); + return s; + } + + case SectionType.Article: + { + var dto = ParseData(old, opts, report); + var s = new SectionArticle + { + ArticleContent = dto?.content ?? new List(), + ArticleIsContentTop = dto?.isContentTop ?? false, + ArticleAudioIds = dto?.audioIds ?? new List(), + ArticleIsReadAudioAuto = dto?.isReadAudioAuto ?? false, + ArticleContents = dto?.contents ?? new List(), + }; + FillBase(s); + return s; + } + + case SectionType.PDF: + { + var dto = ParseData(old, opts, report); + var s = new SectionPdf + { + PDFOrderedTranslationAndResources = dto?.pdfs?.Select(p => + new OrderedTranslationAndResourceDTO + { + translationAndResourceDTOs = p.pdfFilesAndTitles ?? new List(), + order = p.order, + }).ToList() ?? new List(), + }; + FillBase(s); + return s; + } + + case SectionType.Game: + { + var dto = ParseData(old, opts, report); + var s = new SectionGame + { + GameMessageDebut = ToTranslationAndResourceList(dto?.messageDebut), + GameMessageFin = ToTranslationAndResourceList(dto?.messageFin), + GamePuzzleImageId = dto?.image?.resourceId, + GamePuzzleRows = dto?.rows ?? 3, + GamePuzzleCols = dto?.cols ?? 3, + GameType = SectionGame.GameTypes.Puzzle, + }; + FillBase(s); + return s; + } + + case SectionType.Agenda: + { + var dto = ParseData(old, opts, report); + var s = new SectionAgenda + { + IsOnlineAgenda = true, + AgendaResourceIds = dto?.resourceIds ?? new List(), + AgendaMapProvider = dto?.mapProvider, + EventAgendas = new List(), + }; + FillBase(s); + return s; + } + + case SectionType.Weather: + { + var dto = ParseData(old, opts, report); + var s = new SectionWeather + { + WeatherCity = dto?.city, + WeatherUpdatedDate = dto?.updatedDate, + WeatherResult = dto?.result, + }; + FillBase(s); + return s; + } + + default: + report.Errors.Add($"Section {old.Id}: unknown SectionType {old.Type} — skipped"); + return null; + } + } + + private GeoPoint BuildGeoPoint(OldGeoPointDTO p, string sectionMapId) + { + Geometry geometry = null; + if (p.latitude != null && p.longitude != null + && double.TryParse(p.latitude, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var lat) + && double.TryParse(p.longitude, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var lon)) + { + geometry = new Point(lon, lat) { SRID = 4326 }; + } + + return new GeoPoint + { + Title = p.title ?? new List(), + Description = p.description ?? new List(), + Contents = p.contents?.Select(c => new ContentDTO + { + resourceId = c.resourceId, + }).ToList() ?? new List(), + CategorieId = p.categorieId ?? p.categorie?.id, + Geometry = geometry, + ImageResourceId = p.imageResourceId, + ImageUrl = p.imageUrl, + Schedules = p.schedules ?? new List(), + Prices = p.prices ?? new List(), + Phone = p.phone ?? new List(), + Email = p.email ?? new List(), + Site = p.site ?? new List(), + SectionMapId = sectionMapId, + }; + } + + // ─── Devices ────────────────────────────────────────────────────────── + + private async Task MigrateDevicesAsync(MigrationReportDTO report, bool dryRun, string filterInstanceId) + { + var instanceIds = filterInstanceId != null + ? new List { filterInstanceId } + : _instanceSvc.GetAll().Select(i => i.Id).ToList(); + var source = instanceIds.SelectMany(id => _deviceSvc.GetAll(id)).ToList(); + + foreach (var old in source) + { + try + { + if (await _db.Devices.AnyAsync(x => x.Id == old.Id)) + { + report.Skipped.Add($"Device {old.Id} ({old.Name}) — already exists"); + continue; + } + + var entity = new Device + { + Id = old.Id, + Identifier = old.Identifier, + Name = old.Name, + IpAddressWLAN = old.IpAddressWLAN, + IpAddressETH = old.IpAddressETH, + ConfigurationId = old.ConfigurationId, + Connected = old.Connected, + DateCreation = old.DateCreation, + DateUpdate = old.DateUpdate < new DateTime(2000, 1, 1) ? DateTime.MinValue : old.DateUpdate, + BatteryLevel = old.BatteryLevel?.ToString(), + LastBatteryLevel = old.LastBatteryLevel, + ConnectionLevel = old.ConnectionLevel?.ToString(), + LastConnectionLevel = old.LastConnectionLevel, + InstanceId = old.InstanceId, + }; + + if (!dryRun) + _db.Devices.Add(entity); + + report.Migrated.Devices++; + } + catch (Exception ex) + { + report.Errors.Add($"Device {old.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Post-processing : relier SectionMenu.MenuSections ─────────────── + + private async Task LinkMenuSectionsAsync(MigrationReportDTO report, bool dryRun) + { + var menus = await _db.Sections + .OfType() + .Include(m => m.MenuSections) + .ToListAsync(); + + foreach (var menu in menus) + { + if (menu.MenuSections.Count > 0) + { + report.Skipped.Add($"MenuLink {menu.Id} — already linked ({menu.MenuSections.Count} children)"); + continue; + } + + var oldSection = _sectionSvc.GetById(menu.Id); + if (oldSection?.Data == null) continue; + + try + { + var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var dto = JsonSerializer.Deserialize(oldSection.Data, opts); + if (dto?.sections == null) continue; + + var childIds = dto.sections.Select(s => s.id).Where(id => id != null).ToList(); + var children = await _db.Sections.Where(s => childIds.Contains(s.Id)).ToListAsync(); + + if (!dryRun) + { + menu.MenuSections = children; + _db.Sections.Update(menu); + } + + report.Migrated.MenuLinks += children.Count; + } + catch (Exception ex) + { + report.Errors.Add($"MenuLink {menu.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Post-processing : enrichir les resource dans les JSONB PDF ───────── + + private async Task EnrichPdfResourcesAsync(MigrationReportDTO report, bool dryRun) + { + var pdfSections = await _db.Sections.OfType().ToListAsync(); + + foreach (var pdf in pdfSections) + { + try + { + bool modified = false; + foreach (var ordered in pdf.PDFOrderedTranslationAndResources ?? new List()) + { + foreach (var tar in ordered.translationAndResourceDTOs ?? new List()) + { + if (tar.resource == null && tar.resourceId != null) + { + var resource = await _db.Resources.FindAsync(tar.resourceId); + if (resource != null) + { + tar.resource = resource.ToDTO(); + modified = true; + } + } + } + } + + if (modified) + { + if (!dryRun) _db.Sections.Update(pdf); + report.Migrated.PdfResourcesEnriched++; + } + } + catch (Exception ex) + { + report.Errors.Add($"EnrichPdf {pdf.Id}: {ex.Message}"); + } + } + + if (!dryRun) await _db.SaveChangesAsync(); + } + + // ─── Helpers ────────────────────────────────────────────────────────── + + private T ParseData(OldSection old, JsonSerializerOptions opts, MigrationReportDTO report) where T : class + { + if (string.IsNullOrEmpty(old.Data)) return null; + try + { + return JsonSerializer.Deserialize(old.Data, opts); + } + catch (Exception ex) + { + report.Errors.Add($"Section {old.Id} — failed to parse Data as {typeof(T).Name}: {ex.Message}"); + return null; + } + } + + private List ToTranslationAndResourceList(List source) + => source ?? new List(); + } + + // ─── DTO du rapport ─────────────────────────────────────────────────────── + + public class MigrationReportDTO + { + public bool DryRun { get; set; } + public MigrationCountsDTO Migrated { get; set; } = new(); + public List Skipped { get; set; } = new(); + public List Errors { get; set; } = new(); + public List Details { get; set; } = new(); + public string FatalError { get; set; } + } + + public class MigrationCountsDTO + { + public int Instances { get; set; } + public int Resources { get; set; } + public int Users { get; set; } + public int Configurations { get; set; } + public int ApplicationInstances { get; set; } + public int AppConfigurationLinks { get; set; } + public int Sections { get; set; } + public int Devices { get; set; } + public int MenuLinks { get; set; } + public int PdfResourcesEnriched { get; set; } + } +} diff --git a/ManagerService/Data/MyInfoMateDbContext.cs b/ManagerService/Data/MyInfoMateDbContext.cs index 37cd625..cfe3323 100644 --- a/ManagerService/Data/MyInfoMateDbContext.cs +++ b/ManagerService/Data/MyInfoMateDbContext.cs @@ -239,6 +239,31 @@ namespace ManagerService.Data .HasForeignKey(ma => ma.SectionEventId) .IsRequired(false) .OnDelete(DeleteBehavior.Cascade); + + // Seed : plans d'abonnement par défaut + modelBuilder.Entity().HasData( + new SubscriptionPlan + { + Id = "plan-starter", + Name = "Starter", + StorageQuotaBytes = 1L * 1024 * 1024 * 1024, // 1 GB + AiRequestsPerMonth = 0, + }, + new SubscriptionPlan + { + Id = "plan-standard", + Name = "Standard", + StorageQuotaBytes = 10L * 1024 * 1024 * 1024, // 10 GB + AiRequestsPerMonth = 100, + }, + new SubscriptionPlan + { + Id = "plan-premium", + Name = "Premium", + StorageQuotaBytes = 50L * 1024 * 1024 * 1024, // 50 GB + AiRequestsPerMonth = 500, + } + ); } } } diff --git a/ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.Designer.cs b/ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.Designer.cs new file mode 100644 index 0000000..d2e2028 --- /dev/null +++ b/ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.Designer.cs @@ -0,0 +1,1505 @@ +// +using System; +using System.Collections.Generic; +using Manager.DTOs; +using ManagerService.DTOs; +using ManagerService.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ManagerService.Migrations +{ + [DbContext(typeof(MyInfoMateDbContext))] + [Migration("20260401152545_SeedSubscriptionPlans")] + partial class SeedSubscriptionPlans + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ManagerService.Data.ApiKey", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("KeyHash") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("ManagerService.Data.AppConfigurationLink", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ApplicationInstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfigurationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDate") + .HasColumnType("boolean"); + + b.Property("IsHour") + .HasColumnType("boolean"); + + b.Property("IsSectionImageBackground") + .HasColumnType("boolean"); + + b.Property("LayoutMainPage") + .HasColumnType("integer"); + + b.Property("LoaderImageId") + .HasColumnType("text"); + + b.Property("LoaderImageUrl") + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PrimaryColor") + .HasColumnType("text"); + + b.Property("RoundedValue") + .HasColumnType("integer"); + + b.Property("ScreenPercentageSectionsMainPage") + .HasColumnType("integer"); + + b.Property("SecondaryColor") + .HasColumnType("text"); + + b.Property("WeightMasonryGrid") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationInstanceId"); + + b.HasIndex("ConfigurationId"); + + b.HasIndex("DeviceId"); + + b.ToTable("AppConfigurationLinks"); + }); + + modelBuilder.Entity("ManagerService.Data.ApplicationInstance", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsAssistant") + .HasColumnType("boolean"); + + b.PrimitiveCollection>("Languages") + .HasColumnType("text[]"); + + b.Property("LayoutMainPage") + .HasColumnType("integer"); + + b.Property("LoaderImageId") + .HasColumnType("text"); + + b.Property("LoaderImageUrl") + .HasColumnType("text"); + + b.Property("MainImageId") + .HasColumnType("text"); + + b.Property("MainImageUrl") + .HasColumnType("text"); + + b.Property("PrimaryColor") + .HasColumnType("text"); + + b.Property("SecondaryColor") + .HasColumnType("text"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("SectionEventId"); + + b.ToTable("ApplicationInstances"); + }); + + modelBuilder.Entity("ManagerService.Data.Configuration", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("ImageId") + .HasColumnType("text"); + + b.Property("ImageSource") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsOffline") + .HasColumnType("boolean"); + + b.Property("IsQRCode") + .HasColumnType("boolean"); + + b.Property("IsSearchNumber") + .HasColumnType("boolean"); + + b.Property("IsSearchText") + .HasColumnType("boolean"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("Languages") + .HasColumnType("text[]"); + + b.Property("LoaderImageId") + .HasColumnType("text"); + + b.Property("LoaderImageUrl") + .HasColumnType("text"); + + b.Property("PrimaryColor") + .HasColumnType("text"); + + b.Property("SecondaryColor") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.ToTable("Configurations"); + }); + + modelBuilder.Entity("ManagerService.Data.Device", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("BatteryLevel") + .HasColumnType("text"); + + b.Property("ConfigurationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Connected") + .HasColumnType("boolean"); + + b.Property("ConnectionLevel") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IpAddressETH") + .HasColumnType("text"); + + b.Property("IpAddressWLAN") + .HasColumnType("text"); + + b.Property("LastBatteryLevel") + .HasColumnType("timestamp with time zone"); + + b.Property("LastConnectionLevel") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ConfigurationId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("ManagerService.Data.Instance", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AiRequestsThisMonth") + .HasColumnType("integer"); + + b.Property("AiUsageMonthKey") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAssistant") + .HasColumnType("boolean"); + + b.Property("IsMobile") + .HasColumnType("boolean"); + + b.Property("IsPushNotification") + .HasColumnType("boolean"); + + b.Property("IsStatistic") + .HasColumnType("boolean"); + + b.Property("IsTablet") + .HasColumnType("boolean"); + + b.Property("IsVR") + .HasColumnType("boolean"); + + b.Property("IsWeb") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PinCode") + .HasColumnType("text"); + + b.Property("SubscriptionPlanId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionPlanId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("ManagerService.Data.PushNotification", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("HangfireJobId") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ScheduledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.ToTable("PushNotifications"); + }); + + modelBuilder.Entity("ManagerService.Data.Resource", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("Url") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Resources"); + }); + + modelBuilder.Entity("ManagerService.Data.Section", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("BeaconId") + .HasColumnType("integer"); + + b.Property("ConfigurationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("jsonb"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ImageId") + .HasColumnType("text"); + + b.Property("ImageSource") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsBeacon") + .HasColumnType("boolean"); + + b.Property("IsSubSection") + .HasColumnType("boolean"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("Latitude") + .HasColumnType("text"); + + b.Property("Longitude") + .HasColumnType("text"); + + b.Property("MeterZoneGPS") + .HasColumnType("integer"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("ParentId") + .HasColumnType("text"); + + b.Property("SectionMenuId") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SectionMenuId"); + + b.ToTable("Sections"); + + b.HasDiscriminator().HasValue("Base"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.EventAgenda", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("jsonb"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone"); + + b.Property("DateFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTo") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("IdVideoYoutube") + .HasColumnType("text"); + + b.Property("IsSynced") + .HasColumnType("boolean"); + + b.Property("Label") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("ResourceId") + .HasColumnType("text"); + + b.Property("SectionAgendaId") + .HasColumnType("text"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("text"); + + b.Property("VideoLink") + .HasColumnType("text"); + + b.Property("VideoResourceId") + .HasColumnType("text"); + + b.Property("Website") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ResourceId"); + + b.HasIndex("SectionAgendaId"); + + b.HasIndex("SectionEventId"); + + b.HasIndex("VideoResourceId"); + + b.ToTable("EventAgendas"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GeoPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategorieId") + .HasColumnType("integer"); + + b.Property("Contents") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Description") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Email") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Geometry") + .HasColumnType("geometry"); + + b.Property("ImageResourceId") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PolyColor") + .HasColumnType("text"); + + b.Property("Prices") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Schedules") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("SectionMapId") + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("SectionEventId"); + + b.HasIndex("SectionMapId"); + + b.ToTable("GeoPoints"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedPath", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("jsonb"); + + b.Property("HideNextStepsUntilComplete") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsLinear") + .HasColumnType("boolean"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("RequireSuccessToAdvance") + .HasColumnType("boolean"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("SectionGameId") + .HasColumnType("text"); + + b.Property("SectionMapId") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("SectionEventId"); + + b.HasIndex("SectionGameId"); + + b.HasIndex("SectionMapId"); + + b.ToTable("GuidedPaths"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedStep", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("jsonb"); + + b.Property("Geometry") + .HasColumnType("geometry"); + + b.Property("GuidedPathId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasColumnType("text"); + + b.Property("IsHiddenInitially") + .HasColumnType("boolean"); + + b.Property("IsStepLocked") + .HasColumnType("boolean"); + + b.Property("IsStepTimer") + .HasColumnType("boolean"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("TimerExpiredMessage") + .HasColumnType("jsonb"); + + b.Property("TimerSeconds") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("TriggerGeoPointId") + .HasColumnType("integer"); + + b.Property("ZoneRadiusMeters") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("GuidedPathId"); + + b.HasIndex("TriggerGeoPointId"); + + b.ToTable("GuidedSteps"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.QuizQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GuidedStepId") + .HasColumnType("text"); + + b.Property("IsSlidingPuzzle") + .HasColumnType("boolean"); + + b.Property>("Label") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PuzzleCols") + .HasColumnType("integer"); + + b.Property("PuzzleImageId") + .HasColumnType("text"); + + b.Property("PuzzleRows") + .HasColumnType("integer"); + + b.Property("ResourceId") + .HasColumnType("text"); + + b.Property>("Responses") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SectionQuizId") + .HasColumnType("text"); + + b.Property("ValidationQuestionType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GuidedStepId"); + + b.HasIndex("PuzzleImageId"); + + b.HasIndex("ResourceId"); + + b.HasIndex("SectionQuizId"); + + b.ToTable("QuizQuestions"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+MapAnnotation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Geometry") + .HasColumnType("geometry"); + + b.Property("GeometryType") + .HasColumnType("integer"); + + b.Property("Icon") + .HasColumnType("text"); + + b.Property("IconResourceId") + .HasColumnType("text"); + + b.Property>("Label") + .HasColumnType("jsonb"); + + b.Property("PolyColor") + .HasColumnType("text"); + + b.Property("ProgrammeBlockId") + .HasColumnType("text"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property>("Type") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("IconResourceId"); + + b.HasIndex("ProgrammeBlockId"); + + b.HasIndex("SectionEventId"); + + b.ToTable("MapAnnotations"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property>("Description") + .HasColumnType("jsonb"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SectionEventId") + .HasColumnType("text"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property>("Title") + .HasColumnType("jsonb"); + + b.HasKey("Id"); + + b.HasIndex("SectionEventId"); + + b.ToTable("ProgrammeBlocks"); + }); + + modelBuilder.Entity("ManagerService.Data.SubscriptionPlan", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AiRequestsPerMonth") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("StorageQuotaBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("SubscriptionPlans"); + + b.HasData( + new + { + Id = "plan-starter", + AiRequestsPerMonth = 0, + Name = "Starter", + StorageQuotaBytes = 1073741824L + }, + new + { + Id = "plan-standard", + AiRequestsPerMonth = 100, + Name = "Standard", + StorageQuotaBytes = 10737418240L + }, + new + { + Id = "plan-premium", + AiRequestsPerMonth = 500, + Name = "Premium", + StorageQuotaBytes = 53687091200L + }); + }); + + modelBuilder.Entity("ManagerService.Data.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DateCreation") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ManagerService.Data.VisitEvent", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AppType") + .HasColumnType("integer"); + + b.Property("ConfigurationId") + .HasColumnType("text"); + + b.Property("DurationSeconds") + .HasColumnType("integer"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("InstanceId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("SectionId") + .HasColumnType("text"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Timestamp"); + + b.ToTable("VisitEvents"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionAgenda", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("AgendaMapProvider") + .HasColumnType("integer"); + + b.Property>("AgendaResourceIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("IsOnlineAgenda") + .HasColumnType("boolean"); + + b.HasDiscriminator().HasValue("Agenda"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionArticle", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("ArticleAudioIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("ArticleContent") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("ArticleContents") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ArticleIsContentTop") + .HasColumnType("boolean"); + + b.Property("ArticleIsReadAudioAuto") + .HasColumnType("boolean"); + + b.HasDiscriminator().HasValue("Article"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("BaseSectionMapId") + .HasColumnType("text"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property>("ParcoursIds") + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.HasIndex("BaseSectionMapId"); + + b.HasDiscriminator().HasValue("Event"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionGame", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("GameMessageDebut") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("GameMessageFin") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("GamePuzzleCols") + .HasColumnType("integer"); + + b.Property("GamePuzzleImageId") + .HasColumnType("text"); + + b.Property("GamePuzzleRows") + .HasColumnType("integer"); + + b.Property("GameType") + .HasColumnType("integer"); + + b.HasIndex("GamePuzzleImageId"); + + b.HasDiscriminator().HasValue("Game"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("IsListViewEnabled") + .HasColumnType("boolean"); + + b.Property>("MapCategories") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("MapCenterLatitude") + .HasColumnType("text"); + + b.Property("MapCenterLongitude") + .HasColumnType("text"); + + b.Property("MapMapProvider") + .HasColumnType("integer"); + + b.Property("MapMapType") + .HasColumnType("integer"); + + b.Property("MapResourceId") + .HasColumnType("text"); + + b.Property("MapTypeMapbox") + .HasColumnType("integer"); + + b.Property("MapZoom") + .HasColumnType("integer"); + + b.HasIndex("MapResourceId"); + + b.HasDiscriminator().HasValue("Map"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMenu", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.HasDiscriminator().HasValue("Menu"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionPdf", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("PDFOrderedTranslationAndResources") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasDiscriminator().HasValue("PDF"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionQuiz", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("QuizBadLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("QuizGoodLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("QuizGreatLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property>("QuizMediumLevel") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasDiscriminator().HasValue("Quiz"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionSlider", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property>("SliderContents") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasDiscriminator().HasValue("Slider"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionVideo", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("VideoSource") + .IsRequired() + .HasColumnType("text"); + + b.HasDiscriminator().HasValue("Video"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionWeather", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("WeatherCity") + .HasColumnType("text"); + + b.Property("WeatherResult") + .HasColumnType("text"); + + b.Property("WeatherUpdatedDate") + .HasColumnType("timestamp with time zone"); + + b.HasDiscriminator().HasValue("Weather"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionWeb", b => + { + b.HasBaseType("ManagerService.Data.Section"); + + b.Property("WebSource") + .IsRequired() + .HasColumnType("text"); + + b.HasDiscriminator().HasValue("Web"); + }); + + modelBuilder.Entity("ManagerService.Data.ApiKey", b => + { + b.HasOne("ManagerService.Data.Instance", "Instance") + .WithMany() + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("ManagerService.Data.AppConfigurationLink", b => + { + b.HasOne("ManagerService.Data.ApplicationInstance", "ApplicationInstance") + .WithMany("Configurations") + .HasForeignKey("ApplicationInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ManagerService.Data.Configuration", "Configuration") + .WithMany() + .HasForeignKey("ConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ManagerService.Data.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId"); + + b.Navigation("ApplicationInstance"); + + b.Navigation("Configuration"); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("ManagerService.Data.ApplicationInstance", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.Navigation("SectionEvent"); + }); + + modelBuilder.Entity("ManagerService.Data.Device", b => + { + b.HasOne("ManagerService.Data.Configuration", "Configuration") + .WithMany() + .HasForeignKey("ConfigurationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Configuration"); + }); + + modelBuilder.Entity("ManagerService.Data.Instance", b => + { + b.HasOne("ManagerService.Data.SubscriptionPlan", "SubscriptionPlan") + .WithMany() + .HasForeignKey("SubscriptionPlanId"); + + b.Navigation("SubscriptionPlan"); + }); + + modelBuilder.Entity("ManagerService.Data.Section", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionMenu", null) + .WithMany("MenuSections") + .HasForeignKey("SectionMenuId"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.EventAgenda", b => + { + b.HasOne("ManagerService.Data.Resource", "Resource") + .WithMany() + .HasForeignKey("ResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionAgenda", "SectionAgenda") + .WithMany("EventAgendas") + .HasForeignKey("SectionAgendaId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.HasOne("ManagerService.Data.Resource", "VideoResource") + .WithMany() + .HasForeignKey("VideoResourceId"); + + b.Navigation("Resource"); + + b.Navigation("SectionAgenda"); + + b.Navigation("SectionEvent"); + + b.Navigation("VideoResource"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GeoPoint", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.HasOne("ManagerService.Data.SubSection.SectionMap", "SectionMap") + .WithMany("MapPoints") + .HasForeignKey("SectionMapId"); + + b.Navigation("SectionEvent"); + + b.Navigation("SectionMap"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedPath", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", "SectionEvent") + .WithMany() + .HasForeignKey("SectionEventId"); + + b.HasOne("ManagerService.Data.SubSection.SectionGame", "SectionGame") + .WithMany() + .HasForeignKey("SectionGameId"); + + b.HasOne("ManagerService.Data.SubSection.SectionMap", "SectionMap") + .WithMany() + .HasForeignKey("SectionMapId"); + + b.Navigation("SectionEvent"); + + b.Navigation("SectionGame"); + + b.Navigation("SectionMap"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedStep", b => + { + b.HasOne("ManagerService.Data.SubSection.GuidedPath", "GuidedPath") + .WithMany("Steps") + .HasForeignKey("GuidedPathId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ManagerService.Data.SubSection.GeoPoint", "TriggerGeoPoint") + .WithMany() + .HasForeignKey("TriggerGeoPointId"); + + b.Navigation("GuidedPath"); + + b.Navigation("TriggerGeoPoint"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.QuizQuestion", b => + { + b.HasOne("ManagerService.Data.SubSection.GuidedStep", "GuidedStep") + .WithMany("QuizQuestions") + .HasForeignKey("GuidedStepId"); + + b.HasOne("ManagerService.Data.Resource", "PuzzleImage") + .WithMany() + .HasForeignKey("PuzzleImageId"); + + b.HasOne("ManagerService.Data.Resource", "Resource") + .WithMany() + .HasForeignKey("ResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionQuiz", "SectionQuiz") + .WithMany("QuizQuestions") + .HasForeignKey("SectionQuizId"); + + b.Navigation("GuidedStep"); + + b.Navigation("PuzzleImage"); + + b.Navigation("Resource"); + + b.Navigation("SectionQuiz"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+MapAnnotation", b => + { + b.HasOne("ManagerService.Data.Resource", "IconResource") + .WithMany() + .HasForeignKey("IconResourceId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", null) + .WithMany("MapAnnotations") + .HasForeignKey("ProgrammeBlockId"); + + b.HasOne("ManagerService.Data.SubSection.SectionEvent", null) + .WithMany("GlobalMapAnnotations") + .HasForeignKey("SectionEventId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("IconResource"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionEvent", null) + .WithMany("Programme") + .HasForeignKey("SectionEventId"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent", b => + { + b.HasOne("ManagerService.Data.SubSection.SectionMap", "BaseMap") + .WithMany() + .HasForeignKey("BaseSectionMapId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("BaseMap"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionGame", b => + { + b.HasOne("ManagerService.Data.Resource", "GamePuzzleImage") + .WithMany() + .HasForeignKey("GamePuzzleImageId"); + + b.Navigation("GamePuzzleImage"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => + { + b.HasOne("ManagerService.Data.Resource", "MapResource") + .WithMany() + .HasForeignKey("MapResourceId"); + + b.Navigation("MapResource"); + }); + + modelBuilder.Entity("ManagerService.Data.ApplicationInstance", b => + { + b.Navigation("Configurations"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedPath", b => + { + b.Navigation("Steps"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.GuidedStep", b => + { + b.Navigation("QuizQuestions"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent+ProgrammeBlock", b => + { + b.Navigation("MapAnnotations"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionAgenda", b => + { + b.Navigation("EventAgendas"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionEvent", b => + { + b.Navigation("GlobalMapAnnotations"); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => + { + b.Navigation("MapPoints"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionMenu", b => + { + b.Navigation("MenuSections"); + }); + + modelBuilder.Entity("ManagerService.Data.SubSection.SectionQuiz", b => + { + b.Navigation("QuizQuestions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.cs b/ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.cs new file mode 100644 index 0000000..13b9745 --- /dev/null +++ b/ManagerService/Migrations/20260401152545_SeedSubscriptionPlans.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace ManagerService.Migrations +{ + /// + public partial class SeedSubscriptionPlans : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "SubscriptionPlans", + columns: new[] { "Id", "AiRequestsPerMonth", "Name", "StorageQuotaBytes" }, + values: new object[,] + { + { "plan-premium", 500, "Premium", 53687091200L }, + { "plan-standard", 100, "Standard", 10737418240L }, + { "plan-starter", 0, "Starter", 1073741824L } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-premium"); + + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-standard"); + + migrationBuilder.DeleteData( + table: "SubscriptionPlans", + keyColumn: "Id", + keyValue: "plan-starter"); + } + } +} diff --git a/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs b/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs index a90e587..c140af9 100644 --- a/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs +++ b/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs @@ -889,6 +889,29 @@ namespace ManagerService.Migrations b.HasKey("Id"); b.ToTable("SubscriptionPlans"); + + b.HasData( + new + { + Id = "plan-starter", + AiRequestsPerMonth = 0, + Name = "Starter", + StorageQuotaBytes = 1073741824L + }, + new + { + Id = "plan-standard", + AiRequestsPerMonth = 100, + Name = "Standard", + StorageQuotaBytes = 10737418240L + }, + new + { + Id = "plan-premium", + AiRequestsPerMonth = 500, + Name = "Premium", + StorageQuotaBytes = 53687091200L + }); }); modelBuilder.Entity("ManagerService.Data.User", b =>