diff --git a/ManagerService/Controllers/InstanceController.cs b/ManagerService/Controllers/InstanceController.cs
index c1c32de..5109257 100644
--- a/ManagerService/Controllers/InstanceController.cs
+++ b/ManagerService/Controllers/InstanceController.cs
@@ -137,7 +137,11 @@ namespace ManagerService.Controllers
if (_myInfoMateDbContext.Instances.Any(i => i.Name == instance.Name))
throw new InvalidOperationException("This name is already used");
- //OldInstance instanceCreated = _instanceService.Create(newInstance);
+ instance.WebSlug = GenerateUniqueSlug(instance.Name);
+ instance.PublicApiKey = "ap_" + Convert.ToBase64String(
+ System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
+ .Replace("+", "-").Replace("/", "_").TrimEnd('=');
+
_myInfoMateDbContext.Instances.Add(instance);
_myInfoMateDbContext.SaveChanges();
@@ -218,6 +222,38 @@ namespace ManagerService.Controllers
}
}
+ ///
+ /// Get Instance by web slug (public, used by visitapp-web)
+ ///
+ /// Web slug of the instance
+ [AllowAnonymous]
+ [ProducesResponseType(typeof(InstanceDTO), 200)]
+ [ProducesResponseType(typeof(string), 404)]
+ [ProducesResponseType(typeof(string), 500)]
+ [HttpGet("slug/{slug}")]
+ public ObjectResult GetInstanceBySlug(string slug)
+ {
+ try
+ {
+ Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.WebSlug == slug);
+
+ if (instance == null)
+ throw new KeyNotFoundException("Instance was not found");
+
+ var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
+
+ return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
+ }
+ catch (KeyNotFoundException ex)
+ {
+ return new NotFoundObjectResult(ex.Message) { };
+ }
+ catch (Exception ex)
+ {
+ return new ObjectResult(ex.Message) { StatusCode = 500 };
+ }
+ }
+
///
/// Get Instance by pincode
///
@@ -385,5 +421,59 @@ namespace ManagerService.Controllers
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
+ ///
+ /// Generate (or regenerate) WebSlug and PublicApiKey for an existing instance
+ ///
+ /// Id of the instance
+ [ProducesResponseType(typeof(InstanceDTO), 200)]
+ [ProducesResponseType(typeof(string), 404)]
+ [ProducesResponseType(typeof(string), 500)]
+ [HttpPost("{id}/generate-web-keys")]
+ public ObjectResult GenerateWebKeys(string id)
+ {
+ try
+ {
+ var instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.Id == id);
+ if (instance == null)
+ return new NotFoundObjectResult("Instance not found");
+
+ if (string.IsNullOrEmpty(instance.WebSlug))
+ instance.WebSlug = GenerateUniqueSlug(instance.Name);
+
+ instance.PublicApiKey = "ap_" + Convert.ToBase64String(
+ System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
+ .Replace("+", "-").Replace("/", "_").TrimEnd('=');
+
+ _myInfoMateDbContext.SaveChanges();
+
+ var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
+ return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
+ }
+ catch (Exception ex)
+ {
+ return new ObjectResult(ex.Message) { StatusCode = 500 };
+ }
+ }
+
+ private string GenerateUniqueSlug(string name)
+ {
+ var slug = name.ToLowerInvariant();
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[àáâãäå]", "a");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[èéêë]", "e");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ìíîï]", "i");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[òóôõö]", "o");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ùúûü]", "u");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ç]", "c");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ñ]", "n");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[^a-z0-9\s-]", "");
+ slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[\s-]+", "-").Trim('-');
+
+ var baseSlug = slug;
+ var counter = 1;
+ while (_myInfoMateDbContext.Instances.Any(i => i.WebSlug == slug))
+ slug = $"{baseSlug}-{counter++}";
+
+ return slug;
+ }
}
}
diff --git a/ManagerService/Controllers/SectionController.cs b/ManagerService/Controllers/SectionController.cs
index 2bce333..3522b4f 100644
--- a/ManagerService/Controllers/SectionController.cs
+++ b/ManagerService/Controllers/SectionController.cs
@@ -197,7 +197,7 @@ namespace ManagerService.Controllers
switch (section.Type)
{
case SectionType.Agenda:
- var eventAgendas = _myInfoMateDbContext.EventAgendas.Where(ea => ea.SectionAgendaId == section.Id)/*.OrderBy(gp => gp.or)*/.ToList();
+ var eventAgendas = _myInfoMateDbContext.EventAgendas.Where(ea => ea.SectionAgendaId == section.Id).Include(ea => ea.Resource).ToList();
List eventAgendaDTOs = new List();
foreach (var eventAgenda in eventAgendas)
{
@@ -213,6 +213,20 @@ namespace ManagerService.Controllers
(dto as SectionEventDTO).Programme = sectionEvent.Programme; // TODO test ! Need dto ?
(dto as SectionEventDTO).GlobalMapAnnotations = sectionEvent.GlobalMapAnnotations?.Select(ma => ma.ToDTO()).ToList() ?? new();
break;
+ case SectionType.Slider:
+ var sliderContents = (dto as SliderDTO).contents;
+ if (sliderContents != null)
+ {
+ foreach (var content in sliderContents)
+ {
+ if (content.resourceId != null && content.resource == null)
+ {
+ var contentResource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == content.resourceId);
+ content.resource = contentResource?.ToDTO();
+ }
+ }
+ }
+ break;
case SectionType.Game:
Resource resource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (dto as GameDTO).puzzleImageId);
(dto as GameDTO).puzzleImage = resource.ToDTO();
@@ -267,6 +281,20 @@ namespace ManagerService.Controllers
var subDTO = SectionFactory.ToDTO(subSection);
switch (subSection.Type)
{
+ case SectionType.Slider:
+ var sliderContentsSub = (subDTO as SliderDTO).contents;
+ if (sliderContentsSub != null)
+ {
+ foreach (var content in sliderContentsSub)
+ {
+ if (content.resourceId != null && content.resource == null)
+ {
+ var contentResource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == content.resourceId);
+ content.resource = contentResource?.ToDTO();
+ }
+ }
+ }
+ break;
case SectionType.Game:
Resource resourceSub = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (subDTO as GameDTO).puzzleImageId);
(subDTO as GameDTO).puzzleImage = resourceSub?.ToDTO();
diff --git a/ManagerService/DTOs/InstanceDTO.cs b/ManagerService/DTOs/InstanceDTO.cs
index 4e40bf3..9434ed0 100644
--- a/ManagerService/DTOs/InstanceDTO.cs
+++ b/ManagerService/DTOs/InstanceDTO.cs
@@ -18,6 +18,10 @@ namespace ManagerService.DTOs
public bool? isAssistant { get; set; }
+ public string? webSlug { get; set; }
+
+ public string? publicApiKey { get; set; }
+
public string? subscriptionPlanId { get; set; }
public SubscriptionPlanDTO? subscriptionPlan { get; set; }
public int? aiRequestsThisMonth { get; set; }
diff --git a/ManagerService/DTOs/RemoteEventAgendaDTO.cs b/ManagerService/DTOs/RemoteEventAgendaDTO.cs
index 9ef4b3f..d8182e6 100644
--- a/ManagerService/DTOs/RemoteEventAgendaDTO.cs
+++ b/ManagerService/DTOs/RemoteEventAgendaDTO.cs
@@ -72,8 +72,8 @@ namespace ManagerService.DTOs
// Handle YYYYMMDD format (e.g. "20260327")
if (dateStr.Length == 8 && long.TryParse(dateStr, out _))
if (DateTime.TryParseExact(dateStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var dt8))
- return dt8;
- if (DateTime.TryParse(dateStr, out var dt)) return dt;
+ return DateTime.SpecifyKind(dt8, DateTimeKind.Utc);
+ if (DateTime.TryParse(dateStr, out var dt)) return DateTime.SpecifyKind(dt, DateTimeKind.Utc);
return null;
}
}
diff --git a/ManagerService/Data/Instance.cs b/ManagerService/Data/Instance.cs
index 28db7f8..2ae64d8 100644
--- a/ManagerService/Data/Instance.cs
+++ b/ManagerService/Data/Instance.cs
@@ -36,6 +36,10 @@ namespace ManagerService.Data
public bool IsAssistant { get; set; }
+ public string? WebSlug { get; set; }
+
+ public string? PublicApiKey { get; set; }
+
public string? SubscriptionPlanId { get; set; }
[ForeignKey("SubscriptionPlanId")]
@@ -71,6 +75,8 @@ namespace ManagerService.Data
isWeb = IsWeb,
isVR = IsVR,
isAssistant = IsAssistant,
+ webSlug = WebSlug,
+ publicApiKey = PublicApiKey,
subscriptionPlanId = SubscriptionPlanId,
subscriptionPlan = SubscriptionPlan?.ToDTO(),
aiRequestsThisMonth = AiRequestsThisMonth,
@@ -96,6 +102,10 @@ namespace ManagerService.Data
IsWeb = instanceDTO.isWeb ?? false;
IsVR = instanceDTO.isVR ?? false;
IsAssistant = instanceDTO.isAssistant ?? false;
+ if (instanceDTO.webSlug != null)
+ WebSlug = instanceDTO.webSlug;
+ if (instanceDTO.publicApiKey != null)
+ PublicApiKey = instanceDTO.publicApiKey;
if (instanceDTO.subscriptionPlanId != null)
SubscriptionPlanId = instanceDTO.subscriptionPlanId;
if (instanceDTO.storageQuotaBytes.HasValue)
diff --git a/ManagerService/Migrations/20260428113203_AddWebSlugAndPublicApiKeyToInstance.Designer.cs b/ManagerService/Migrations/20260428113203_AddWebSlugAndPublicApiKeyToInstance.Designer.cs
new file mode 100644
index 0000000..2e2eb4a
--- /dev/null
+++ b/ManagerService/Migrations/20260428113203_AddWebSlugAndPublicApiKeyToInstance.Designer.cs
@@ -0,0 +1,1584 @@
+//
+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("20260428113203_AddWebSlugAndPublicApiKeyToInstance")]
+ partial class AddWebSlugAndPublicApiKeyToInstance
+ {
+ ///
+ 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.AuditLog", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Action")
+ .HasColumnType("text");
+
+ b.Property("EntityId")
+ .HasColumnType("text");
+
+ b.Property("EntityType")
+ .HasColumnType("text");
+
+ b.Property("InstanceId")
+ .HasColumnType("text");
+
+ b.Property("NewValues")
+ .HasColumnType("text");
+
+ b.Property("OldValues")
+ .HasColumnType("text");
+
+ b.Property("Timestamp")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("AuditLogs");
+ });
+
+ 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("AppVersion")
+ .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("LastSeen")
+ .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("AiRequestsPerMonth")
+ .HasColumnType("integer");
+
+ b.Property("AiRequestsThisMonth")
+ .HasColumnType("integer");
+
+ b.Property("AiUsageMonthKey")
+ .HasColumnType("text");
+
+ b.Property("DateCreation")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("HasAdvancedStats")
+ .HasColumnType("boolean");
+
+ b.Property("HasStats")
+ .HasColumnType("boolean");
+
+ 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("PublicApiKey")
+ .HasColumnType("text");
+
+ b.Property("StatsHistoryDays")
+ .HasColumnType("integer");
+
+ b.Property("StorageQuotaBytes")
+ .HasColumnType("bigint");
+
+ b.Property("SubscriptionPlanId")
+ .HasColumnType("text");
+
+ b.Property("WebSlug")
+ .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("HasAdvancedStats")
+ .HasColumnType("boolean");
+
+ b.Property("HasStats")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StatsHistoryDays")
+ .HasColumnType("integer");
+
+ b.Property("StorageQuotaBytes")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.ToTable("SubscriptionPlans");
+
+ b.HasData(
+ new
+ {
+ Id = "plan-starter",
+ AiRequestsPerMonth = 0,
+ HasAdvancedStats = false,
+ HasStats = false,
+ Name = "Starter",
+ StatsHistoryDays = 30,
+ StorageQuotaBytes = 1073741824L
+ },
+ new
+ {
+ Id = "plan-standard",
+ AiRequestsPerMonth = 500,
+ HasAdvancedStats = false,
+ HasStats = true,
+ Name = "Standard",
+ StatsHistoryDays = 30,
+ StorageQuotaBytes = 10737418240L
+ },
+ new
+ {
+ Id = "plan-premium",
+ AiRequestsPerMonth = 2000,
+ HasAdvancedStats = true,
+ HasStats = true,
+ Name = "Premium",
+ StatsHistoryDays = 0,
+ 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