manager-service/ManagerService/DTOs/ExportConfigurationDTO.cs
Thomas Fransolet b87e49d808 Cle d'API sur les routes visiteur, canal VR et contenu immersif
Securite
- RequireAppKey : filtre qui exige une cle d'API valide ou un utilisateur du
  manager. Pose sur les routes de contenu consommees par les apps visiteur
  (Configuration, Section, Resource, SectionMap, SectionEvent, SectionAgenda,
  SectionParcours, SectionQuiz, ApplicationInstance). Un [Authorize] d'action
  ne peut pas assouplir celui de la classe ; seul [AllowAnonymous] le
  court-circuite, et le filtre redevient le controle d'acces. Il ferme
  l'enumeration par identifiant, pas la confidentialite : la cle s'obtient
  par le slug ou le pincode.
- Instance/slug/{slug} rendait le pinCode, l'adresse de facturation, la TVA et
  les quotas a qui lit l'URL du site visiteur. StripCommercialFields est
  desormais applique sur slug et byPin ; isTrialActive reste expose pour le
  filigrane d'essai.
- Device.Create et Device/{id}/detail repondaient 403 a toute tablette depuis
  a452f4a (13/03) : la classe exige InstanceAdmin, une cle ne porte que
  AppRead. Ouverts a la cle, le cloisonnement par instance etait deja ecrit.

Canal VR
- Device.AppType (defaut Tablet, backfill a 1) ; Create resout
  l'ApplicationInstance sur ce type au lieu de Tablet en dur.
- Get filtre optionnellement par appType ; DeviceDetailDTO expose appVersion
  et lastSeen.
- PUT Device/{id}/heartbeat : batterie, version, connexion. Volontairement
  etroit, une app ne peut ni se renommer ni changer d'instance.
- ApiKeyAppType.VrApp en fin d'enum.

Contenu immersif
- ResourceType : Image360 (11), Video360 (12), Model3D (13), en fin d'enum.
- Section Scene3D : un modele GLB et ses points d'interet, en objet manipule
  ou en decor habite. Les points sont des GeoPoint, avec une LocalTransform
  en jsonb (convention glTF) ; CRUD dans SectionScene3DController.
- Instance.HasImmersiveContent : l'add-on ajoute 100 Go au quota de stockage,
  repose apres un changement de plan et retire a la desactivation.
- ImmersiveBackground (owned) sur Configuration et ApplicationInstance, avec
  une image de repli pour les canaux qui ne rendent pas l'immersif.

Export de configuration
- exportVersion (1) et generatedAt : le JSON devient un contrat, lu tel quel
  par l'app Unity.
- Section.ToDTO() n'etant pas virtuelle, l'export ne portait aucun champ
  specifique de sous-type. Passe par SectionFactory.ToDTO, et charge les
  points des Map et des Scene3D.
- Le fond immersif et son repli partent avec les ressources, URL resolues.

Migrations : AddAppTypeToDevice, AddScene3DSectionAndImmersiveAddon,
AddImmersiveBackground.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 16:54:10 +02:00

48 lines
2.2 KiB
C#

using System;
using System.Collections.Generic;
namespace ManagerService.DTOs
{
/// <summary>
/// Le contenu complet d'une configuration, en un seul appel — le <b>contrat public</b>
/// entre le back-office et tout ce qui affiche une visite.
///
/// Trois consommateurs, et c'est ce qui en fait un contrat plutôt qu'un DTO interne :
/// la visite hors ligne de <c>mymuseum-visitapp</c>, l'import/export du back-office,
/// et l'app Unity du canal VR — qui n'a pas de client généré et lit ce JSON tel quel.
///
/// <b>Règles de compatibilité</b>, à tenir tant que le numéro ne change pas :
/// <list type="bullet">
/// <item>on <b>ajoute</b> des champs, on n'en retire ni n'en renomme aucun ;</item>
/// <item>les valeurs d'enum s'ajoutent <b>en fin</b> — elles sont persistées en int
/// et un client plus ancien doit pouvoir ignorer ce qu'il ne connaît pas ;</item>
/// <item>un champ absent vaut « pas de valeur », jamais « valeur par défaut »
/// différente de celle du client.</item>
/// </list>
///
/// Documentation complète : <c>DOCS/export-contract.md</c>.
/// </summary>
public class ExportConfigurationDTO : ConfigurationDTO
{
/// <summary>
/// Version du <b>format</b>, pas du contenu. Un client qui lit un numéro plus
/// grand que celui qu'il connaît doit le dire à l'utilisateur plutôt que de
/// deviner — c'est exactement ce que fait <c>ManifestReader</c> côté casque.
/// </summary>
public int exportVersion { get; set; } = CurrentExportVersion;
/// <summary>Quand ce JSON a été produit. Sert au diagnostic d'un cache périmé.</summary>
public DateTime generatedAt { get; set; }
public List<SectionDTO> sections { get; set; }
public List<ResourceDTO> resources { get; set; }
/// <summary>
/// <b>1</b> — version initiale, figée le 2026-09-12. L'export existait avant, sans
/// numéro : un client qui n'en trouve pas lit un export antérieur à cette date, et
/// doit le traiter comme la version 1.
/// </summary>
public const int CurrentExportVersion = 1;
}
}