Add instance index + date update audit + ai quota to token

This commit is contained in:
Thomas Fransolet 2026-07-17 15:21:51 +02:00
parent 19a0157b19
commit f2228617ca
33 changed files with 5666 additions and 87 deletions

View File

@ -16,7 +16,7 @@ namespace ManagerService.Tests.Controllers
{ {
public class AiControllerTests public class AiControllerTests
{ {
private static readonly AiChatResponse FakeResponse = new AiChatResponse { Reply = "OK" }; private static readonly AiChatResponse FakeResponse = new AiChatResponse { Reply = "OK", TokensUsed = 42 };
private AiController BuildController(MyInfoMateDbContext db, Mock<IAssistantService>? mockService = null) private AiController BuildController(MyInfoMateDbContext db, Mock<IAssistantService>? mockService = null)
{ {
@ -101,13 +101,13 @@ namespace ManagerService.Tests.Controllers
// ── QUOTA COUNTER ──────────────────────────────────────────────────── // ── QUOTA COUNTER ────────────────────────────────────────────────────
[Fact] [Fact]
public async Task Chat_FirstRequestOfMonth_ResetsCounterAndSets1() public async Task Chat_FirstRequestOfMonth_ResetsCounterAndAddsTokensUsed()
{ {
using var db = DbContextFactory.Create(); using var db = DbContextFactory.Create();
db.Instances.Add(new Instance db.Instances.Add(new Instance
{ {
Id = "i1", Name = "Musée", IsAssistant = true, DateCreation = DateTime.UtcNow, Id = "i1", Name = "Musée", IsAssistant = true, DateCreation = DateTime.UtcNow,
AiRequestsThisMonth = 99, AiUsageMonthKey = "2020-01" AiTokensThisMonth = 99, AiUsageMonthKey = "2020-01"
}); });
db.ApplicationInstances.Add(new ApplicationInstance db.ApplicationInstances.Add(new ApplicationInstance
{ {
@ -119,19 +119,19 @@ namespace ManagerService.Tests.Controllers
await BuildController(db).Chat(MakeRequest("i1")); await BuildController(db).Chat(MakeRequest("i1"));
var inst = db.Instances.First(); var inst = db.Instances.First();
Assert.Equal(1, inst.AiRequestsThisMonth); Assert.Equal(42, inst.AiTokensThisMonth);
Assert.Equal(DateTime.UtcNow.ToString("yyyy-MM"), inst.AiUsageMonthKey); Assert.Equal(DateTime.UtcNow.ToString("yyyy-MM"), inst.AiUsageMonthKey);
} }
[Fact] [Fact]
public async Task Chat_SameMonth_IncrementsCounter() public async Task Chat_SameMonth_AddsTokensUsedToCounter()
{ {
using var db = DbContextFactory.Create(); using var db = DbContextFactory.Create();
var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
db.Instances.Add(new Instance db.Instances.Add(new Instance
{ {
Id = "i1", Name = "Musée", IsAssistant = true, DateCreation = DateTime.UtcNow, Id = "i1", Name = "Musée", IsAssistant = true, DateCreation = DateTime.UtcNow,
AiRequestsThisMonth = 3, AiUsageMonthKey = monthKey AiTokensThisMonth = 3, AiUsageMonthKey = monthKey
}); });
db.ApplicationInstances.Add(new ApplicationInstance db.ApplicationInstances.Add(new ApplicationInstance
{ {
@ -142,7 +142,34 @@ namespace ManagerService.Tests.Controllers
await BuildController(db).Chat(MakeRequest("i1")); await BuildController(db).Chat(MakeRequest("i1"));
Assert.Equal(4, db.Instances.First().AiRequestsThisMonth); Assert.Equal(45, db.Instances.First().AiTokensThisMonth);
}
[Fact]
public async Task Chat_QuotaAlreadyReached_ReturnsTooManyRequestsWithoutCallingAssistantService()
{
using var db = DbContextFactory.Create();
var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
db.Instances.Add(new Instance
{
Id = "i1", Name = "Musée", IsAssistant = true, DateCreation = DateTime.UtcNow,
AiTokensThisMonth = 100, AiTokensPerMonth = 100, AiUsageMonthKey = monthKey
});
db.ApplicationInstances.Add(new ApplicationInstance
{
Id = "ai1", InstanceId = "i1", AppType = AppType.Tablet, IsAssistant = true,
Languages = new List<string>()
});
db.SaveChanges();
var mockService = new Mock<IAssistantService>();
mockService.Setup(s => s.ChatAsync(It.IsAny<AiChatRequest>())).ReturnsAsync(FakeResponse);
var result = await BuildController(db, mockService).Chat(MakeRequest("i1"));
var status = Assert.IsType<ObjectResult>(result);
Assert.Equal(429, status.StatusCode);
mockService.Verify(s => s.ChatAsync(It.IsAny<AiChatRequest>()), Times.Never);
} }
// ── NOMINAL ────────────────────────────────────────────────────────── // ── NOMINAL ──────────────────────────────────────────────────────────

View File

@ -130,7 +130,7 @@ namespace ManagerService.Tests.Controllers
var ok = Assert.IsType<OkObjectResult>(result); var ok = Assert.IsType<OkObjectResult>(result);
var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value); var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value);
Assert.Equal(0, dto.storageQuotaBytes); Assert.Equal(0, dto.storageQuotaBytes);
Assert.Equal(0, dto.aiRequestsPerMonth); Assert.Equal(0, dto.aiTokensPerMonth);
} }
[Fact] [Fact]
@ -141,7 +141,7 @@ namespace ManagerService.Tests.Controllers
{ {
Id = "p1", Name = "Standard", Id = "p1", Name = "Standard",
StorageQuotaBytes = 10_000_000_000L, StorageQuotaBytes = 10_000_000_000L,
AiRequestsPerMonth = 100 AiTokensPerMonth = 100
}); });
db.Instances.Add(new Instance db.Instances.Add(new Instance
{ {
@ -154,7 +154,7 @@ namespace ManagerService.Tests.Controllers
var ok = Assert.IsType<OkObjectResult>(result); var ok = Assert.IsType<OkObjectResult>(result);
var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value); var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value);
Assert.Equal(10_000_000_000L, dto.storageQuotaBytes); Assert.Equal(10_000_000_000L, dto.storageQuotaBytes);
Assert.Equal(100, dto.aiRequestsPerMonth); Assert.Equal(100, dto.aiTokensPerMonth);
} }
[Fact] [Fact]
@ -184,7 +184,7 @@ namespace ManagerService.Tests.Controllers
db.Instances.Add(new Instance db.Instances.Add(new Instance
{ {
Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow,
AiRequestsThisMonth = 7, AiUsageMonthKey = monthKey AiTokensThisMonth = 7, AiUsageMonthKey = monthKey
}); });
db.SaveChanges(); db.SaveChanges();
@ -192,7 +192,7 @@ namespace ManagerService.Tests.Controllers
var ok = Assert.IsType<OkObjectResult>(result); var ok = Assert.IsType<OkObjectResult>(result);
var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value); var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value);
Assert.Equal(7, dto.aiRequestsUsed); Assert.Equal(7, dto.aiTokensUsed);
} }
[Fact] [Fact]
@ -202,7 +202,7 @@ namespace ManagerService.Tests.Controllers
db.Instances.Add(new Instance db.Instances.Add(new Instance
{ {
Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow,
AiRequestsThisMonth = 7, AiUsageMonthKey = "2020-01" AiTokensThisMonth = 7, AiUsageMonthKey = "2020-01"
}); });
db.SaveChanges(); db.SaveChanges();
@ -210,7 +210,7 @@ namespace ManagerService.Tests.Controllers
var ok = Assert.IsType<OkObjectResult>(result); var ok = Assert.IsType<OkObjectResult>(result);
var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value); var dto = Assert.IsType<InstanceQuotaDTO>(ok.Value);
Assert.Equal(0, dto.aiRequestsUsed); Assert.Equal(0, dto.aiTokensUsed);
} }
// ── DELETE ─────────────────────────────────────────────────────────── // ── DELETE ───────────────────────────────────────────────────────────

View File

@ -38,7 +38,7 @@ namespace ManagerService.Tests.Controllers
using var db = DbContextFactory.Create(); using var db = DbContextFactory.Create();
db.SubscriptionPlans.Add(new SubscriptionPlan db.SubscriptionPlans.Add(new SubscriptionPlan
{ {
Id = "p1", Name = "Starter", StorageQuotaBytes = 1024, AiRequestsPerMonth = 50 Id = "p1", Name = "Starter", StorageQuotaBytes = 1024, AiTokensPerMonth = 50
}); });
db.SaveChanges(); db.SaveChanges();
@ -48,7 +48,7 @@ namespace ManagerService.Tests.Controllers
var dto = Assert.IsType<SubscriptionPlanDTO>(ok.Value); var dto = Assert.IsType<SubscriptionPlanDTO>(ok.Value);
Assert.Equal("Starter", dto.name); Assert.Equal("Starter", dto.name);
Assert.Equal(1024, dto.storageQuotaBytes); Assert.Equal(1024, dto.storageQuotaBytes);
Assert.Equal(50, dto.aiRequestsPerMonth); Assert.Equal(50, dto.aiTokensPerMonth);
} }
[Fact] [Fact]
@ -67,7 +67,7 @@ namespace ManagerService.Tests.Controllers
public void Create_ValidDto_PersistsAndReturnsDto() public void Create_ValidDto_PersistsAndReturnsDto()
{ {
using var db = DbContextFactory.Create(); using var db = DbContextFactory.Create();
var dto = new SubscriptionPlanDTO { name = "Premium", storageQuotaBytes = 5000, aiRequestsPerMonth = 100 }; var dto = new SubscriptionPlanDTO { name = "Premium", storageQuotaBytes = 5000, aiTokensPerMonth = 100 };
var result = BuildController(db).Create(dto); var result = BuildController(db).Create(dto);
@ -97,7 +97,7 @@ namespace ManagerService.Tests.Controllers
db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p1", Name = "Old" }); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p1", Name = "Old" });
db.SaveChanges(); db.SaveChanges();
var result = BuildController(db).Update(new SubscriptionPlanDTO { id = "p1", name = "New", storageQuotaBytes = 0, aiRequestsPerMonth = 0 }); var result = BuildController(db).Update(new SubscriptionPlanDTO { id = "p1", name = "New", storageQuotaBytes = 0, aiTokensPerMonth = 0 });
Assert.IsType<OkObjectResult>(result); Assert.IsType<OkObjectResult>(result);
Assert.Equal("New", db.SubscriptionPlans.First().Name); Assert.Equal("New", db.SubscriptionPlans.First().Name);

