diff --git a/ManagerService/Controllers/AiController.cs b/ManagerService/Controllers/AiController.cs index 9f071b3..523f4d9 100644 --- a/ManagerService/Controllers/AiController.cs +++ b/ManagerService/Controllers/AiController.cs @@ -53,6 +53,15 @@ namespace ManagerService.Controllers if (appInstance == null || !appInstance.IsAssistant) return Forbid(); + var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); + if (instance.AiUsageMonthKey != monthKey) + { + instance.AiRequestsThisMonth = 0; + instance.AiUsageMonthKey = monthKey; + } + instance.AiRequestsThisMonth++; + _context.SaveChanges(); + var result = await _assistantService.ChatAsync(request); return Ok(result); } diff --git a/ManagerService/Controllers/InstanceController.cs b/ManagerService/Controllers/InstanceController.cs index 94f629f..83fdbf1 100644 --- a/ManagerService/Controllers/InstanceController.cs +++ b/ManagerService/Controllers/InstanceController.cs @@ -10,6 +10,7 @@ using ManagerService.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; using NSwag.Annotations; namespace ManagerService.Controllers @@ -170,6 +171,10 @@ namespace ManagerService.Controllers instance.DateCreation = updatedInstance.dateCreation != null ? updatedInstance.dateCreation.Value : instance.DateCreation; instance.Name= updatedInstance.name != null ? updatedInstance.name : instance.Name; instance.PinCode = updatedInstance.pinCode != null ? updatedInstance.pinCode : instance.PinCode; + if (updatedInstance.subscriptionPlanId == "") + instance.SubscriptionPlanId = null; + else if (updatedInstance.subscriptionPlanId != null) + instance.SubscriptionPlanId = updatedInstance.subscriptionPlanId; //OldInstance instanceModified = _instanceService.Update(updatedInstance.Id, instance); _myInfoMateDbContext.SaveChanges(); @@ -253,6 +258,47 @@ namespace ManagerService.Controllers } } + /// + /// Get quota usage for an instance + /// + /// Id instance + [Authorize(Policy = ManagerService.Service.Security.Policies.Viewer)] + [ProducesResponseType(typeof(InstanceQuotaDTO), 200)] + [ProducesResponseType(typeof(string), 404)] + [ProducesResponseType(typeof(string), 500)] + [HttpGet("{id}/quota")] + public ObjectResult GetQuota(string id) + { + try + { + var instance = _myInfoMateDbContext.Instances + .Include(i => i.SubscriptionPlan) + .FirstOrDefault(i => i.Id == id); + + if (instance == null) + return new NotFoundObjectResult("Instance not found"); + + var storageUsed = _myInfoMateDbContext.Resources + .Where(r => r.InstanceId == id) + .Sum(r => (long?)r.SizeBytes) ?? 0; + + var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); + var aiUsed = instance.AiUsageMonthKey == monthKey ? instance.AiRequestsThisMonth : 0; + + return new OkObjectResult(new InstanceQuotaDTO + { + storageUsedBytes = storageUsed, + storageQuotaBytes = instance.SubscriptionPlan?.StorageQuotaBytes ?? 0, + aiRequestsUsed = aiUsed, + aiRequestsPerMonth = instance.SubscriptionPlan?.AiRequestsPerMonth ?? 0 + }); + } + catch (Exception ex) + { + return new ObjectResult(ex.Message) { StatusCode = 500 }; + } + } + /// /// Delete an instance /// diff --git a/ManagerService/Controllers/ResourceController.cs b/ManagerService/Controllers/ResourceController.cs index 01151b8..4103818 100644 --- a/ManagerService/Controllers/ResourceController.cs +++ b/ManagerService/Controllers/ResourceController.cs @@ -269,6 +269,7 @@ namespace ManagerService.Controllers resource.DateCreation = DateTime.Now.ToUniversalTime(); resource.InstanceId = instanceId; resource.Id = idService.GenerateHexId(); + resource.SizeBytes = file.Length; _myInfoMateDbContext.Add(resource); _myInfoMateDbContext.SaveChanges(); diff --git a/ManagerService/Controllers/SubscriptionPlanController.cs b/ManagerService/Controllers/SubscriptionPlanController.cs new file mode 100644 index 0000000..156317b --- /dev/null +++ b/ManagerService/Controllers/SubscriptionPlanController.cs @@ -0,0 +1,150 @@ +using ManagerService.Data; +using ManagerService.DTOs; +using ManagerService.Helpers; +using ManagerService.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NSwag.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ManagerService.Controllers +{ + [Authorize(Policy = ManagerService.Service.Security.Policies.SuperAdmin)] + [ApiController, Route("api/[controller]")] + [OpenApiTag("SubscriptionPlan", Description = "Subscription plan management")] + public class SubscriptionPlanController : ControllerBase + { + private readonly MyInfoMateDbContext _myInfoMateDbContext; + private readonly ILogger _logger; + IHexIdGeneratorService idService = new HexIdGeneratorService(); + + public SubscriptionPlanController(ILogger logger, MyInfoMateDbContext myInfoMateDbContext) + { + _logger = logger; + _myInfoMateDbContext = myInfoMateDbContext; + } + + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(string), 500)] + [HttpGet] + public ObjectResult Get() + { + try + { + var plans = _myInfoMateDbContext.SubscriptionPlans.ToList(); + return new OkObjectResult(plans.Select(p => p.ToDTO()).ToList()); + } + catch (Exception ex) + { + return new ObjectResult(ex.Message) { StatusCode = 500 }; + } + } + + [ProducesResponseType(typeof(SubscriptionPlanDTO), 200)] + [ProducesResponseType(typeof(string), 404)] + [ProducesResponseType(typeof(string), 500)] + [HttpGet("{id}")] + public ObjectResult GetById(string id) + { + try + { + var plan = _myInfoMateDbContext.SubscriptionPlans.FirstOrDefault(p => p.Id == id); + if (plan == null) + return new NotFoundObjectResult("Subscription plan not found"); + + return new OkObjectResult(plan.ToDTO()); + } + catch (Exception ex) + { + return new ObjectResult(ex.Message) { StatusCode = 500 }; + } + } + + [ProducesResponseType(typeof(SubscriptionPlanDTO), 200)] + [ProducesResponseType(typeof(string), 400)] + [ProducesResponseType(typeof(string), 500)] + [HttpPost] + public ObjectResult Create([FromBody] SubscriptionPlanDTO dto) + { + try + { + if (dto == null) + return new BadRequestObjectResult("Plan data is required"); + + var plan = new SubscriptionPlan().FromDTO(dto); + plan.Id = idService.GenerateHexId(); + + _myInfoMateDbContext.SubscriptionPlans.Add(plan); + _myInfoMateDbContext.SaveChanges(); + + return new OkObjectResult(plan.ToDTO()); + } + catch (Exception ex) + { + return new ObjectResult(ex.Message) { StatusCode = 500 }; + } + } + + [ProducesResponseType(typeof(SubscriptionPlanDTO), 200)] + [ProducesResponseType(typeof(string), 400)] + [ProducesResponseType(typeof(string), 404)] + [ProducesResponseType(typeof(string), 500)] + [HttpPut] + public ObjectResult Update([FromBody] SubscriptionPlanDTO dto) + { + try + { + if (dto == null || dto.id == null) + return new BadRequestObjectResult("Plan id is required"); + + var plan = _myInfoMateDbContext.SubscriptionPlans.FirstOrDefault(p => p.Id == dto.id); + if (plan == null) + return new NotFoundObjectResult("Subscription plan not found"); + + plan.FromDTO(dto); + _myInfoMateDbContext.SaveChanges(); + + return new OkObjectResult(plan.ToDTO()); + } + catch (Exception ex) + { + return new ObjectResult(ex.Message) { StatusCode = 500 }; + } + } + + [ProducesResponseType(typeof(string), 202)] + [ProducesResponseType(typeof(string), 400)] + [ProducesResponseType(typeof(string), 404)] + [ProducesResponseType(typeof(string), 409)] + [ProducesResponseType(typeof(string), 500)] + [HttpDelete("{id}")] + public ObjectResult Delete(string id) + { + try + { + if (id == null) + return new BadRequestObjectResult("Plan id is required"); + + var plan = _myInfoMateDbContext.SubscriptionPlans.FirstOrDefault(p => p.Id == id); + if (plan == null) + return new NotFoundObjectResult("Subscription plan not found"); + + var isUsed = _myInfoMateDbContext.Instances.Any(i => i.SubscriptionPlanId == id); + if (isUsed) + return new ConflictObjectResult("This plan is assigned to one or more instances"); + + _myInfoMateDbContext.SubscriptionPlans.Remove(plan); + _myInfoMateDbContext.SaveChanges(); + + return new ObjectResult("The subscription plan has been deleted") { StatusCode = 202 }; + } + catch (Exception ex) + { + return new ObjectResult(ex.Message) { StatusCode = 500 }; + } + } + } +} diff --git a/ManagerService/DTOs/InstanceDTO.cs b/ManagerService/DTOs/InstanceDTO.cs index f5d949c..eba457e 100644 --- a/ManagerService/DTOs/InstanceDTO.cs +++ b/ManagerService/DTOs/InstanceDTO.cs @@ -18,6 +18,11 @@ namespace ManagerService.DTOs public bool isAssistant { get; set; } + public string? subscriptionPlanId { get; set; } + public SubscriptionPlanDTO? subscriptionPlan { get; set; } + public int aiRequestsThisMonth { get; set; } + public string? aiUsageMonthKey { get; set; } + public List applicationInstanceDTOs { get; set; } } } diff --git a/ManagerService/DTOs/InstanceQuotaDTO.cs b/ManagerService/DTOs/InstanceQuotaDTO.cs new file mode 100644 index 0000000..8fd49b5 --- /dev/null +++ b/ManagerService/DTOs/InstanceQuotaDTO.cs @@ -0,0 +1,10 @@ +namespace ManagerService.DTOs +{ + public class InstanceQuotaDTO + { + public long storageUsedBytes { get; set; } + public long storageQuotaBytes { get; set; } + public int aiRequestsUsed { get; set; } + public int aiRequestsPerMonth { get; set; } + } +} diff --git a/ManagerService/DTOs/ResourceDTO.cs b/ManagerService/DTOs/ResourceDTO.cs index 8a4822f..55f71ee 100644 --- a/ManagerService/DTOs/ResourceDTO.cs +++ b/ManagerService/DTOs/ResourceDTO.cs @@ -11,5 +11,6 @@ namespace ManagerService.DTOs public string url { get; set; } // firebase url public DateTime dateCreation { get; set; } public string instanceId { get; set; } + public long sizeBytes { get; set; } } } diff --git a/ManagerService/DTOs/SubscriptionPlanDTO.cs b/ManagerService/DTOs/SubscriptionPlanDTO.cs new file mode 100644 index 0000000..c42511f --- /dev/null +++ b/ManagerService/DTOs/SubscriptionPlanDTO.cs @@ -0,0 +1,10 @@ +namespace ManagerService.DTOs +{ + public class SubscriptionPlanDTO + { + public string? id { get; set; } + public string name { get; set; } + public long storageQuotaBytes { get; set; } + public int aiRequestsPerMonth { get; set; } + } +} diff --git a/ManagerService/Data/Instance.cs b/ManagerService/Data/Instance.cs index cdb36e3..3c42ce6 100644 --- a/ManagerService/Data/Instance.cs +++ b/ManagerService/Data/Instance.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace ManagerService.Data { @@ -35,6 +36,15 @@ namespace ManagerService.Data public bool IsAssistant { get; set; } + public string? SubscriptionPlanId { get; set; } + + [ForeignKey("SubscriptionPlanId")] + public SubscriptionPlan? SubscriptionPlan { get; set; } + + public int AiRequestsThisMonth { get; set; } = 0; + + public string AiUsageMonthKey { get; set; } = ""; + public InstanceDTO ToDTO(List applicationInstanceDTOs) { @@ -51,6 +61,10 @@ namespace ManagerService.Data isWeb = IsWeb, isVR = IsVR, isAssistant = IsAssistant, + subscriptionPlanId = SubscriptionPlanId, + subscriptionPlan = SubscriptionPlan?.ToDTO(), + aiRequestsThisMonth = AiRequestsThisMonth, + aiUsageMonthKey = AiUsageMonthKey, applicationInstanceDTOs = applicationInstanceDTOs }; } @@ -67,6 +81,8 @@ namespace ManagerService.Data IsWeb = instanceDTO.isWeb; IsVR = instanceDTO.isVR; IsAssistant = instanceDTO.isAssistant; + if (instanceDTO.subscriptionPlanId != null) + SubscriptionPlanId = instanceDTO.subscriptionPlanId; return this; } diff --git a/ManagerService/Data/MyInfoMateDbContext.cs b/ManagerService/Data/MyInfoMateDbContext.cs index a3d6329..37cd625 100644 --- a/ManagerService/Data/MyInfoMateDbContext.cs +++ b/ManagerService/Data/MyInfoMateDbContext.cs @@ -13,6 +13,7 @@ namespace ManagerService.Data public MyInfoMateDbContext(DbContextOptions options) : base(options) { } public DbSet Instances { get; set; } + public DbSet SubscriptionPlans { get; set; } public DbSet Configurations { get; set; } public DbSet
Sections { get; set; } public DbSet Devices { get; set; } diff --git a/ManagerService/Data/Resource.cs b/ManagerService/Data/Resource.cs index 5257d1d..0820523 100644 --- a/ManagerService/Data/Resource.cs +++ b/ManagerService/Data/Resource.cs @@ -36,7 +36,9 @@ namespace ManagerService.Data /*[BsonElement("URL")]*/ public string Url { get; set; } // Firebase url - public ResourceDTO ToDTO() + public long SizeBytes { get; set; } = 0; + + public ResourceDTO ToDTO() { return new ResourceDTO() { @@ -45,7 +47,8 @@ namespace ManagerService.Data type = Type, url = Url, dateCreation = DateCreation, - instanceId = InstanceId + instanceId = InstanceId, + sizeBytes = SizeBytes }; } } diff --git a/ManagerService/Data/SubscriptionPlan.cs b/ManagerService/Data/SubscriptionPlan.cs new file mode 100644 index 0000000..8ebafd2 --- /dev/null +++ b/ManagerService/Data/SubscriptionPlan.cs @@ -0,0 +1,38 @@ +using ManagerService.DTOs; +using System.ComponentModel.DataAnnotations; + +namespace ManagerService.Data +{ + public class SubscriptionPlan + { + [Key] + [Required] + public string Id { get; set; } + + [Required] + public string Name { get; set; } + + public long StorageQuotaBytes { get; set; } = 0; + + public int AiRequestsPerMonth { get; set; } = 0; + + public SubscriptionPlanDTO ToDTO() + { + return new SubscriptionPlanDTO + { + id = Id, + name = Name, + storageQuotaBytes = StorageQuotaBytes, + aiRequestsPerMonth = AiRequestsPerMonth + }; + } + + public SubscriptionPlan FromDTO(SubscriptionPlanDTO dto) + { + Name = dto.name; + StorageQuotaBytes = dto.storageQuotaBytes; + AiRequestsPerMonth = dto.aiRequestsPerMonth; + return this; + } + } +} diff --git a/ManagerService/Migrations/20260401140706_AddSubscriptionPlanAndQuota.Designer.cs b/ManagerService/Migrations/20260401140706_AddSubscriptionPlanAndQuota.Designer.cs new file mode 100644 index 0000000..d8419d7 --- /dev/null +++ b/ManagerService/Migrations/20260401140706_AddSubscriptionPlanAndQuota.Designer.cs @@ -0,0 +1,1482 @@ +// +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("20260401140706_AddSubscriptionPlanAndQuota")] + partial class AddSubscriptionPlanAndQuota + { + /// + 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"); + }); + + 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/20260401140706_AddSubscriptionPlanAndQuota.cs b/ManagerService/Migrations/20260401140706_AddSubscriptionPlanAndQuota.cs new file mode 100644 index 0000000..c162160 --- /dev/null +++ b/ManagerService/Migrations/20260401140706_AddSubscriptionPlanAndQuota.cs @@ -0,0 +1,97 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ManagerService.Migrations +{ + /// + public partial class AddSubscriptionPlanAndQuota : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SizeBytes", + table: "Resources", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "AiRequestsThisMonth", + table: "Instances", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "AiUsageMonthKey", + table: "Instances", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "SubscriptionPlanId", + table: "Instances", + type: "text", + nullable: true); + + migrationBuilder.CreateTable( + name: "SubscriptionPlans", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + StorageQuotaBytes = table.Column(type: "bigint", nullable: false), + AiRequestsPerMonth = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SubscriptionPlans", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Instances_SubscriptionPlanId", + table: "Instances", + column: "SubscriptionPlanId"); + + migrationBuilder.AddForeignKey( + name: "FK_Instances_SubscriptionPlans_SubscriptionPlanId", + table: "Instances", + column: "SubscriptionPlanId", + principalTable: "SubscriptionPlans", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Instances_SubscriptionPlans_SubscriptionPlanId", + table: "Instances"); + + migrationBuilder.DropTable( + name: "SubscriptionPlans"); + + migrationBuilder.DropIndex( + name: "IX_Instances_SubscriptionPlanId", + table: "Instances"); + + migrationBuilder.DropColumn( + name: "SizeBytes", + table: "Resources"); + + migrationBuilder.DropColumn( + name: "AiRequestsThisMonth", + table: "Instances"); + + migrationBuilder.DropColumn( + name: "AiUsageMonthKey", + table: "Instances"); + + migrationBuilder.DropColumn( + name: "SubscriptionPlanId", + table: "Instances"); + } + } +} diff --git a/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs b/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs index 11c3f52..a90e587 100644 --- a/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs +++ b/ManagerService/Migrations/MyInfoMateDbContextModelSnapshot.cs @@ -296,6 +296,12 @@ namespace ManagerService.Migrations b.Property("Id") .HasColumnType("text"); + b.Property("AiRequestsThisMonth") + .HasColumnType("integer"); + + b.Property("AiUsageMonthKey") + .HasColumnType("text"); + b.Property("DateCreation") .HasColumnType("timestamp with time zone"); @@ -327,8 +333,13 @@ namespace ManagerService.Migrations b.Property("PinCode") .HasColumnType("text"); + b.Property("SubscriptionPlanId") + .HasColumnType("text"); + b.HasKey("Id"); + b.HasIndex("SubscriptionPlanId"); + b.ToTable("Instances"); }); @@ -391,6 +402,9 @@ namespace ManagerService.Migrations .IsRequired() .HasColumnType("text"); + b.Property("SizeBytes") + .HasColumnType("bigint"); + b.Property("Type") .HasColumnType("integer"); @@ -857,6 +871,26 @@ namespace ManagerService.Migrations 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"); + }); + modelBuilder.Entity("ManagerService.Data.User", b => { b.Property("Id") @@ -1217,6 +1251,15 @@ namespace ManagerService.Migrations 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)