Lot B : geler le schéma, plus sécurité rapide et calculateur de stockage

LOT B — une seule migration EF (LotB_FreezeSchema) :
- SectionMap.MapResourceId → IconResourceId. L'écart (g) de la bascule tombe
  avec. Il fallait renommer aussi la propriété de navigation MapResource :
  la convention EF l'appariait au FK, la laisser aurait fabriqué un FK
  fantôme. Elle n'était utilisée nulle part ailleurs.
- SectionEvent.ParcoursIds supprimé (champ, DTO, SectionFactory, et une
  initialisation dans un montage de test).
- Instance.IsImageWatermark remplace le `instanceId == "633ee379…"` en dur
  de ResourceController.

EF a généré un RenameColumn, pas un drop+add : les icônes déjà configurées
survivent. L'avertissement de perte de données ne porte que sur le DropColumn
de ParcoursIds, ce qui est l'intention.

Non fait, et c'était une erreur de doc : « supprimer SectionEvent.IconResourceId ».
Ce champ n'existe pas — la ligne visée appartient à la classe imbriquée
MapAnnotation, partagée par SectionEvent, SectionAgenda et SectionMap, lue par
cinq contrôleurs et par GetReferencedResourceIds. La supprimer aurait cassé
les icônes d'annotation des trois types et la collecte offline.

SÉCURITÉ (lot A, même repo) :
- AuthenticationController.Authenticate : un bloc #if DEBUG écrasait l'email
  et le mot de passe reçus par un compte de test, donc toute compilation en
  Debug authentifiait n'importe quelle saisie. Retiré.
- EnableSensitiveDataLogging (qui écrit les valeurs des paramètres dans les
  logs) passe sous #if DEBUG, l'idiome déjà employé dans Startup.cs pour le
  CORS et Hangfire. Le Dockerfile publiant en -c Release, c'est un verrou réel.

LOT C1 :
- Calculateur StoragePath/SizeBytes extrait dans Helpers/ResourceStorage.cs,
  avec 13 tests fixant l'invariant des types URL. Il ferme le lien L5 : le
  backfill (C2) et l'écart (e) de la migration appelleront le même code.
- L'extraction a révélé la divergence qu'elle devait empêcher : des deux
  chemins de création de ResourceController, le chemin multipart écrivait
  SizeBytes mais laissait StoragePath nul.

dotnet build Debug et Release verts, dotnet test 143/143 (130 + 13).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Fransolet 2026-08-11 15:32:19 +02:00
parent 18e4240f0f
commit 7625487806
16 changed files with 2130 additions and 53 deletions

View File

@ -43,8 +43,7 @@ namespace ManagerService.Tests.Controllers
ConfigurationId = "c1", ConfigurationId = "c1",
Title = EmptyTranslations(), Title = EmptyTranslations(),
Description = EmptyTranslations(), Description = EmptyTranslations(),
Programme = new List<ProgrammeBlock>(), Programme = new List<ProgrammeBlock>()
ParcoursIds = new List<string>()
}); });
db.SaveChanges(); db.SaveChanges();

View File

