75 fichiers, ~1 mois de travail depuis f222861 (17/07). Contenu :
Onboarding self-service — jamais exécuté de bout en bout
OnboardingController, StripeWebhookController, StripeService, ResendEmailService
+ IEmailService (10 templates), EmailTemplates/, TrialLifecycleService (Hangfire),
PasswordTokenHelper, SlugHelper, 5 DTOs. Essai 14 j, Stripe customer/Checkout/Tax,
mot de passe oublié + invitation user, plafond IA d'essai.
Schéma Postgres v3 — passe pendant que la base est vide
ContentEmbedding + index HNSW (vector_cosine_ops), IEmbeddingService +
GoogleEmbeddingService (gemini-embedding-001, 768 dims), Deployment/Dockerfile.postgres
(postgis 3.4.3 + pgvector 0.8.6, épinglé par digest — un tag mobile rejouerait le
warning de collation glibc). Colonnes Resource : StoragePath, FileName,
IncludeInAiKnowledge, AiIndexStatus + nouveaux ResourceType ajoutés EN FIN d'enum.
Guide IA
Champs Guide* sur Instance + InstanceDTO, AssistantService lit la configuration client
dans les 4 blocs de prompt (ton codé en dur retiré, règle hors-sujet ajoutée aux deux
variantes qui n'en avaient pas), IHttpClientFactory à la place des new HttpClient().
Table VisitorQuestion + ConversationId sur AiChatRequest.
Stats
Rétention unifiée à 13 mois (instances ET plans), VisitEventPurgeService.
Nettoyage
IsStepLocked / IsHiddenInitially / FactContent supprimés de GuidedStep — IsStepLocked
rendait une étape définitivement infranchissable même après réussite.
SectionMap allégé (-57 lignes).
Tests
SectionParcoursControllerTests, FakeConfiguration, FakeEmailService.
ContentEmbedding a cassé 116 tests sur 124 (EF InMemory ne connaît pas Vector) :
l'entité est exclue quand le provider n'est pas Npgsql. Conséquence assumée —
le vector store n'est couvert par aucun test. dotnet test 124/124.
10 migrations EF. Base locale à jour, dotnet build 0 erreur.
Rien n'est en prod : la bascule Mongo → Postgres est décrite dans DOCS/STATUS.md §1quinquies.
⚠️ appsettings.json contient les clés Stripe (test) et Resend (prod) en clair — à rotationner.
195 lines
7.2 KiB
C#
195 lines
7.2 KiB
C#
using ManagerService.DTOs;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.ComponentModel.DataAnnotations.Schema;
|
|
|
|
namespace ManagerService.Data
|
|
{
|
|
/// <summary>
|
|
/// Instance Information
|
|
/// </summary>
|
|
public class Instance : IAuditableEntity
|
|
{
|
|
[Key]
|
|
[Required]
|
|
public string Id { get; set; }
|
|
|
|
[Required]
|
|
public string Name { get; set; } // UNIQUE !..
|
|
|
|
public DateTime DateCreation { get; set; }
|
|
|
|
public DateTime DateUpdate { get; set; }
|
|
|
|
public string PinCode { get; set; }
|
|
|
|
public bool IsPushNotification { get; set; }
|
|
|
|
public bool IsMobile { get; set; }
|
|
|
|
public bool IsTablet { get; set; }
|
|
|
|
public bool IsWeb { get; set; }
|
|
|
|
public bool IsVR { get; set; }
|
|
|
|
public bool IsAssistant { get; set; }
|
|
|
|
/// <summary>Nom du guide affiché au visiteur, libre. Ex: "Léon".</summary>
|
|
public string? GuideName { get; set; }
|
|
|
|
/// <summary>
|
|
/// Personnalité du guide : instruction envoyée au modèle, jamais affichée.
|
|
/// Écrite dans une seule langue — le modèle répond dans celle du visiteur.
|
|
/// </summary>
|
|
public string? GuidePersonaPrompt { get; set; }
|
|
|
|
/// <summary>Voix Gemini TTS : "Sulafat" (Viva, féminine) ou "Umbriel" (Marco, masculine).</summary>
|
|
public string? GuideVoiceId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Formulations utilisées quand le guide ne trouve pas de réponse.
|
|
/// Liste à plat : plusieurs entrées peuvent partager la même langue,
|
|
/// une est tirée au hasard parmi celles de la langue du visiteur.
|
|
/// </summary>
|
|
public List<TranslationDTO>? GuideFallbackMessages { get; set; }
|
|
|
|
public string? WebSlug { get; set; }
|
|
|
|
public string? PublicApiKey { get; set; }
|
|
|
|
public string? SubscriptionPlanId { get; set; }
|
|
|
|
[ForeignKey("SubscriptionPlanId")]
|
|
public SubscriptionPlan? SubscriptionPlan { get; set; }
|
|
|
|
public long AiTokensThisMonth { get; set; } = 0;
|
|
|
|
public string AiUsageMonthKey { get; set; } = "";
|
|
|
|
public long StorageQuotaBytes { get; set; } = 0;
|
|
|
|
public long AiTokensPerMonth { get; set; } = 0;
|
|
|
|
public bool HasStats { get; set; } = false;
|
|
|
|
public int StatsHistoryDays { get; set; } = 30;
|
|
|
|
public bool HasAdvancedStats { get; set; } = false;
|
|
|
|
public bool IsActive { get; set; } = true;
|
|
|
|
public bool IsTrialActive { get; set; } = false;
|
|
|
|
public DateTime? TrialEndsAt { get; set; }
|
|
|
|
public long TrialAiTokensUsed { get; set; } = 0;
|
|
|
|
public bool TrialCheckInEmailSent { get; set; } = false;
|
|
|
|
public bool TrialReminderEmailSent { get; set; } = false;
|
|
|
|
public bool TrialLastDayEmailSent { get; set; } = false;
|
|
|
|
public string? BillingAddress { get; set; }
|
|
|
|
public string? BillingCountry { get; set; }
|
|
|
|
public string? VatNumber { get; set; }
|
|
|
|
public decimal? VatRate { get; set; }
|
|
|
|
public string? StripeCustomerId { get; set; }
|
|
|
|
public string? StripeSubscriptionId { get; set; }
|
|
|
|
public InstanceDTO ToDTO(List<ApplicationInstanceDTO> applicationInstanceDTOs)
|
|
{
|
|
return new InstanceDTO()
|
|
{
|
|
id = Id,
|
|
name = Name,
|
|
dateCreation = DateCreation,
|
|
pinCode = PinCode,
|
|
isPushNotification = IsPushNotification,
|
|
isMobile = IsMobile,
|
|
isTablet = IsTablet,
|
|
isWeb = IsWeb,
|
|
isVR = IsVR,
|
|
isAssistant = IsAssistant,
|
|
guideName = GuideName,
|
|
guidePersonaPrompt = GuidePersonaPrompt,
|
|
guideVoiceId = GuideVoiceId,
|
|
guideFallbackMessages = GuideFallbackMessages,
|
|
webSlug = WebSlug,
|
|
publicApiKey = PublicApiKey,
|
|
subscriptionPlanId = SubscriptionPlanId,
|
|
subscriptionPlan = SubscriptionPlan?.ToDTO(),
|
|
aiTokensThisMonth = AiTokensThisMonth,
|
|
aiUsageMonthKey = AiUsageMonthKey,
|
|
storageQuotaBytes = StorageQuotaBytes,
|
|
aiTokensPerMonth = AiTokensPerMonth,
|
|
hasStats = HasStats,
|
|
statsHistoryDays = StatsHistoryDays,
|
|
hasAdvancedStats = HasAdvancedStats,
|
|
isTrialActive = IsTrialActive,
|
|
trialEndsAt = TrialEndsAt,
|
|
trialAiTokensUsed = TrialAiTokensUsed,
|
|
billingAddress = BillingAddress,
|
|
billingCountry = BillingCountry,
|
|
vatNumber = VatNumber,
|
|
vatRate = VatRate,
|
|
applicationInstanceDTOs = applicationInstanceDTOs
|
|
};
|
|
}
|
|
|
|
public Instance FromDTO(InstanceDTO instanceDTO)
|
|
{
|
|
Name = instanceDTO.name;
|
|
DateCreation = instanceDTO.dateCreation != null ? instanceDTO.dateCreation.Value : DateTime.Now.ToUniversalTime();
|
|
PinCode = instanceDTO.pinCode;
|
|
IsPushNotification = instanceDTO.isPushNotification ?? false;
|
|
IsMobile = instanceDTO.isMobile ?? false;
|
|
IsTablet = instanceDTO.isTablet ?? false;
|
|
IsWeb = instanceDTO.isWeb ?? false;
|
|
IsVR = instanceDTO.isVR ?? false;
|
|
IsAssistant = instanceDTO.isAssistant ?? false;
|
|
if (instanceDTO.guideName != null)
|
|
GuideName = instanceDTO.guideName;
|
|
if (instanceDTO.guidePersonaPrompt != null)
|
|
GuidePersonaPrompt = instanceDTO.guidePersonaPrompt;
|
|
if (instanceDTO.guideVoiceId != null)
|
|
GuideVoiceId = instanceDTO.guideVoiceId;
|
|
if (instanceDTO.guideFallbackMessages != null)
|
|
GuideFallbackMessages = instanceDTO.guideFallbackMessages;
|
|
if (instanceDTO.webSlug != null)
|
|
WebSlug = instanceDTO.webSlug;
|
|
if (instanceDTO.publicApiKey != null)
|
|
PublicApiKey = instanceDTO.publicApiKey;
|
|
if (instanceDTO.subscriptionPlanId != null)
|
|
SubscriptionPlanId = instanceDTO.subscriptionPlanId;
|
|
if (instanceDTO.storageQuotaBytes.HasValue)
|
|
StorageQuotaBytes = instanceDTO.storageQuotaBytes.Value;
|
|
if (instanceDTO.aiTokensPerMonth.HasValue)
|
|
AiTokensPerMonth = instanceDTO.aiTokensPerMonth.Value;
|
|
if (instanceDTO.hasStats.HasValue)
|
|
HasStats = instanceDTO.hasStats.Value;
|
|
if (instanceDTO.statsHistoryDays.HasValue)
|
|
StatsHistoryDays = instanceDTO.statsHistoryDays.Value;
|
|
if (instanceDTO.hasAdvancedStats.HasValue)
|
|
HasAdvancedStats = instanceDTO.hasAdvancedStats.Value;
|
|
if (instanceDTO.billingAddress != null)
|
|
BillingAddress = instanceDTO.billingAddress;
|
|
if (instanceDTO.billingCountry != null)
|
|
BillingCountry = instanceDTO.billingCountry;
|
|
if (instanceDTO.vatNumber != null)
|
|
VatNumber = instanceDTO.vatNumber;
|
|
if (instanceDTO.vatRate.HasValue)
|
|
VatRate = instanceDTO.vatRate.Value;
|
|
return this;
|
|
}
|
|
|
|
}
|
|
}
|