157 lines
6.0 KiB
C#
157 lines
6.0 KiB
C#
using Manager.DTOs;
|
||
using ManagerService.DTOs;
|
||
using NetTopologySuite.Geometries;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel.DataAnnotations.Schema;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using System.Linq;
|
||
using ManagerService.Helpers;
|
||
|
||
namespace ManagerService.Data.SubSection
|
||
{
|
||
public class GuidedStep // Étape d’un parcours
|
||
{
|
||
[Key]
|
||
public string Id { get; set; }
|
||
|
||
[Required]
|
||
public string GuidedPathId { get; set; }
|
||
|
||
[ForeignKey("GuidedPathId")]
|
||
public GuidedPath GuidedPath { get; set; }
|
||
|
||
public int Order { get; set; }
|
||
|
||
[Required]
|
||
[Column(TypeName = "jsonb")]
|
||
public List<TranslationDTO> Title { get; set; }
|
||
|
||
[Column(TypeName = "jsonb")]
|
||
public List<TranslationDTO> Description { get; set; }
|
||
|
||
public Geometry? Geometry { get; set; } // Polygon ou centre du cercle
|
||
|
||
// Option : si true, cette étape doit être atteinte géographiquement (zone/rayon) pour être validée
|
||
public bool IsGeoTriggered { get; set; } = false;
|
||
|
||
public double? ZoneRadiusMeters { get; set; } // Optionnel, utile si zone cercle ou point
|
||
|
||
public string ImageUrl { get; set; }
|
||
|
||
// Rich content (calqué sur SectionArticle)
|
||
[Column(TypeName = "jsonb")]
|
||
public List<TranslationDTO> AudioIds { get; set; } = new();
|
||
|
||
[Column(TypeName = "jsonb")]
|
||
public List<ContentDTO> Contents { get; set; } = new();
|
||
|
||
[Column(TypeName = "jsonb")]
|
||
public List<TranslationDTO> FactContent { get; set; } = new();
|
||
|
||
public bool IsHiddenInitially { get; set; } = false;
|
||
|
||
// Exemple pour escape game
|
||
public List<QuizQuestion>? QuizQuestions { get; set; } // One or multiple question
|
||
|
||
// Option : si true, cette étape a un compte à rebourds, false sinon
|
||
public bool IsStepTimer { get; set; } = false;
|
||
|
||
// Option : si true, cette étape doit être validée avant de passer à la suivante
|
||
public bool IsStepLocked { get; set; } = true;
|
||
|
||
// Timer en secondes (durée max pour valider cette étape, optionnel)
|
||
public int? TimerSeconds { get; set; }
|
||
|
||
// Option : message ou action à effectuer si timer expire (ex: afficher aide, fin du jeu...)
|
||
[Column(TypeName = "jsonb")]
|
||
public List<TranslationDTO> TimerExpiredMessage { get; set; }
|
||
|
||
public GuidedStepDTO ToDTO()
|
||
{
|
||
return new GuidedStepDTO
|
||
{
|
||
id = Id,
|
||
guidedPathId = GuidedPathId,
|
||
order = Order,
|
||
title = Title,
|
||
description = Description,
|
||
geometry = Geometry?.ToDto(),
|
||
isGeoTriggered = IsGeoTriggered,
|
||
zoneRadiusMeters = ZoneRadiusMeters,
|
||
imageUrl = ImageUrl,
|
||
audioIds = AudioIds,
|
||
contents = Contents,
|
||
factContent = FactContent,
|
||
isHiddenInitially = IsHiddenInitially,
|
||
isStepTimer = IsStepTimer,
|
||
isStepLocked = IsStepLocked,
|
||
timerSeconds = TimerSeconds,
|
||
timerExpiredMessage = TimerExpiredMessage,
|
||
quizQuestions = QuizQuestions
|
||
};
|
||
}
|
||
|
||
public GuidedStep FromDTO(GuidedStepDTO dto)
|
||
{
|
||
if (dto == null) return null;
|
||
|
||
GuidedPathId = dto.guidedPathId;
|
||
Title = dto.title ?? new List<TranslationDTO>();
|
||
Description = dto.description ?? new List<TranslationDTO>();
|
||
Geometry = dto.geometry.FromDto();
|
||
IsGeoTriggered = dto.isGeoTriggered;
|
||
ZoneRadiusMeters = dto.zoneRadiusMeters;
|
||
ImageUrl = dto.imageUrl;
|
||
AudioIds = dto.audioIds ?? new List<TranslationDTO>();
|
||
Contents = dto.contents ?? new List<ContentDTO>();
|
||
FactContent = dto.factContent ?? new List<TranslationDTO>();
|
||
IsHiddenInitially = dto.isHiddenInitially;
|
||
IsStepTimer = dto.isStepTimer;
|
||
IsStepLocked = dto.isStepLocked;
|
||
TimerSeconds = dto.timerSeconds;
|
||
TimerExpiredMessage = dto.timerExpiredMessage ?? new List<TranslationDTO>();
|
||
Order = dto.order.GetValueOrDefault();
|
||
SyncQuizQuestions(dto.quizQuestions);
|
||
return this;
|
||
}
|
||
|
||
// Merges incoming quiz questions into the tracked QuizQuestions collection by Id,
|
||
// instead of replacing the reference outright — a blind reassignment would make EF
|
||
// treat every incoming question (including already-persisted ones) as a brand new
|
||
// row to insert, which fails with a duplicate key error as soon as a step already
|
||
// has a saved question and a new one is added alongside it.
|
||
public void SyncQuizQuestions(List<QuizQuestion>? dtoQuestions)
|
||
{
|
||
dtoQuestions ??= new List<QuizQuestion>();
|
||
QuizQuestions ??= new List<QuizQuestion>();
|
||
|
||
var dtoIds = dtoQuestions.Where(q => q.Id != 0).Select(q => q.Id).ToHashSet();
|
||
QuizQuestions.RemoveAll(q => !dtoIds.Contains(q.Id));
|
||
|
||
foreach (var qDto in dtoQuestions)
|
||
{
|
||
var existing = qDto.Id != 0 ? QuizQuestions.FirstOrDefault(q => q.Id == qDto.Id) : null;
|
||
if (existing != null)
|
||
{
|
||
existing.Label = qDto.Label;
|
||
existing.ResourceId = qDto.ResourceId;
|
||
existing.Order = qDto.Order;
|
||
existing.Responses = qDto.Responses;
|
||
existing.ValidationQuestionType = qDto.ValidationQuestionType;
|
||
existing.PuzzleImageId = qDto.PuzzleImageId;
|
||
existing.PuzzleRows = qDto.PuzzleRows;
|
||
existing.PuzzleCols = qDto.PuzzleCols;
|
||
existing.IsSlidingPuzzle = qDto.IsSlidingPuzzle;
|
||
}
|
||
else
|
||
{
|
||
qDto.Id = 0;
|
||
QuizQuestions.Add(qDto);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
}
|