@ -0,0 +1,62 @@
using ManagerService.Data;
using ManagerService.Helpers;
using Xunit;
namespace ManagerService.Tests.Helpers
{
/// <summary>
/// Le calculateur est appelé par la création, le backfill et MigrationController.
/// Ces tests fixent la distinction qu'ils perdaient chacun de leur côté : un type
/// URL n'a pas de blob, donc ni chemin de stockage ni poids au quota.
/// </summary>
public class ResourceStorageTests
{
[Theory]
[InlineData(ResourceType.Image)]
[InlineData(ResourceType.Video)]
[InlineData(ResourceType.Audio)]
[InlineData(ResourceType.PDF)]
[InlineData(ResourceType.JSON)]
[InlineData(ResourceType.Word)]
[InlineData(ResourceType.PowerPoint)]
[InlineData(ResourceType.Text)]
public void Apply_BlobType_WritesPathAndSize(ResourceType type)
{
var resource = new Resource { Id = "res-1", InstanceId = "inst-1", Type = type };
ResourceStorage.Apply(resource, 4096);
Assert.Equal("pictures/inst-1/res-1", resource.StoragePath);
Assert.Equal(4096, resource.SizeBytes);
}
[Theory]
[InlineData(ResourceType.ImageUrl)]
[InlineData(ResourceType.VideoUrl)]
[InlineData(ResourceType.JSONUrl)]
public void Apply_UrlType_WritesNothing(ResourceType type)
{
var resource = new Resource { Id = "res-1", InstanceId = "inst-1", Type = type };
ResourceStorage.Apply(resource, 4096);
Assert.Null(resource.StoragePath);
// Reste à 0 : un média hébergé ailleurs ne doit pas peser sur le quota.
Assert.Equal(0, resource.SizeBytes);
}
[Fact]
public void PathFor_IsDeterministic_SoBackfillCanRebuildIt()
{
Assert.Equal(
ResourceStorage.PathFor(ResourceType.Image, "inst-1", "res-1"),
ResourceStorage.PathFor(ResourceType.Image, "inst-1", "res-1"));
}
[Fact]
public void PathFor_UrlType_IsNull()
{
Assert.Null(ResourceStorage.PathFor(ResourceType.ImageUrl, "inst-1", "res-1"));
}
}
}

View File