View File

@ -58,18 +58,20 @@ namespace ManagerService.Controllers
var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
if (instance.AiUsageMonthKey != monthKey) if (instance.AiUsageMonthKey != monthKey)
{ {
instance.AiRequestsThisMonth = 0; instance.AiTokensThisMonth = 0;
instance.AiUsageMonthKey = monthKey; instance.AiUsageMonthKey = monthKey;
_context.SaveChanges();
} }
var quota = instance.AiRequestsPerMonth; var quota = instance.AiTokensPerMonth;
if (quota > 0 && instance.AiRequestsThisMonth >= quota) if (quota > 0 && instance.AiTokensThisMonth >= quota)
return StatusCode(429, "Quota IA mensuel dépassé"); return StatusCode(429, "Quota IA mensuel dépassé");
instance.AiRequestsThisMonth++; var result = await _assistantService.TranslateAsync(request);
instance.AiTokensThisMonth += result.TokensUsed;
_context.SaveChanges(); _context.SaveChanges();
var result = await _assistantService.TranslateAsync(request);
return Ok(result); return Ok(result);
} }
catch (Exception ex) catch (Exception ex)
@ -110,18 +112,20 @@ namespace ManagerService.Controllers
var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
if (instance.AiUsageMonthKey != monthKey) if (instance.AiUsageMonthKey != monthKey)
{ {
instance.AiRequestsThisMonth = 0; instance.AiTokensThisMonth = 0;
instance.AiUsageMonthKey = monthKey; instance.AiUsageMonthKey = monthKey;
_context.SaveChanges();
} }
var quota = instance.AiRequestsPerMonth; var quota = instance.AiTokensPerMonth;
if (quota > 0 && instance.AiRequestsThisMonth >= quota) if (quota > 0 && instance.AiTokensThisMonth >= quota)
return StatusCode(429, "Quota IA mensuel dépassé"); return StatusCode(429, "Quota IA mensuel dépassé");
instance.AiRequestsThisMonth++; var result = await _assistantService.ChatAsync(request);
instance.AiTokensThisMonth += result.TokensUsed;
_context.SaveChanges(); _context.SaveChanges();
var result = await _assistantService.ChatAsync(request);
return Ok(result); return Ok(result);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -48,8 +48,8 @@ namespace ManagerService.Controllers
/// <summary> /// <summary>
/// Get a list of all configuration (summary) /// Get a list of all configuration (summary)
/// </summary> /// </summary>
/// <param name="id">id instance</param> /// <param name="id">id instance</param>
[Authorize(Policy = ManagerService.Service.Security.Policies.AppReadAccess)] [AllowAnonymous]
[ProducesResponseType(typeof(List<ConfigurationDTO>), 200)] [ProducesResponseType(typeof(List<ConfigurationDTO>), 200)]
[ProducesResponseType(typeof(string), 500)] [ProducesResponseType(typeof(string), 500)]
[HttpGet] [HttpGet]
@ -113,8 +113,8 @@ namespace ManagerService.Controllers
/// <summary> /// <summary>
/// Get a specific display configuration /// Get a specific display configuration
/// </summary> /// </summary>
/// <param name="id">id configuration</param> /// <param name="id">id configuration</param>
[Authorize(Policy = ManagerService.Service.Security.Policies.AppReadAccess)] [AllowAnonymous]
[ProducesResponseType(typeof(ConfigurationDTO), 200)] [ProducesResponseType(typeof(ConfigurationDTO), 200)]
[ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(typeof(string), 500)] [ProducesResponseType(typeof(string), 500)]

