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>
221 lines
8.2 KiB
C#
221 lines
8.2 KiB
C#
using Manager.DTOs;
|
|
using ManagerService.DTOs;
|
|
using ManagerService.Helpers;
|
|
using NetTopologySuite.Geometries;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.ComponentModel.DataAnnotations.Schema;
|
|
using System.Linq;
|
|
|
|
using static ManagerService.Data.SectionText;
|
|
|
|
namespace ManagerService.Data.SubSection
|
|
{
|
|
/// <summary>
|
|
/// Section map
|
|
/// </summary>
|
|
public class SectionMap : Section
|
|
{
|
|
public int MapZoom { get; set; } // Default = 18
|
|
public bool IsListViewEnabled { get; set; } = false;
|
|
public MapTypeApp? MapMapType { get; set; } // Default = Hybrid for Google
|
|
public MapTypeMapBox? MapTypeMapbox { get; set; } // Default = standard for MapBox
|
|
public MapProvider? MapMapProvider { get; set; } // Default = Google
|
|
public List<GeoPoint> MapPoints { get; set; }
|
|
public string IconResourceId { get; set; }
|
|
public Resource IconResource { get; set; } // Icon
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<CategorieDTO> MapCategories { get; set; }
|
|
public string MapCenterLatitude { get; set; } // Center on
|
|
public string MapCenterLongitude { get; set; } // Center on
|
|
|
|
|
|
public override string GetEmbeddableText(string language) =>
|
|
JoinText(new[] { BaseText(language) }
|
|
.Concat((MapCategories ?? new List<CategorieDTO>())
|
|
.OrderBy(c => c.order ?? 0)
|
|
.Select(c => Translate(c.label, language)))
|
|
.Concat((MapPoints ?? new List<GeoPoint>())
|
|
.Select(p => p.GetEmbeddableText(language)))
|
|
.ToArray());
|
|
|
|
public override IEnumerable<string> GetReferencedResourceIds(string language = null) =>
|
|
BaseResourceIds()
|
|
.Concat(ResourceId(IconResourceId))
|
|
.Concat((MapCategories ?? new List<CategorieDTO>())
|
|
.SelectMany(c => ResourceId(c.resourceDTO?.id)))
|
|
.Concat((MapPoints ?? new List<GeoPoint>())
|
|
.SelectMany(p => p.GetReferencedResourceIds()));
|
|
|
|
public MapDTO ToDTO()
|
|
{
|
|
return new MapDTO()
|
|
{
|
|
id = Id,
|
|
label = Label,
|
|
title = Title.ToList(),
|
|
description = Description.ToList(),
|
|
order = Order,
|
|
type = Type,
|
|
imageId = ImageId,
|
|
imageSource = ImageSource,
|
|
configurationId = ConfigurationId,
|
|
isSubSection = IsSubSection,
|
|
parentId = ParentId,
|
|
dateCreation = DateCreation,
|
|
instanceId = InstanceId,
|
|
isBeacon = IsBeacon,
|
|
beaconId = BeaconId,
|
|
latitude = Latitude,
|
|
longitude = Longitude,
|
|
meterZoneGPS = MeterZoneGPS,
|
|
zoom = MapZoom,
|
|
mapType = MapMapType,
|
|
mapTypeMapbox = MapTypeMapbox,
|
|
mapProvider = MapMapProvider,
|
|
iconResourceId = IconResourceId,
|
|
categories = MapCategories,
|
|
centerLatitude = MapCenterLatitude,
|
|
centerLongitude = MapCenterLongitude
|
|
};
|
|
}
|
|
}
|
|
|
|
public class GeoPoint
|
|
{
|
|
[Required]
|
|
public int? Id { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Title { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Description { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<ContentDTO> Contents { get; set; }
|
|
|
|
public int? CategorieId { get; set; }
|
|
|
|
public Geometry Geometry { get; set; }
|
|
|
|
public string PolyColor { get; set; } // color of the polyline or polygon
|
|
|
|
public string ImageResourceId { get; set; }
|
|
|
|
public string ImageUrl { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Schedules { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Prices { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Phone { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Email { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "jsonb")]
|
|
public List<TranslationDTO> Site { get; set; }
|
|
|
|
public string? SectionMapId { get; set; }
|
|
|
|
[ForeignKey(nameof(SectionMapId))]
|
|
public SectionMap? SectionMap { get; set; }
|
|
|
|
public string? SectionEventId { get; set; }
|
|
|
|
[ForeignKey(nameof(SectionEventId))]
|
|
public SectionEvent? SectionEvent { get; set; }
|
|
|
|
public string? SectionScene3DId { get; set; }
|
|
|
|
[ForeignKey(nameof(SectionScene3DId))]
|
|
public SectionScene3D? SectionScene3D { get; set; }
|
|
|
|
/// <summary>
|
|
/// Position du point <b>sur un modèle 3D</b>, quand il en décore un — à côté du
|
|
/// <see cref="Geometry"/> PostGIS, qui reste la position sur une carte.
|
|
///
|
|
/// C'est toute l'économie du lot E7 : un point d'intérêt sur une maquette porte
|
|
/// déjà titre, description, image, contenus audio et multilingue. Il ne lui
|
|
/// manquait qu'un endroit où se tenir.
|
|
/// </summary>
|
|
[Column(TypeName = "jsonb")]
|
|
public Position3D? LocalTransform { get; set; }
|
|
|
|
public string GetEmbeddableText(string language) =>
|
|
JoinText(Translate(Title, language),
|
|
Translate(Description, language),
|
|
TranslateContents(Contents, language),
|
|
Translate(Schedules, language),
|
|
Translate(Prices, language),
|
|
Translate(Phone, language),
|
|
Translate(Email, language),
|
|
Translate(Site, language));
|
|
|
|
public IEnumerable<string> GetReferencedResourceIds() =>
|
|
ResourceId(ImageResourceId).Concat(ResourceIds(Contents));
|
|
|
|
public GeoPointDTO ToDTO()
|
|
{
|
|
return new GeoPointDTO
|
|
{
|
|
id = Id,
|
|
title = Title,
|
|
description = Description,
|
|
contents = Contents,
|
|
categorieId = CategorieId,
|
|
geometry = Geometry != null ? Geometry.ToDto() : null,
|
|
polyColor = PolyColor,
|
|
imageResourceId = ImageResourceId,
|
|
imageUrl = ImageUrl,
|
|
schedules = Schedules,
|
|
prices = Prices,
|
|
phone = Phone,
|
|
email = Email,
|
|
site = Site,
|
|
sectionMapId = SectionMapId,
|
|
sectionEventId = SectionEventId,
|
|
sectionScene3DId = SectionScene3DId,
|
|
localTransform = LocalTransform
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Une position dans un modèle 3D, dans le repère du modèle lui-même — pas dans celui
|
|
/// du monde. Un point posé sur une maquette doit rester au même endroit de la maquette
|
|
/// quand on la déplace, l'oriente ou la change d'échelle.
|
|
///
|
|
/// ⚠️ <b>Convention glTF</b>, celle du fichier, pas celle d'Unity : Y vers le haut,
|
|
/// <b>Z vers l'arrière</b>, main droite. La conversion vit dans un seul endroit côté
|
|
/// casque (<c>GltfSpace</c>), et c'est exprès — un miroir d'axes est invisible sur une
|
|
/// maquette symétrique et se paie très cher découvert tard.
|
|
/// </summary>
|
|
public class Position3D
|
|
{
|
|
public float x { get; set; }
|
|
public float y { get; set; }
|
|
public float z { get; set; }
|
|
|
|
/// <summary>Rotation en degrés autour de Y. Sert à orienter un panneau vers l'allée.</summary>
|
|
public float? rotationY { get; set; }
|
|
}
|
|
|
|
// SectionMap "normal" comme avant => Via geopoints. Si il y a des guidedPath lié à la sectionMap alors il y a un parcours lié à la sectionMap, tu peux aussi avoir des geopoints classique mis là + des parcours. et aussi lier un guidedstep à un geopoint (pour le trigger et aussi pour "afficher plus")
|
|
|
|
}
|