@ -60,11 +60,10 @@ namespace ManagerService.Service.Controllers
{ {
try try
{ {
#if DEBUG // Retiré le 2026-08-11 : un bloc `#if DEBUG` écrasait ici l'email et le
email = "test@email.be"; // mot de passe reçus par un compte de test, donc toute compilation en
password = "kljqsdkljqsd"; // password = "kljqsdkljqsd"; // W/7aj4NB60i3YFKJq50pbw== // Debug authentifiait n'importe qui en tant que test@email.be.
#endif // Set user token ?
// Set user token ?
var user = _myInfoMateDbContext.Users.FirstOrDefault(u => u.Email.ToLower() == email.ToLower()); var user = _myInfoMateDbContext.Users.FirstOrDefault(u => u.Email.ToLower() == email.ToLower());
//var user = _UserDatabaseService.GetByEmail(email.ToLower()); //var user = _UserDatabaseService.GetByEmail(email.ToLower());

View File

@ -466,7 +466,7 @@ namespace ManagerService.Controllers
MapMapType = dto?.mapType, MapMapType = dto?.mapType,
MapTypeMapbox = dto?.mapTypeMapbox, MapTypeMapbox = dto?.mapTypeMapbox,
MapMapProvider = dto?.mapProvider, MapMapProvider = dto?.mapProvider,
MapResourceId = dto?.iconResourceId, IconResourceId = dto?.iconResourceId,
MapCenterLatitude = dto?.latitude, MapCenterLatitude = dto?.latitude,
MapCenterLongitude = dto?.longitude, MapCenterLongitude = dto?.longitude,
MapCategories = dto?.categories?.Select(c => new CategorieDTO MapCategories = dto?.categories?.Select(c => new CategorieDTO

View File

@ -260,14 +260,9 @@ namespace ManagerService.Controllers
{ {
file.CopyTo(ms); file.CopyTo(ms);
var fileBytes = ms.ToArray(); var fileBytes = ms.ToArray();
if (resourceType == ResourceType.Image) if (resourceType == ResourceType.Image && IsWatermarkEnabled(instanceId))
{ {
bool isFort = instanceId == "633ee379d9405f32f166f047"; // If fort saint heribert, TODO add watermark in configuration and model fileBytes = ImageHelper.ResizeAndAddWatermark(fileBytes, true, MaxWidth, MaxHeight);
if(isFort) // TODO We need to know for which purpose (mobile or tablet)
{
fileBytes = ImageHelper.ResizeAndAddWatermark(fileBytes, isFort, MaxWidth, MaxHeight);
}
} }
stringResult = Convert.ToBase64String(fileBytes); stringResult = Convert.ToBase64String(fileBytes);
} }
@ -283,7 +278,7 @@ namespace ManagerService.Controllers
resource.DateCreation = DateTime.Now.ToUniversalTime(); resource.DateCreation = DateTime.Now.ToUniversalTime();
resource.InstanceId = instanceId; resource.InstanceId = instanceId;
resource.Id = idService.GenerateHexId(); resource.Id = idService.GenerateHexId();
resource.SizeBytes = file.Length; ResourceStorage.Apply(resource, file.Length);
_myInfoMateDbContext.Add(resource); _myInfoMateDbContext.Add(resource);
_myInfoMateDbContext.SaveChanges(); _myInfoMateDbContext.SaveChanges();
@ -339,15 +334,7 @@ namespace ManagerService.Controllers
resource.InstanceId = newResource.instanceId; resource.InstanceId = newResource.instanceId;
resource.Id = idService.GenerateHexId(); resource.Id = idService.GenerateHexId();
// Les types URL pointent hors du bucket : ni chemin de stockage, ni poids à compter dans le quota. ResourceStorage.Apply(resource, newResource.sizeBytes);
bool hasBlob = resource.Type != ResourceType.ImageUrl
&& resource.Type != ResourceType.VideoUrl
&& resource.Type != ResourceType.JSONUrl;
if (hasBlob)
{
resource.StoragePath = $"pictures/{resource.InstanceId}/{resource.Id}";
resource.SizeBytes = newResource.sizeBytes;
}
_myInfoMateDbContext.Add(resource); _myInfoMateDbContext.Add(resource);
_myInfoMateDbContext.SaveChanges(); _myInfoMateDbContext.SaveChanges();
@ -466,7 +453,7 @@ namespace ManagerService.Controllers
switch (section) switch (section)
{ {
case SectionMap map: case SectionMap map:
map.MapResourceId = map.MapResourceId == id ? null : map.MapResourceId; map.IconResourceId = map.IconResourceId == id ? null : map.IconResourceId;
List<GeoPoint> geoPoints = _myInfoMateDbContext.GeoPoints.Where(s => s.SectionMapId == section.Id).ToList(); List<GeoPoint> geoPoints = _myInfoMateDbContext.GeoPoints.Where(s => s.SectionMapId == section.Id).ToList();
foreach (var point in geoPoints) foreach (var point in geoPoints)
{ {
@ -716,5 +703,15 @@ namespace ManagerService.Controllers
return new ObjectResult(ex.Message) { StatusCode = 500 }; return new ObjectResult(ex.Message) { StatusCode = 500 };
} }
} }
/// <summary>
/// Le filigrane est un réglage d'instance depuis le 2026-08-11. Auparavant
/// un `instanceId == "633ee379…"` en dur ne l'activait que pour Fort Saint-Héribert.
/// </summary>
private bool IsWatermarkEnabled(string instanceId) =>
_myInfoMateDbContext.Instances
.Where(i => i.Id == instanceId)
.Select(i => i.IsImageWatermark)
.FirstOrDefault();
} }
} }

View File

@ -16,6 +16,7 @@ namespace ManagerService.DTOs
public bool? isVR { get; set; } public bool? isVR { get; set; }
public bool? isAssistant { get; set; } public bool? isAssistant { get; set; }
public bool? isImageWatermark { get; set; }
public string? guideName { get; set; } public string? guideName { get; set; }
public string? guidePersonaPrompt { get; set; } public string? guidePersonaPrompt { get; set; }

View File

@ -10,7 +10,6 @@ namespace Manager.DTOs
public DateTime? StartDate { get; set; } public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; } public DateTime? EndDate { get; set; }
public string? BaseSectionMapId { get; set; } public string? BaseSectionMapId { get; set; }
public List<string> ParcoursIds { get; set; }
public List<MapAnnotationDTO> GlobalMapAnnotations { get; set; } = new(); public List<MapAnnotationDTO> GlobalMapAnnotations { get; set; } = new();
public List<ProgrammeBlock> Programme { get; set; } = new(); public List<ProgrammeBlock> Programme { get; set; } = new();
} }