View File

@ -123,7 +123,7 @@ namespace ManagerService.Controllers
if (plan != null) if (plan != null)
{ {
instance.StorageQuotaBytes = plan.StorageQuotaBytes; instance.StorageQuotaBytes = plan.StorageQuotaBytes;
instance.AiRequestsPerMonth = plan.AiRequestsPerMonth; instance.AiTokensPerMonth = plan.AiTokensPerMonth;
instance.HasStats = plan.HasStats; instance.HasStats = plan.HasStats;
instance.StatsHistoryDays = plan.StatsHistoryDays; instance.StatsHistoryDays = plan.StatsHistoryDays;
instance.HasAdvancedStats = plan.HasAdvancedStats; instance.HasAdvancedStats = plan.HasAdvancedStats;
@ -338,10 +338,10 @@ namespace ManagerService.Controllers
.Sum(r => (long?)r.SizeBytes) ?? 0; .Sum(r => (long?)r.SizeBytes) ?? 0;
var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
var aiUsed = instance.AiUsageMonthKey == monthKey ? instance.AiRequestsThisMonth : 0; var aiUsed = instance.AiUsageMonthKey == monthKey ? instance.AiTokensThisMonth : 0;
var storageQuota = instance.StorageQuotaBytes; var storageQuota = instance.StorageQuotaBytes;
var aiQuota = instance.AiRequestsPerMonth; var aiQuota = instance.AiTokensPerMonth;
if ((storageQuota == 0 || aiQuota == 0) && instance.SubscriptionPlanId != null) if ((storageQuota == 0 || aiQuota == 0) && instance.SubscriptionPlanId != null)
{ {
@ -349,7 +349,7 @@ namespace ManagerService.Controllers
if (plan != null) if (plan != null)
{ {
if (storageQuota == 0) storageQuota = plan.StorageQuotaBytes; if (storageQuota == 0) storageQuota = plan.StorageQuotaBytes;
if (aiQuota == 0) aiQuota = plan.AiRequestsPerMonth; if (aiQuota == 0) aiQuota = plan.AiTokensPerMonth;
} }
} }
@ -357,8 +357,8 @@ namespace ManagerService.Controllers
{ {
storageUsedBytes = storageUsed, storageUsedBytes = storageUsed,
storageQuotaBytes = storageQuota, storageQuotaBytes = storageQuota,
aiRequestsUsed = aiUsed, aiTokensUsed = aiUsed,
aiRequestsPerMonth = aiQuota aiTokensPerMonth = aiQuota
}); });
} }
catch (Exception ex) catch (Exception ex)

View File

@ -851,7 +851,6 @@ namespace ManagerService.Controllers
[HttpPut("order")] [HttpPut("order")]
public ObjectResult UpdateOrder([FromBody] List<SectionDTO> updatedSectionsOrder) public ObjectResult UpdateOrder([FromBody] List<SectionDTO> updatedSectionsOrder)
{ {
// TODO REWRITE LOGIC..
try try
{ {
if (updatedSectionsOrder == null) if (updatedSectionsOrder == null)
@ -864,16 +863,42 @@ namespace ManagerService.Controllers
throw new KeyNotFoundException($"Section {section.label} with id {section.id} does not exist"); throw new KeyNotFoundException($"Section {section.label} with id {section.id} does not exist");
} }
foreach (var updatedSection in updatedSectionsOrder) // The client may only send a subset of the configuration's sections (e.g. a filtered
// reorder view) — renormalize against the full top-level set so `Order` always stays
// 0-based and contiguous, instead of blindly writing the payload's values.
foreach (var configurationId in updatedSectionsOrder.Select(s => s.configurationId).Distinct())
{ {
var section = _myInfoMateDbContext.Sections.FirstOrDefault(s => s.Id == updatedSection.id); var baseline = _myInfoMateDbContext.Sections
//OldSection section = _sectionService.GetById(updatedSection.id); .Where(s => s.ConfigurationId == configurationId && !s.IsSubSection)
section.Order = updatedSection.order.GetValueOrDefault(); .OrderBy(s => s.Order)
_myInfoMateDbContext.SaveChanges(); .ToList();
//_sectionService.Update(section.Id, section); var includedIds = updatedSectionsOrder
.Where(s => s.configurationId == configurationId)
.Select(s => s.id)
.ToHashSet();
var newSubsequence = updatedSectionsOrder
.Where(s => s.configurationId == configurationId)
.OrderBy(s => s.order.GetValueOrDefault())
.Select(s => baseline.First(b => b.Id == s.id))
.ToList();
var slots = baseline
.Select((section, index) => (section, index))
.Where(t => includedIds.Contains(t.section.Id))
.Select(t => t.index)
.ToList();
for (int k = 0; k < slots.Count; k++)
baseline[slots[k]] = newSubsequence[k];
for (int i = 0; i < baseline.Count; i++)
baseline[i].Order = i;
} }
_myInfoMateDbContext.SaveChanges();
if (updatedSectionsOrder.Count > 0) { if (updatedSectionsOrder.Count > 0) {
MqttClientService.PublishMessage($"config/{updatedSectionsOrder[0].configurationId}", JsonConvert.SerializeObject(new PlayerMessageDTO() { configChanged = true })); MqttClientService.PublishMessage($"config/{updatedSectionsOrder[0].configurationId}", JsonConvert.SerializeObject(new PlayerMessageDTO() { configChanged = true }));
} }
@ -975,7 +1000,7 @@ namespace ManagerService.Controllers
var configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == section.ConfigurationId); var configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == section.ConfigurationId);
List<Section> sections = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == section.ConfigurationId && !s.IsSubSection).ToList(); List<Section> sections = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == section.ConfigurationId && !s.IsSubSection).ToList();
int i = 1; int i = 0;
List<Section> orderedSection = sections.OrderBy(s => s.Order).ToList(); List<Section> orderedSection = sections.OrderBy(s => s.Order).ToList();
foreach (var sectionDb in orderedSection) foreach (var sectionDb in orderedSection)
{ {

View File

@ -50,6 +50,7 @@ namespace ManagerService.DTOs
/// Le front peut désactiver l'écoute automatique après le TTS. /// Le front peut désactiver l'écoute automatique après le TTS.
/// </summary> /// </summary>
public bool ExpectsReply { get; set; } = true; public bool ExpectsReply { get; set; } = true;
public long TokensUsed { get; set; }
} }
public class AiTranslateRequest public class AiTranslateRequest
@ -62,5 +63,6 @@ namespace ManagerService.DTOs
public class AiTranslateResponse public class AiTranslateResponse
{ {
public Dictionary<string, string> Translations { get; set; } = new(); public Dictionary<string, string> Translations { get; set; } = new();
public long TokensUsed { get; set; }
} }
} }

