Thomas Fransolet af6b5b76ef Self-service onboarding (Stripe + Resend) + Postgres v3 schema (pgvector, media, visitor questions)
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.
2026-08-09 22:11:48 +02:00

110 lines
3.2 KiB
C#

using ManagerService.DTOs;
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
namespace ManagerService.Data
{
/// <summary>
/// Resource Information
/// </summary>
[Index(nameof(InstanceId))]
public class Resource : IAuditableEntity
{
[Key]
[Required]
/*[BsonId]
[BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]*/
public string Id { get; set; }
/*[BsonElement("Type")]
[BsonRequired]*/
[Required]
public ResourceType Type { get; set; }
/*[BsonElement("Label")]
[BsonRequired]*/
[Required]
public string Label { get; set; }
/*[BsonElement("DateCreation")]*/
public DateTime DateCreation { get; set; }
public DateTime DateUpdate { get; set; }
/*[BsonElement("InstanceId")]
[BsonRequired]*/
[Required]
public string InstanceId { get; set; }
/*[BsonElement("URL")]*/
public string Url { get; set; } // Firebase url
public long SizeBytes { get; set; } = 0;
/// <summary>
/// Clé relative dans le stockage (`pictures/{instanceId}/{resourceId}`), source de vérité.
/// L'URL absolue à jeton reste dans Url, mais elle n'est plus ce dont on part.
/// </summary>
public string StoragePath { get; set; }
/// <summary>
/// Vrai nom de fichier ("brochure-2026.pdf"). Ni Label (libellé libre) ni Url (jeton)
/// ne le donnent — nécessaire pour choisir l'extracteur et citer la source.
/// </summary>
public string FileName { get; set; }
/// <summary>Inclure ce document dans les connaissances du guide IA. Exposé dans l'UI en V2 seulement.</summary>
public bool IncludeInAiKnowledge { get; set; } = false;
public AiIndexStatus AiIndexStatus { get; set; } = AiIndexStatus.NotIndexed;
/// <summary>Raison de l'échec d'indexation, affichée telle quelle au client.</summary>
public string AiIndexMessage { get; set; }
public DateTime? AiIndexedAt { get; set; }
public int AiChunkCount { get; set; } = 0;
public ResourceDTO ToDTO()
{
return new ResourceDTO()
{
id = Id,
label = Label,
type = Type,
url = Url,
dateCreation = DateCreation,
instanceId = InstanceId,
sizeBytes = SizeBytes
};
}
}
public enum ResourceType
{
// ⚠️ Persisté en int. Ne JAMAIS réordonner ni insérer au milieu : les valeurs
// existantes en base se décaleraient (les PDF deviendraient des JSON).
// Toute nouvelle valeur s'ajoute à la fin.
Image, // 0
Video, // 1
ImageUrl, // 2
VideoUrl, // 3
Audio, // 4
PDF, // 5
JSON, // 6
JSONUrl, // 7
Word, // 8
PowerPoint, // 9
Text // 10
}
public enum AiIndexStatus
{
NotIndexed = 0,
Pending = 1,
Indexed = 2,
Failed = 3
}
}