View File

@ -36,6 +36,12 @@ namespace ManagerService.Data
public bool IsAssistant { get; set; } public bool IsAssistant { get; set; }
/// <summary>
/// Appose le filigrane du lieu sur les images téléversées. Remplace le
/// `instanceId == "633ee379…"` en dur de ResourceController.Create.
/// </summary>
public bool IsImageWatermark { get; set; }
/// <summary>Nom du guide affiché au visiteur, libre. Ex: "Léon".</summary> /// <summary>Nom du guide affiché au visiteur, libre. Ex: "Léon".</summary>
public string? GuideName { get; set; } public string? GuideName { get; set; }
@ -118,6 +124,7 @@ namespace ManagerService.Data
isWeb = IsWeb, isWeb = IsWeb,
isVR = IsVR, isVR = IsVR,
isAssistant = IsAssistant, isAssistant = IsAssistant,
isImageWatermark = IsImageWatermark,
guideName = GuideName, guideName = GuideName,
guidePersonaPrompt = GuidePersonaPrompt, guidePersonaPrompt = GuidePersonaPrompt,
guideVoiceId = GuideVoiceId, guideVoiceId = GuideVoiceId,
@ -155,6 +162,7 @@ namespace ManagerService.Data
IsWeb = instanceDTO.isWeb ?? false; IsWeb = instanceDTO.isWeb ?? false;
IsVR = instanceDTO.isVR ?? false; IsVR = instanceDTO.isVR ?? false;
IsAssistant = instanceDTO.isAssistant ?? false; IsAssistant = instanceDTO.isAssistant ?? false;
IsImageWatermark = instanceDTO.isImageWatermark ?? false;
if (instanceDTO.guideName != null) if (instanceDTO.guideName != null)
GuideName = instanceDTO.guideName; GuideName = instanceDTO.guideName;
if (instanceDTO.guidePersonaPrompt != null) if (instanceDTO.guidePersonaPrompt != null)

View File