View File

@ -23,10 +23,10 @@ namespace ManagerService.DTOs
public string? subscriptionPlanId { get; set; } public string? subscriptionPlanId { get; set; }
public SubscriptionPlanDTO? subscriptionPlan { get; set; } public SubscriptionPlanDTO? subscriptionPlan { get; set; }
public int? aiRequestsThisMonth { get; set; } public long? aiTokensThisMonth { get; set; }
public string? aiUsageMonthKey { get; set; } public string? aiUsageMonthKey { get; set; }
public long? storageQuotaBytes { get; set; } public long? storageQuotaBytes { get; set; }
public int? aiRequestsPerMonth { get; set; } public long? aiTokensPerMonth { get; set; }
public bool? hasStats { get; set; } public bool? hasStats { get; set; }
public int? statsHistoryDays { get; set; } public int? statsHistoryDays { get; set; }
public bool? hasAdvancedStats { get; set; } public bool? hasAdvancedStats { get; set; }

View File

@ -4,7 +4,7 @@ namespace ManagerService.DTOs
{ {
public long storageUsedBytes { get; set; } public long storageUsedBytes { get; set; }
public long storageQuotaBytes { get; set; } public long storageQuotaBytes { get; set; }
public int aiRequestsUsed { get; set; } public long aiTokensUsed { get; set; }
public int aiRequestsPerMonth { get; set; } public long aiTokensPerMonth { get; set; }
} }
} }

View File

@ -5,7 +5,7 @@ namespace ManagerService.DTOs
public string? id { get; set; } public string? id { get; set; }
public string name { get; set; } public string name { get; set; }
public long storageQuotaBytes { get; set; } public long storageQuotaBytes { get; set; }
public int aiRequestsPerMonth { get; set; } public long aiTokensPerMonth { get; set; }
public bool hasStats { get; set; } public bool hasStats { get; set; }
public int statsHistoryDays { get; set; } public int statsHistoryDays { get; set; }
public bool hasAdvancedStats { get; set; } public bool hasAdvancedStats { get; set; }

View File

@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations;
namespace ManagerService.Data namespace ManagerService.Data
{ {
public class ApiKey public class ApiKey : IAuditableEntity
{ {
[Key] [Key]
public string Id { get; set; } public string Id { get; set; }
@ -28,6 +28,8 @@ namespace ManagerService.Data
public DateTime DateCreation { get; set; } public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
public DateTime? DateExpiration { get; set; } public DateTime? DateExpiration { get; set; }
} }
} }

View File

