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