@ -24,8 +24,6 @@ namespace ManagerService.Data.SubSection
public SectionMap? BaseMap { get; set; } public SectionMap? BaseMap { get; set; }
public List<MapAnnotation> GlobalMapAnnotations { get; set; } = new(); public List<MapAnnotation> GlobalMapAnnotations { get; set; } = new();
public List<ProgrammeBlock> Programme { get; set; } = new(); public List<ProgrammeBlock> Programme { get; set; } = new();
[Column(TypeName = "jsonb")]
public List<string> ParcoursIds { get; set; } = new(); // Liens vers GeoPoints spécifiques
public override string GetEmbeddableText(string language) => public override string GetEmbeddableText(string language) =>
JoinText(new[] JoinText(new[]

View File

@ -22,8 +22,8 @@ namespace ManagerService.Data.SubSection
public MapTypeMapBox? MapTypeMapbox { get; set; } // Default = standard for MapBox public MapTypeMapBox? MapTypeMapbox { get; set; } // Default = standard for MapBox
public MapProvider? MapMapProvider { get; set; } // Default = Google public MapProvider? MapMapProvider { get; set; } // Default = Google
public List<GeoPoint> MapPoints { get; set; } public List<GeoPoint> MapPoints { get; set; }
public string MapResourceId { get; set; } public string IconResourceId { get; set; }
public Resource MapResource { get; set; } // Icon public Resource IconResource { get; set; } // Icon
[Required] [Required]
[Column(TypeName = "jsonb")] [Column(TypeName = "jsonb")]
public List<CategorieDTO> MapCategories { get; set; } public List<CategorieDTO> MapCategories { get; set; }
@ -42,7 +42,7 @@ namespace ManagerService.Data.SubSection
public override IEnumerable<string> GetReferencedResourceIds(string language = null) => public override IEnumerable<string> GetReferencedResourceIds(string language = null) =>
BaseResourceIds() BaseResourceIds()
.Concat(ResourceId(MapResourceId)) .Concat(ResourceId(IconResourceId))
.Concat((MapCategories ?? new List<CategorieDTO>()) .Concat((MapCategories ?? new List<CategorieDTO>())
.SelectMany(c => ResourceId(c.resourceDTO?.id))) .SelectMany(c => ResourceId(c.resourceDTO?.id)))
.Concat((MapPoints ?? new List<GeoPoint>()) .Concat((MapPoints ?? new List<GeoPoint>())
@ -74,7 +74,7 @@ namespace ManagerService.Data.SubSection
mapType = MapMapType, mapType = MapMapType,
mapTypeMapbox = MapTypeMapbox, mapTypeMapbox = MapTypeMapbox,
mapProvider = MapMapProvider, mapProvider = MapMapProvider,
iconResourceId = MapResourceId, iconResourceId = IconResourceId,
categories = MapCategories, categories = MapCategories,
centerLatitude = MapCenterLatitude, centerLatitude = MapCenterLatitude,
centerLongitude = MapCenterLongitude centerLongitude = MapCenterLongitude

View File

@ -0,0 +1,45 @@
using ManagerService.Data;
namespace ManagerService.Helpers
{
/// <summary>
/// Seule source de vérité pour StoragePath et SizeBytes d'une Resource.
///
/// Trois chemins écrivent ces colonnes : la création par téléversement et la
/// création par JSON (ResourceController), le backfill des lignes existantes,
/// et MigrationController (écart e de la bascule). Chacun calculant pour son
/// compte, ils divergeaient déjà : le chemin multipart renseignait SizeBytes
/// mais laissait StoragePath nul.
/// </summary>
public static class ResourceStorage
{
/// <summary>
/// Les types URL pointent hors du bucket : ni chemin de stockage, ni poids
/// à compter dans le quota. C'est la distinction que les appelants perdaient.
/// </summary>
public static bool HasBlob(ResourceType type) =>
type != ResourceType.ImageUrl
&& type != ResourceType.VideoUrl
&& type != ResourceType.JSONUrl;
/// <summary>
/// Chemin déterministe dans le bucket — reconstructible sans lire la ligne,
/// ce dont le backfill dépend. Null pour un type sans blob.
/// </summary>
public static string PathFor(ResourceType type, string instanceId, string resourceId) =>
HasBlob(type) ? $"pictures/{instanceId}/{resourceId}" : null;
/// <summary>
/// Renseigne les deux colonnes de stockage. Ne touche à rien pour un type URL,
/// dont SizeBytes doit rester à 0 pour ne pas peser sur le quota.
/// </summary>
public static void Apply(Resource resource, long sizeBytes)
{
if (!HasBlob(resource.Type))
return;
resource.StoragePath = PathFor(resource.Type, resource.InstanceId, resource.Id);
resource.SizeBytes = sizeBytes;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ManagerService.Migrations
{
/// <inheritdoc />
public partial class LotB_FreezeSchema : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Sections_Resources_MapResourceId",
table: "Sections");
migrationBuilder.DropColumn(
name: "ParcoursIds",
table: "Sections");
migrationBuilder.RenameColumn(
name: "MapResourceId",
table: "Sections",
newName: "IconResourceId");
migrationBuilder.RenameIndex(
name: "IX_Sections_MapResourceId",
table: "Sections",
newName: "IX_Sections_IconResourceId");
migrationBuilder.AddColumn<bool>(
name: "IsImageWatermark",
table: "Instances",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddForeignKey(
name: "FK_Sections_Resources_IconResourceId",
table: "Sections",
column: "IconResourceId",
principalTable: "Resources",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Sections_Resources_IconResourceId",
table: "Sections");
migrationBuilder.DropColumn(
name: "IsImageWatermark",
table: "Instances");
migrationBuilder.RenameColumn(
name: "IconResourceId",
table: "Sections",
newName: "MapResourceId");
migrationBuilder.RenameIndex(
name: "IX_Sections_IconResourceId",
table: "Sections",
newName: "IX_Sections_MapResourceId");
migrationBuilder.AddColumn<List<string>>(
name: "ParcoursIds",
table: "Sections",
type: "jsonb",
nullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Sections_Resources_MapResourceId",
table: "Sections",
column: "MapResourceId",
principalTable: "Resources",
principalColumn: "Id");
}
}
}

View File

@ -465,6 +465,9 @@ namespace ManagerService.Migrations
b.Property<bool>("IsAssistant") b.Property<bool>("IsAssistant")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<bool>("IsImageWatermark")
.HasColumnType("boolean");
b.Property<bool>("IsMobile") b.Property<bool>("IsMobile")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -1398,9 +1401,6 @@ namespace ManagerService.Migrations
b.Property<DateTime?>("EndDate") b.Property<DateTime?>("EndDate")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<List<string>>("ParcoursIds")
.HasColumnType("jsonb");
b.Property<DateTime?>("StartDate") b.Property<DateTime?>("StartDate")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@ -1442,6 +1442,9 @@ namespace ManagerService.Migrations
{ {
b.HasBaseType("ManagerService.Data.Section"); b.HasBaseType("ManagerService.Data.Section");
b.Property<string>("IconResourceId")
.HasColumnType("text");
b.Property<bool>("IsListViewEnabled") b.Property<bool>("IsListViewEnabled")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -1461,16 +1464,13 @@ namespace ManagerService.Migrations
b.Property<int?>("MapMapType") b.Property<int?>("MapMapType")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<string>("MapResourceId")
.HasColumnType("text");
b.Property<int?>("MapTypeMapbox") b.Property<int?>("MapTypeMapbox")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("MapZoom") b.Property<int>("MapZoom")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasIndex("MapResourceId"); b.HasIndex("IconResourceId");
b.HasDiscriminator().HasValue("Map"); b.HasDiscriminator().HasValue("Map");
}); });
@ -1806,11 +1806,11 @@ namespace ManagerService.Migrations
modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b => modelBuilder.Entity("ManagerService.Data.SubSection.SectionMap", b =>
{ {
b.HasOne("ManagerService.Data.Resource", "MapResource") b.HasOne("ManagerService.Data.Resource", "IconResource")
.WithMany() .WithMany()
.HasForeignKey("MapResourceId"); .HasForeignKey("IconResourceId");
b.Navigation("MapResource"); b.Navigation("IconResource");
}); });
modelBuilder.Entity("ManagerService.Data.SubSection.SectionParcours", b => modelBuilder.Entity("ManagerService.Data.SubSection.SectionParcours", b =>

View File

@ -31,8 +31,7 @@ namespace ManagerService.Services
}, },
SectionType.Event => new SectionEvent SectionType.Event => new SectionEvent
{ {
Programme = new List<SectionEvent.ProgrammeBlock>(), Programme = new List<SectionEvent.ProgrammeBlock>()
ParcoursIds = new List<string>()
}, },
SectionType.Map => new SectionMap SectionType.Map => new SectionMap
{ {
@ -196,7 +195,6 @@ namespace ManagerService.Services
Type = dto.type, Type = dto.type,
StartDate = sectionEventDTO.StartDate?.ToUniversalTime(), StartDate = sectionEventDTO.StartDate?.ToUniversalTime(),
EndDate = sectionEventDTO.EndDate?.ToUniversalTime(), EndDate = sectionEventDTO.EndDate?.ToUniversalTime(),
ParcoursIds = sectionEventDTO.ParcoursIds,
BaseSectionMapId = sectionEventDTO.BaseSectionMapId, BaseSectionMapId = sectionEventDTO.BaseSectionMapId,
//Programmes = // TODO specific //Programmes = // TODO specific
}, },
@ -224,7 +222,7 @@ namespace ManagerService.Services
MapMapType = mapDTO.mapType, MapMapType = mapDTO.mapType,
MapTypeMapbox = mapDTO.mapTypeMapbox, MapTypeMapbox = mapDTO.mapTypeMapbox,
MapMapProvider = mapDTO.mapProvider, MapMapProvider = mapDTO.mapProvider,
MapResourceId = mapDTO.iconResourceId, IconResourceId = mapDTO.iconResourceId,
MapCenterLatitude = mapDTO.centerLatitude, MapCenterLatitude = mapDTO.centerLatitude,
MapCenterLongitude = mapDTO.centerLongitude, MapCenterLongitude = mapDTO.centerLongitude,
MapCategories = mapDTO.categories, MapCategories = mapDTO.categories,
@ -526,7 +524,6 @@ namespace ManagerService.Services
type = sectionEvent.Type, type = sectionEvent.Type,
StartDate = sectionEvent.StartDate?.Year > 1000 ? sectionEvent.StartDate : null, StartDate = sectionEvent.StartDate?.Year > 1000 ? sectionEvent.StartDate : null,
EndDate = sectionEvent.EndDate?.Year > 1000 ? sectionEvent.EndDate : null, EndDate = sectionEvent.EndDate?.Year > 1000 ? sectionEvent.EndDate : null,
ParcoursIds = sectionEvent.ParcoursIds,
BaseSectionMapId = sectionEvent.BaseSectionMapId, BaseSectionMapId = sectionEvent.BaseSectionMapId,
GlobalMapAnnotations = sectionEvent.GlobalMapAnnotations?.Select(ma => ma.ToDTO()).ToList() ?? new(), GlobalMapAnnotations = sectionEvent.GlobalMapAnnotations?.Select(ma => ma.ToDTO()).ToList() ?? new(),
// Programme TODO specific // Programme TODO specific
@ -556,7 +553,7 @@ namespace ManagerService.Services
mapType = map.MapMapType, mapType = map.MapMapType,
mapTypeMapbox = map.MapTypeMapbox, mapTypeMapbox = map.MapTypeMapbox,
mapProvider = map.MapMapProvider, mapProvider = map.MapMapProvider,
iconResourceId = map.MapResourceId, iconResourceId = map.IconResourceId,
centerLatitude = map.MapCenterLatitude, centerLatitude = map.MapCenterLatitude,
centerLongitude = map.MapCenterLongitude, centerLongitude = map.MapCenterLongitude,
categories = map.MapCategories, categories = map.MapCategories,

View File

@ -243,10 +243,17 @@ namespace ManagerService
services.AddScoped<SectionIndexingInterceptor>(); services.AddScoped<SectionIndexingInterceptor>();
services.AddDbContext<MyInfoMateDbContext>((serviceProvider, options) => services.AddDbContext<MyInfoMateDbContext>((serviceProvider, options) =>
options.UseNpgsql(dataSource, o => o.UseNetTopologySuite().UseVector()) {
.AddInterceptors(serviceProvider.GetRequiredService<SectionIndexingInterceptor>()) options.UseNpgsql(dataSource, o => o.UseNetTopologySuite().UseVector())
.EnableSensitiveDataLogging() .AddInterceptors(serviceProvider.GetRequiredService<SectionIndexingInterceptor>());
.LogTo(Console.WriteLine, LogLevel.Information) #if DEBUG
// Écrit les valeurs des paramètres dans les logs : mots de passe hashés,
// clés API, données de visiteurs. Jamais en production — le Dockerfile
// publie en `-c Release`, donc ce bloc n'y est pas compilé.
options.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine, LogLevel.Information);
#endif
}
); );
services.AddHealthChecks() services.AddHealthChecks()