@ -13,11 +13,15 @@ namespace ManagerService.Data
/// application instances, optionally with an order or active status. /// application instances, optionally with an order or active status.
/// Useful for managing configuration assignment and display logic per app type. /// Useful for managing configuration assignment and display logic per app type.
/// </summary> /// </summary>
public class AppConfigurationLink public class AppConfigurationLink : IAuditableEntity
{ {
[Key] [Key]
public string Id { get; set; } public string Id { get; set; }
public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
[Required] [Required]
public string ConfigurationId { get; set; } public string ConfigurationId { get; set; }

View File

@ -2,18 +2,21 @@
using ManagerService.Data.SubSection; using ManagerService.Data.SubSection;
using ManagerService.DTOs; using ManagerService.DTOs;
using ManagerService.Services; using ManagerService.Services;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
{ {
/// <summary> /// <summary>
/// Defines the link between an application instance and a configuration, /// Defines the link between an application instance and a configuration,
/// allowing apps to use one or multiple configurations. /// allowing apps to use one or multiple configurations.
/// </summary> /// </summary>
public class ApplicationInstance [Index(nameof(InstanceId))]
public class ApplicationInstance : IAuditableEntity
{ {
[Key] [Key]
public string Id { get; set; } public string Id { get; set; }
@ -21,6 +24,10 @@ namespace ManagerService.Data
[Required] [Required]
public string InstanceId { get; set; } public string InstanceId { get; set; }
public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
[Required] [Required]
public AppType AppType { get; set; } public AppType AppType { get; set; }

View File

@ -1,7 +1,9 @@
using System; using System;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
{ {
[Index(nameof(InstanceId))]
public class AuditLog public class AuditLog
{ {
public string Id { get; set; } = Guid.NewGuid().ToString(); public string Id { get; set; } = Guid.NewGuid().ToString();

View File

@ -5,13 +5,15 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
{ {
/// <summary> /// <summary>
/// Configuration Information /// Configuration Information
/// </summary> /// </summary>
public class Configuration [Index(nameof(InstanceId))]
public class Configuration : IAuditableEntity
{ {
[Key] [Key]
[Required] [Required]
@ -39,6 +41,8 @@ namespace ManagerService.Data
public DateTime DateCreation { get; set; } public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
public bool IsOffline { get; set; } public bool IsOffline { get; set; }
public string LoaderImageId { get; set; } // Config can have their specific loader if needed public string LoaderImageId { get; set; } // Config can have their specific loader if needed

View File

@ -2,13 +2,15 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
{ {
/// <summary> /// <summary>
/// Device Information (Tablet) /// Device Information (Tablet)
/// </summary> /// </summary>
public class Device [Index(nameof(InstanceId))]
public class Device : IAuditableEntity
{ {
[Key] [Key]
[Required] [Required]

View File

@ -0,0 +1,10 @@
using System;
namespace ManagerService.Data
{
public interface IAuditableEntity
{
DateTime DateCreation { get; set; }
DateTime DateUpdate { get; set; }
}
}

View File

@ -9,7 +9,7 @@ namespace ManagerService.Data
/// <summary> /// <summary>
/// Instance Information /// Instance Information
/// </summary> /// </summary>
public class Instance public class Instance : IAuditableEntity
{ {
[Key] [Key]
[Required] [Required]
@ -20,6 +20,8 @@ namespace ManagerService.Data
public DateTime DateCreation { get; set; } public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
public string PinCode { get; set; } public string PinCode { get; set; }
public bool IsPushNotification { get; set; } public bool IsPushNotification { get; set; }
@ -43,13 +45,13 @@ namespace ManagerService.Data
[ForeignKey("SubscriptionPlanId")] [ForeignKey("SubscriptionPlanId")]
public SubscriptionPlan? SubscriptionPlan { get; set; } public SubscriptionPlan? SubscriptionPlan { get; set; }
public int AiRequestsThisMonth { get; set; } = 0; public long AiTokensThisMonth { get; set; } = 0;
public string AiUsageMonthKey { get; set; } = ""; public string AiUsageMonthKey { get; set; } = "";
public long StorageQuotaBytes { get; set; } = 0; public long StorageQuotaBytes { get; set; } = 0;
public int AiRequestsPerMonth { get; set; } = 0; public long AiTokensPerMonth { get; set; } = 0;
public bool HasStats { get; set; } = false; public bool HasStats { get; set; } = false;
@ -76,10 +78,10 @@ namespace ManagerService.Data
publicApiKey = PublicApiKey, publicApiKey = PublicApiKey,
subscriptionPlanId = SubscriptionPlanId, subscriptionPlanId = SubscriptionPlanId,
subscriptionPlan = SubscriptionPlan?.ToDTO(), subscriptionPlan = SubscriptionPlan?.ToDTO(),
aiRequestsThisMonth = AiRequestsThisMonth, aiTokensThisMonth = AiTokensThisMonth,
aiUsageMonthKey = AiUsageMonthKey, aiUsageMonthKey = AiUsageMonthKey,
storageQuotaBytes = StorageQuotaBytes, storageQuotaBytes = StorageQuotaBytes,
aiRequestsPerMonth = AiRequestsPerMonth, aiTokensPerMonth = AiTokensPerMonth,
hasStats = HasStats, hasStats = HasStats,
statsHistoryDays = StatsHistoryDays, statsHistoryDays = StatsHistoryDays,
hasAdvancedStats = HasAdvancedStats, hasAdvancedStats = HasAdvancedStats,
@ -106,8 +108,8 @@ namespace ManagerService.Data
SubscriptionPlanId = instanceDTO.subscriptionPlanId; SubscriptionPlanId = instanceDTO.subscriptionPlanId;
if (instanceDTO.storageQuotaBytes.HasValue) if (instanceDTO.storageQuotaBytes.HasValue)
StorageQuotaBytes = instanceDTO.storageQuotaBytes.Value; StorageQuotaBytes = instanceDTO.storageQuotaBytes.Value;
if (instanceDTO.aiRequestsPerMonth.HasValue) if (instanceDTO.aiTokensPerMonth.HasValue)
AiRequestsPerMonth = instanceDTO.aiRequestsPerMonth.Value; AiTokensPerMonth = instanceDTO.aiTokensPerMonth.Value;
if (instanceDTO.hasStats.HasValue) if (instanceDTO.hasStats.HasValue)
HasStats = instanceDTO.hasStats.Value; HasStats = instanceDTO.hasStats.Value;
if (instanceDTO.statsHistoryDays.HasValue) if (instanceDTO.statsHistoryDays.HasValue)

View File

@ -64,8 +64,19 @@ namespace ManagerService.Data
// Audit // Audit
public DbSet<AuditLog> AuditLogs { get; set; } public DbSet<AuditLog> AuditLogs { get; set; }
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
StampAuditableEntities();
var auditEntries = BuildAuditEntries();
var result = base.SaveChanges(acceptAllChangesOnSuccess);
if (auditEntries.Any())
base.SaveChanges(acceptAllChangesOnSuccess);
return result;
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
StampAuditableEntities();
var auditEntries = BuildAuditEntries(); var auditEntries = BuildAuditEntries();
var result = await base.SaveChangesAsync(cancellationToken); var result = await base.SaveChangesAsync(cancellationToken);
if (auditEntries.Any()) if (auditEntries.Any())
@ -73,6 +84,18 @@ namespace ManagerService.Data
return result; return result;
} }
private void StampAuditableEntities()
{
var now = DateTime.UtcNow;
foreach (var entry in ChangeTracker.Entries<IAuditableEntity>())
{
if (entry.State == EntityState.Added)
entry.Entity.DateCreation = now;
if (entry.State is EntityState.Added or EntityState.Modified)
entry.Entity.DateUpdate = now;
}
}
private static readonly HashSet<Type> AuditedTypes = new() private static readonly HashSet<Type> AuditedTypes = new()
{ {
typeof(Section), typeof(Resource), typeof(Configuration), typeof(Section), typeof(Resource), typeof(Configuration),
@ -399,7 +422,7 @@ namespace ManagerService.Data
modelBuilder.Entity<Instance>() modelBuilder.Entity<Instance>()
.Property(i => i.StorageQuotaBytes).ValueGeneratedNever(); .Property(i => i.StorageQuotaBytes).ValueGeneratedNever();
modelBuilder.Entity<Instance>() modelBuilder.Entity<Instance>()
.Property(i => i.AiRequestsPerMonth).ValueGeneratedNever(); .Property(i => i.AiTokensPerMonth).ValueGeneratedNever();
modelBuilder.Entity<Instance>() modelBuilder.Entity<Instance>()
.Property(i => i.HasStats).ValueGeneratedNever(); .Property(i => i.HasStats).ValueGeneratedNever();
modelBuilder.Entity<Instance>() modelBuilder.Entity<Instance>()
@ -414,7 +437,7 @@ namespace ManagerService.Data
Id = "plan-starter", Id = "plan-starter",
Name = "Starter", Name = "Starter",
StorageQuotaBytes = 1L * 1024 * 1024 * 1024, // 1 GB StorageQuotaBytes = 1L * 1024 * 1024 * 1024, // 1 GB
AiRequestsPerMonth = 0, AiTokensPerMonth = 0,
HasStats = false, HasStats = false,
StatsHistoryDays = 30, StatsHistoryDays = 30,
HasAdvancedStats = false, HasAdvancedStats = false,
@ -424,7 +447,7 @@ namespace ManagerService.Data
Id = "plan-standard", Id = "plan-standard",
Name = "Standard", Name = "Standard",
StorageQuotaBytes = 10L * 1024 * 1024 * 1024, // 10 GB StorageQuotaBytes = 10L * 1024 * 1024 * 1024, // 10 GB
AiRequestsPerMonth = 500, AiTokensPerMonth = 5_000_000, // ~500 req/mois * ~10k tokens/req estimés, à ajuster selon l'usage réel observé
HasStats = true, HasStats = true,
StatsHistoryDays = 30, StatsHistoryDays = 30,
HasAdvancedStats = false, HasAdvancedStats = false,
@ -434,7 +457,7 @@ namespace ManagerService.Data
Id = "plan-premium", Id = "plan-premium",
Name = "Premium", Name = "Premium",
StorageQuotaBytes = 50L * 1024 * 1024 * 1024, // 50 GB StorageQuotaBytes = 50L * 1024 * 1024 * 1024, // 50 GB
AiRequestsPerMonth = 2000, AiTokensPerMonth = 20_000_000, // ~2000 req/mois * ~10k tokens/req estimés, à ajuster selon l'usage réel observé
HasStats = true, HasStats = true,
StatsHistoryDays = 0, StatsHistoryDays = 0,
HasAdvancedStats = true, HasAdvancedStats = true,

View File

@ -1,13 +1,15 @@
using ManagerService.DTOs; using ManagerService.DTOs;
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
{ {
/// <summary> /// <summary>
/// Resource Information /// Resource Information
/// </summary> /// </summary>
public class Resource [Index(nameof(InstanceId))]
public class Resource : IAuditableEntity
{ {
[Key] [Key]
[Required] [Required]
@ -28,6 +30,8 @@ namespace ManagerService.Data
/*[BsonElement("DateCreation")]*/ /*[BsonElement("DateCreation")]*/
public DateTime DateCreation { get; set; } public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
/*[BsonElement("InstanceId")] /*[BsonElement("InstanceId")]
[BsonRequired]*/ [BsonRequired]*/
[Required] [Required]

View File

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
@ -10,7 +11,8 @@ namespace ManagerService.Data
/// <summary> /// <summary>
/// Section Information /// Section Information
/// </summary> /// </summary>
public class Section [Index(nameof(InstanceId))]
public class Section : IAuditableEntity
{ {
[Key] [Key]
[Required] [Required]
@ -45,6 +47,8 @@ namespace ManagerService.Data
public DateTime DateCreation { get; set; } public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
[Required] [Required]
public string InstanceId { get; set; } public string InstanceId { get; set; }

View File

@ -1,13 +1,16 @@
using ManagerService.Data; using ManagerService.Data;
using ManagerService.DTOs; using ManagerService.DTOs;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data.SubSection namespace ManagerService.Data.SubSection
{ {
public class GuidedPath // Parcours [Index(nameof(InstanceId))]
public class GuidedPath : IAuditableEntity // Parcours
{ {
[Key] [Key]
public string Id { get; set; } public string Id { get; set; }
@ -15,6 +18,10 @@ namespace ManagerService.Data.SubSection
[Required] [Required]
public string InstanceId { get; set; } public string InstanceId { get; set; }
public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
[Required] [Required]
[Column(TypeName = "jsonb")] [Column(TypeName = "jsonb")]
public List<TranslationDTO> Title { get; set; } public List<TranslationDTO> Title { get; set; }

View File

@ -14,7 +14,7 @@ namespace ManagerService.Data
public long StorageQuotaBytes { get; set; } = 0; public long StorageQuotaBytes { get; set; } = 0;
public int AiRequestsPerMonth { get; set; } = 0; public long AiTokensPerMonth { get; set; } = 0;
/// <summary>Whether the plan includes any stats at all.</summary> /// <summary>Whether the plan includes any stats at all.</summary>
public bool HasStats { get; set; } = false; public bool HasStats { get; set; } = false;
@ -32,7 +32,7 @@ namespace ManagerService.Data
id = Id, id = Id,
name = Name, name = Name,
storageQuotaBytes = StorageQuotaBytes, storageQuotaBytes = StorageQuotaBytes,
aiRequestsPerMonth = AiRequestsPerMonth, aiTokensPerMonth = AiTokensPerMonth,
hasStats = HasStats, hasStats = HasStats,
statsHistoryDays = StatsHistoryDays, statsHistoryDays = StatsHistoryDays,
hasAdvancedStats = HasAdvancedStats, hasAdvancedStats = HasAdvancedStats,
@ -43,7 +43,7 @@ namespace ManagerService.Data
{ {
Name = dto.name; Name = dto.name;
StorageQuotaBytes = dto.storageQuotaBytes; StorageQuotaBytes = dto.storageQuotaBytes;
AiRequestsPerMonth = dto.aiRequestsPerMonth; AiTokensPerMonth = dto.aiTokensPerMonth;
HasStats = dto.hasStats; HasStats = dto.hasStats;
StatsHistoryDays = dto.statsHistoryDays; StatsHistoryDays = dto.statsHistoryDays;
HasAdvancedStats = dto.hasAdvancedStats; HasAdvancedStats = dto.hasAdvancedStats;

View File

@ -1,13 +1,15 @@
using ManagerService.DTOs; using ManagerService.DTOs;
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data namespace ManagerService.Data
{ {
/// <summary> /// <summary>
/// User Information /// User Information
/// </summary> /// </summary>
public class User [Index(nameof(InstanceId))]
public class User : IAuditableEntity
{ {
[Key] [Key]
[Required] [Required]
@ -42,6 +44,8 @@ namespace ManagerService.Data
//[BsonElement("DateCreation")] //[BsonElement("DateCreation")]
public DateTime DateCreation { get; set; } public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
/*[BsonElement("InstanceId")] /*[BsonElement("InstanceId")]
[BsonRequired]*/ [BsonRequired]*/
[Required] [Required]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ManagerService.Migrations
{
/// <inheritdoc />
public partial class AddInstanceIdIndexes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_Users_InstanceId",
table: "Users",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_Sections_InstanceId",
table: "Sections",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_Resources_InstanceId",
table: "Resources",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_GuidedPaths_InstanceId",
table: "GuidedPaths",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_Devices_InstanceId",
table: "Devices",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_Configurations_InstanceId",
table: "Configurations",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogs_InstanceId",
table: "AuditLogs",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_ApplicationInstances_InstanceId",
table: "ApplicationInstances",
column: "InstanceId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Users_InstanceId",
table: "Users");
migrationBuilder.DropIndex(
name: "IX_Sections_InstanceId",
table: "Sections");
migrationBuilder.DropIndex(
name: "IX_Resources_InstanceId",
table: "Resources");
migrationBuilder.DropIndex(
name: "IX_GuidedPaths_InstanceId",
table: "GuidedPaths");
migrationBuilder.DropIndex(
name: "IX_Devices_InstanceId",
table: "Devices");
migrationBuilder.DropIndex(
name: "IX_Configurations_InstanceId",
table: "Configurations");
migrationBuilder.DropIndex(
name: "IX_AuditLogs_InstanceId",
table: "AuditLogs");
migrationBuilder.DropIndex(
name: "IX_ApplicationInstances_InstanceId",
table: "ApplicationInstances");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ManagerService.Migrations
{
/// <inheritdoc />
public partial class AddDateUpdateAudit : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "Users",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "Sections",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "Resources",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "Instances",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateCreation",
table: "GuidedPaths",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "GuidedPaths",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "Configurations",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateCreation",
table: "ApplicationInstances",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "ApplicationInstances",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateCreation",
table: "AppConfigurationLinks",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "AppConfigurationLinks",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "DateUpdate",
table: "ApiKeys",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
// Backfill : pour les lignes existantes, DateUpdate reprend la DateCreation connue.
migrationBuilder.Sql("UPDATE \"Users\" SET \"DateUpdate\" = \"DateCreation\";");
migrationBuilder.Sql("UPDATE \"Sections\" SET \"DateUpdate\" = \"DateCreation\";");
migrationBuilder.Sql("UPDATE \"Resources\" SET \"DateUpdate\" = \"DateCreation\";");
migrationBuilder.Sql("UPDATE \"Instances\" SET \"DateUpdate\" = \"DateCreation\";");
migrationBuilder.Sql("UPDATE \"Configurations\" SET \"DateUpdate\" = \"DateCreation\";");
migrationBuilder.Sql("UPDATE \"ApiKeys\" SET \"DateUpdate\" = \"DateCreation\";");
// GuidedPaths/ApplicationInstances/AppConfigurationLinks n'avaient aucune date avant cette migration :
// on ne peut pas reconstituer la vraie date de création, donc on initialise les deux au moment du backfill.
migrationBuilder.Sql("UPDATE \"GuidedPaths\" SET \"DateCreation\" = now(), \"DateUpdate\" = now();");
migrationBuilder.Sql("UPDATE \"ApplicationInstances\" SET \"DateCreation\" = now(), \"DateUpdate\" = now();");
migrationBuilder.Sql("UPDATE \"AppConfigurationLinks\" SET \"DateCreation\" = now(), \"DateUpdate\" = now();");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "Users");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "Sections");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "Resources");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "Instances");
migrationBuilder.DropColumn(
name: "DateCreation",
table: "GuidedPaths");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "GuidedPaths");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "Configurations");
migrationBuilder.DropColumn(
name: "DateCreation",
table: "ApplicationInstances");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "ApplicationInstances");
migrationBuilder.DropColumn(
name: "DateCreation",
table: "AppConfigurationLinks");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "AppConfigurationLinks");
migrationBuilder.DropColumn(
name: "DateUpdate",
table: "ApiKeys");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,126 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ManagerService.Migrations
{
/// <inheritdoc />
public partial class AiQuotaTokensInsteadOfRequests : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AiRequestsPerMonth",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "AiRequestsPerMonth",
table: "Instances");
migrationBuilder.DropColumn(
name: "AiRequestsThisMonth",
table: "Instances");
migrationBuilder.AddColumn<long>(
name: "AiTokensPerMonth",
table: "SubscriptionPlans",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "AiTokensPerMonth",
table: "Instances",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "AiTokensThisMonth",
table: "Instances",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.UpdateData(
table: "SubscriptionPlans",
keyColumn: "Id",
keyValue: "plan-premium",
column: "AiTokensPerMonth",
value: 20000000L);
migrationBuilder.UpdateData(
table: "SubscriptionPlans",
keyColumn: "Id",
keyValue: "plan-standard",
column: "AiTokensPerMonth",
value: 5000000L);
migrationBuilder.UpdateData(
table: "SubscriptionPlans",
keyColumn: "Id",
keyValue: "plan-starter",
column: "AiTokensPerMonth",
value: 0L);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AiTokensPerMonth",
table: "SubscriptionPlans");
migrationBuilder.DropColumn(
name: "AiTokensPerMonth",
table: "Instances");
migrationBuilder.DropColumn(
name: "AiTokensThisMonth",
table: "Instances");
migrationBuilder.AddColumn<int>(
name: "AiRequestsPerMonth",
table: "SubscriptionPlans",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "AiRequestsPerMonth",
table: "Instances",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "AiRequestsThisMonth",
table: "Instances",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.UpdateData(
table: "SubscriptionPlans",
keyColumn: "Id",
keyValue: "plan-premium",
column: "AiRequestsPerMonth",
value: 2000);
migrationBuilder.UpdateData(
table: "SubscriptionPlans",
keyColumn: "Id",
keyValue: "plan-standard",
column: "AiRequestsPerMonth",
value: 500);
migrationBuilder.UpdateData(
table: "SubscriptionPlans",
keyColumn: "Id",
keyValue: "plan-starter",
column: "AiRequestsPerMonth",
value: 0);
}
}
}

View File

@ -41,6 +41,9 @@ namespace ManagerService.Migrations
b.Property<DateTime?>("DateExpiration") b.Property<DateTime?>("DateExpiration")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("InstanceId") b.Property<string>("InstanceId")
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
@ -78,6 +81,12 @@ namespace ManagerService.Migrations
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("DeviceId") b.Property<string>("DeviceId")
.HasColumnType("text"); .HasColumnType("text");
@ -139,6 +148,12 @@ namespace ManagerService.Migrations
b.Property<int>("AppType") b.Property<int>("AppType")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("InstanceId") b.Property<string>("InstanceId")
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
@ -172,6 +187,8 @@ namespace ManagerService.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("SectionEventId"); b.HasIndex("SectionEventId");
b.ToTable("ApplicationInstances"); b.ToTable("ApplicationInstances");
@ -208,6 +225,8 @@ namespace ManagerService.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("InstanceId");
b.ToTable("AuditLogs"); b.ToTable("AuditLogs");
}); });
@ -219,6 +238,9 @@ namespace ManagerService.Migrations
b.Property<DateTime>("DateCreation") b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("ImageId") b.Property<string>("ImageId")
.HasColumnType("text"); .HasColumnType("text");
@ -266,6 +288,8 @@ namespace ManagerService.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("InstanceId");
b.ToTable("Configurations"); b.ToTable("Configurations");
}); });
@ -325,6 +349,8 @@ namespace ManagerService.Migrations
b.HasIndex("ConfigurationId"); b.HasIndex("ConfigurationId");
b.HasIndex("InstanceId");
b.ToTable("Devices"); b.ToTable("Devices");
}); });
@ -333,11 +359,11 @@ namespace ManagerService.Migrations
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("text"); .HasColumnType("text");
b.Property<int>("AiRequestsPerMonth") b.Property<long>("AiTokensPerMonth")
.HasColumnType("integer"); .HasColumnType("bigint");
b.Property<int>("AiRequestsThisMonth") b.Property<long>("AiTokensThisMonth")
.HasColumnType("integer"); .HasColumnType("bigint");
b.Property<string>("AiUsageMonthKey") b.Property<string>("AiUsageMonthKey")
.HasColumnType("text"); .HasColumnType("text");
@ -345,6 +371,9 @@ namespace ManagerService.Migrations
b.Property<DateTime>("DateCreation") b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<bool>("HasAdvancedStats") b.Property<bool>("HasAdvancedStats")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -449,6 +478,9 @@ namespace ManagerService.Migrations
b.Property<DateTime>("DateCreation") b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("InstanceId") b.Property<string>("InstanceId")
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
@ -468,6 +500,8 @@ namespace ManagerService.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("InstanceId");
b.ToTable("Resources"); b.ToTable("Resources");
}); });
@ -486,6 +520,9 @@ namespace ManagerService.Migrations
b.Property<DateTime>("DateCreation") b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description") b.Property<string>("Description")
.HasColumnType("jsonb"); .HasColumnType("jsonb");
@ -544,6 +581,8 @@ namespace ManagerService.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("SectionMenuId"); b.HasIndex("SectionMenuId");
b.ToTable("Sections"); b.ToTable("Sections");
@ -705,6 +744,12 @@ namespace ManagerService.Migrations
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("text"); .HasColumnType("text");
b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description") b.Property<string>("Description")
.HasColumnType("jsonb"); .HasColumnType("jsonb");
@ -753,6 +798,8 @@ namespace ManagerService.Migrations
b.HasIndex("ImageResourceId"); b.HasIndex("ImageResourceId");
b.HasIndex("InstanceId");
b.HasIndex("SectionEventId"); b.HasIndex("SectionEventId");
b.HasIndex("SectionParcoursId"); b.HasIndex("SectionParcoursId");
@ -953,8 +1000,8 @@ namespace ManagerService.Migrations
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("text"); .HasColumnType("text");
b.Property<int>("AiRequestsPerMonth") b.Property<long>("AiTokensPerMonth")
.HasColumnType("integer"); .HasColumnType("bigint");
b.Property<bool>("HasAdvancedStats") b.Property<bool>("HasAdvancedStats")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -980,7 +1027,7 @@ namespace ManagerService.Migrations
new new
{ {
Id = "plan-starter", Id = "plan-starter",
AiRequestsPerMonth = 0, AiTokensPerMonth = 0L,
HasAdvancedStats = false, HasAdvancedStats = false,
HasStats = false, HasStats = false,
Name = "Starter", Name = "Starter",
@ -990,7 +1037,7 @@ namespace ManagerService.Migrations
new new
{ {
Id = "plan-standard", Id = "plan-standard",
AiRequestsPerMonth = 500, AiTokensPerMonth = 5000000L,
HasAdvancedStats = false, HasAdvancedStats = false,
HasStats = true, HasStats = true,
Name = "Standard", Name = "Standard",
@ -1000,7 +1047,7 @@ namespace ManagerService.Migrations
new new
{ {
Id = "plan-premium", Id = "plan-premium",
AiRequestsPerMonth = 2000, AiTokensPerMonth = 20000000L,
HasAdvancedStats = true, HasAdvancedStats = true,
HasStats = true, HasStats = true,
Name = "Premium", Name = "Premium",
@ -1017,6 +1064,9 @@ namespace ManagerService.Migrations
b.Property<DateTime>("DateCreation") b.Property<DateTime>("DateCreation")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateUpdate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Email") b.Property<string>("Email")
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
@ -1045,6 +1095,8 @@ namespace ManagerService.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("InstanceId");
b.ToTable("Users"); b.ToTable("Users");
}); });

View File

@ -57,7 +57,7 @@ namespace ManagerService.Services
} }
var translations = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(json) ?? new(); var translations = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(json) ?? new();
return new AiTranslateResponse { Translations = translations }; return new AiTranslateResponse { Translations = translations, TokensUsed = response.Usage?.TotalTokenCount ?? 0 };
} }
private static string StripHtml(string? html) => private static string StripHtml(string? html) =>
@ -493,7 +493,7 @@ namespace ManagerService.Services
reply = response.Text ?? ""; reply = response.Text ?? "";
expectsReply = true; expectsReply = true;
} }
return new AiChatResponse { Reply = reply, Cards = cards, Navigation = navigation, ExpectsReply = expectsReply }; return new AiChatResponse { Reply = reply, Cards = cards, Navigation = navigation, ExpectsReply = expectsReply, TokensUsed = response.Usage?.TotalTokenCount ?? 0 };
} }
catch (System.ClientModel.ClientResultException ex) catch (System.ClientModel.ClientResultException ex)
{ {
@ -789,7 +789,7 @@ namespace ManagerService.Services
reply = response.Text ?? ""; reply = response.Text ?? "";
expectsReply = true; expectsReply = true;
} }
return new AiChatResponse { Reply = reply, Navigation = navigation, ExpectsReply = expectsReply }; return new AiChatResponse { Reply = reply, Navigation = navigation, ExpectsReply = expectsReply, TokensUsed = response.Usage?.TotalTokenCount ?? 0 };
} }
catch (System.ClientModel.ClientResultException ex) catch (System.ClientModel.ClientResultException ex)
{ {