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>
123 lines
5.7 KiB
C#
123 lines
5.7 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Manager.DTOs;
|
|
using ManagerService.DTOs;
|
|
using static ManagerService.Data.SectionText;
|
|
|
|
namespace ManagerService.Data.SubSection
|
|
{
|
|
/// <summary>Objet manipulé ou décor habité — voir <see cref="SectionScene3D.Mode"/>.</summary>
|
|
public enum Scene3DMode
|
|
{
|
|
// ⚠️ Persisté en int : toute valeur nouvelle s'ajoute à la fin.
|
|
Asset = 0,
|
|
Scene = 1
|
|
}
|
|
|
|
/// <summary>
|
|
/// Une scène 3D : un modèle glTF et ses points d'intérêt — lot <b>XR-4</b>,
|
|
/// items <c>E5</c> (le décor) et <c>E7</c> (l'objet).
|
|
///
|
|
/// <b>Nommé « Scene3D » et pas « Model3D » alors qu'il ne porte aujourd'hui qu'un
|
|
/// modèle et des points</b> : le discriminateur TPH est une chaîne persistée en base.
|
|
/// Le renommer tant que rien n'est en production coûte zéro ; le renommer après la
|
|
/// première maquette livrée coûte une migration de données. Les objets posés, les
|
|
/// personnages et la navigation prévus au §S4 de la conception viendront ici, en
|
|
/// colonnes nullables — pas dans un second type à faire cohabiter.
|
|
///
|
|
/// <b>Pourquoi un type à part et pas une <see cref="SectionMap"/> augmentée</b> : une
|
|
/// maquette n'est pas une carte. Les deux se ressemblent parce qu'elles portent des
|
|
/// points d'intérêt, mais une carte a un fond cartographique, un zoom, un fournisseur
|
|
/// et des coordonnées terrestres — dont une maquette n'a que faire. Tordre la Map
|
|
/// aurait donné un type qui ment sur la moitié de ses champs.
|
|
///
|
|
/// <b>Ce qu'on réutilise en revanche : le <see cref="GeoPoint"/>.</b> Il portait déjà
|
|
/// deux rattachements (<c>SectionMapId</c>, <c>SectionEventId</c>) ; un troisième suit
|
|
/// le même patron. Un point garde ainsi son titre, sa description, son image, ses
|
|
/// contenus audio et son multilingue — et l'éditeur de points du back-office avec.
|
|
/// Seule sa <see cref="GeoPoint.LocalTransform"/> change de nature.
|
|
/// </summary>
|
|
public class SectionScene3D : Section
|
|
{
|
|
/// <summary>Ressource <c>Model3D</c> (glTF binaire) affichée par cette section.</summary>
|
|
public string Model3DResourceId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ce que le visiteur fait de cette scène — et c'est une opposition franche
|
|
/// (§4bis de <c>DOCS/v2/immersif-frontiere-plan.md</c>, arrêté le 11/09) :
|
|
///
|
|
/// <list type="bullet">
|
|
/// <item><b>Asset</b> — un objet posé devant lui, qu'il <b>manipule</b> : caméra
|
|
/// orbitale, on tourne autour, les points tournent avec l'objet. L'épée du
|
|
/// roi, une pièce de collection.</item>
|
|
/// <item><b>Scene</b> — un décor dans lequel il <b>est</b> : caméra fixe, on
|
|
/// regarde autour, les points restent où ils sont. Une salle reconstituée.</item>
|
|
/// </list>
|
|
///
|
|
/// Même GLB, même modèle de point, même éditeur : ce qui change, c'est où l'on met
|
|
/// le visiteur — et aucun fichier ne peut le deviner, d'où ce champ.
|
|
/// </summary>
|
|
public Scene3DMode Mode { get; set; } = Scene3DMode.Asset;
|
|
|
|
/// <summary>
|
|
/// URL du modèle, dupliquée comme l'est <see cref="Section.ImageSource"/> à côté
|
|
/// de <see cref="Section.ImageId"/>. Ce n'est pas de la redondance gratuite : le
|
|
/// viewer et le casque ont besoin de l'URL sans faire de jointure, et l'export
|
|
/// hors ligne la réécrit en chemin local.
|
|
/// </summary>
|
|
public string Model3DSource { get; set; }
|
|
|
|
/// <summary>
|
|
/// Points posés sur le modèle. Même collection que celle d'une Map, même éditeur,
|
|
/// même contenu — seule la position diffère.
|
|
/// </summary>
|
|
public List<GeoPoint> Points { get; set; }
|
|
|
|
public override string GetEmbeddableText(string language) =>
|
|
JoinText(new[] { BaseText(language) }
|
|
.Concat((Points ?? new List<GeoPoint>())
|
|
.Select(p => p.GetEmbeddableText(language)))
|
|
.ToArray());
|
|
|
|
/// <summary>
|
|
/// Le modèle compte parmi les ressources de la visite : sans lui, un téléchargement
|
|
/// hors ligne rapporterait les points d'une maquette absente.
|
|
/// </summary>
|
|
public override IEnumerable<string> GetReferencedResourceIds(string language = null) =>
|
|
BaseResourceIds()
|
|
.Concat(ResourceId(Model3DResourceId))
|
|
.Concat((Points ?? new List<GeoPoint>())
|
|
.SelectMany(p => p.GetReferencedResourceIds()));
|
|
|
|
public Scene3DDTO ToDTO()
|
|
{
|
|
return new Scene3DDTO()
|
|
{
|
|
id = Id,
|
|
label = Label,
|
|
title = Title.ToList(),
|
|
description = Description.ToList(),
|
|
order = Order,
|
|
type = Type,
|
|
imageId = ImageId,
|
|
imageSource = ImageSource,
|
|
configurationId = ConfigurationId,
|
|
isSubSection = IsSubSection,
|
|
parentId = ParentId,
|
|
isActive = IsActive,
|
|
dateCreation = DateCreation,
|
|
instanceId = InstanceId,
|
|
latitude = Latitude,
|
|
longitude = Longitude,
|
|
meterZoneGPS = MeterZoneGPS,
|
|
isBeacon = IsBeacon,
|
|
beaconId = BeaconId,
|
|
model3DResourceId = Model3DResourceId,
|
|
model3DSource = Model3DSource,
|
|
mode = Mode,
|
|
points = (Points ?? new List<GeoPoint>()).Select(p => p.ToDTO()).ToList()
|
|
};
|
|
}
|
|
}
|
|
}
|