Updateinstance recopiait les champs un par un et n'avait jamais été étendu aux champs du guide : GuideName, GuidePersonaPrompt, GuideVoiceId, IsVisitorQuestionCollectionEnabled et GuideFallbackMessages étaient reçus puis ignorés, donc l'écran Guide IA ne sauvegardait rien. Reprend les mêmes gardes que Instance.FromDTO. Rien n'empêchait deux ApplicationInstance du même AppType sur une instance, alors que manager-app et AiController résolvent tous deux le canal par un FirstOrDefault sur (InstanceId, AppType) : un doublon rendait le canal ambigu, l'assistant pouvant s'afficher activé pendant que l'API répondait Forbid. Ajoute l'index unique et le contrôle en amont dans Create (409). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
533 lines
23 KiB
C#
533 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Hangfire;
|
|
using Manager.Services;
|
|
using ManagerService.Data;
|
|
using ManagerService.DTOs;
|
|
using ManagerService.Helpers;
|
|
using ManagerService.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NSwag.Annotations;
|
|
|
|
namespace ManagerService.Controllers
|
|
{
|
|
[Authorize(Policy = ManagerService.Service.Security.Policies.SuperAdmin)]
|
|
[ApiController, Route("api/[controller]")]
|
|
[OpenApiTag("Instance", Description = "Instance management")]
|
|
public class InstanceController : ControllerBase
|
|
{
|
|
private readonly MyInfoMateDbContext _myInfoMateDbContext;
|
|
|
|
private InstanceDatabaseService _instanceService;
|
|
private UserDatabaseService _userService;
|
|
private readonly ILogger<InstanceController> _logger;
|
|
private readonly ProfileLogic _profileLogic;
|
|
private readonly ApiKeyDatabaseService _apiKeyService;
|
|
// Injecté plutôt qu'appelé via la façade statique BackgroundJob : celle-ci lève
|
|
// sans JobStorage.Current, donc dans tout test qui touche cet endpoint.
|
|
private readonly IBackgroundJobClient _jobs;
|
|
IHexIdGeneratorService idService = new HexIdGeneratorService();
|
|
|
|
public InstanceController(ILogger<InstanceController> logger, InstanceDatabaseService instanceService, UserDatabaseService userService, ProfileLogic profileLogic, MyInfoMateDbContext myInfoMateDbContext, ApiKeyDatabaseService apiKeyService, IBackgroundJobClient jobs)
|
|
{
|
|
_logger = logger;
|
|
_instanceService = instanceService;
|
|
_userService = userService;
|
|
_profileLogic = profileLogic;
|
|
_myInfoMateDbContext = myInfoMateDbContext;
|
|
_apiKeyService = apiKeyService;
|
|
_jobs = jobs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a list of instance
|
|
/// </summary>
|
|
[ProducesResponseType(typeof(List<Instance>), 200)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpGet]
|
|
public ObjectResult Get()
|
|
{
|
|
try
|
|
{
|
|
//List<OldInstance> instances = _instanceService.GetAll();
|
|
List<Instance> instances = _myInfoMateDbContext.Instances.ToList();
|
|
|
|
return new OkObjectResult(instances);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get a specific instance
|
|
/// </summary>
|
|
/// <param name="id">id instance</param>
|
|
[ProducesResponseType(typeof(InstanceDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpGet("{id}")]
|
|
public ObjectResult GetDetail(string id)
|
|
{
|
|
try
|
|
{
|
|
Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.Id == id);
|
|
|
|
//OldInstance instance = _instanceService.GetById(id);
|
|
|
|
if (instance == null)
|
|
throw new KeyNotFoundException("This instance was not found");
|
|
|
|
var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
|
|
|
|
return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
return new NotFoundObjectResult(ex.Message) {};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recopie sur l'instance les valeurs portées par son plan. Elles sont dupliquées
|
|
/// volontairement — un client peut recevoir un geste commercial sans changer de plan —
|
|
/// mais elles doivent repartir du plan à chaque changement, sinon l'instance garde
|
|
/// les quotas de l'ancien.
|
|
/// </summary>
|
|
private void ApplyPlanQuotas(Instance instance)
|
|
{
|
|
if (instance.SubscriptionPlanId == null)
|
|
return;
|
|
|
|
var plan = _myInfoMateDbContext.SubscriptionPlans.FirstOrDefault(p => p.Id == instance.SubscriptionPlanId);
|
|
if (plan == null)
|
|
return;
|
|
|
|
instance.StorageQuotaBytes = plan.StorageQuotaBytes;
|
|
instance.AiTokensPerMonth = plan.AiTokensPerMonth;
|
|
instance.HasStats = plan.HasStats;
|
|
instance.StatsHistoryDays = plan.StatsHistoryDays;
|
|
instance.HasAdvancedStats = plan.HasAdvancedStats;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an instance
|
|
/// </summary>
|
|
/// <param name="newInstance">New instance info</param>
|
|
//[AllowAnonymous]
|
|
[ProducesResponseType(typeof(InstanceDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 400)]
|
|
[ProducesResponseType(typeof(string), 409)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpPost]
|
|
public ObjectResult CreateInstance([FromBody] InstanceDTO newInstance)
|
|
{
|
|
try
|
|
{
|
|
if (newInstance == null)
|
|
throw new ArgumentNullException("instance param is null");
|
|
|
|
Instance instance = new Instance().FromDTO(newInstance);
|
|
|
|
instance.DateCreation = DateTime.Now.ToUniversalTime();
|
|
instance.Id = idService.GenerateHexId();
|
|
|
|
// Copier les valeurs du plan comme valeurs par défaut
|
|
ApplyPlanQuotas(instance);
|
|
|
|
/*List<OldInstance> instances = _instanceService.GetAll();
|
|
Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.Id == id);*/
|
|
|
|
|
|
if (_myInfoMateDbContext.Instances.Any(i => i.Name == instance.Name))
|
|
throw new InvalidOperationException("This name is already used");
|
|
|
|
instance.WebSlug = SlugHelper.GenerateUniqueSlug(_myInfoMateDbContext, instance.Name);
|
|
instance.PublicApiKey = "ap_" + Convert.ToBase64String(
|
|
System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
|
|
.Replace("+", "-").Replace("/", "_").TrimEnd('=');
|
|
|
|
_myInfoMateDbContext.Instances.Add(instance);
|
|
_myInfoMateDbContext.SaveChanges();
|
|
|
|
var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
|
|
|
|
return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
|
|
}
|
|
catch (ArgumentNullException ex)
|
|
{
|
|
return new BadRequestObjectResult(ex.Message) {};
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return new ConflictObjectResult(ex.Message) {};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Update an instance
|
|
/// </summary>
|
|
/// <param name="updatedinstance">instance to update</param>
|
|
[ProducesResponseType(typeof(InstanceDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 400)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpPut]
|
|
public ObjectResult Updateinstance([FromBody] InstanceDTO updatedInstance)
|
|
{
|
|
try
|
|
{
|
|
if (updatedInstance == null)
|
|
throw new ArgumentNullException("instance param is null");
|
|
|
|
Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.Id == updatedInstance.id);
|
|
//OldInstance instance = _instanceService.GetById(updatedInstance.Id);
|
|
|
|
if (instance == null)
|
|
throw new KeyNotFoundException("instance does not exist");
|
|
|
|
instance.DateCreation = updatedInstance.dateCreation ?? instance.DateCreation;
|
|
instance.Name = updatedInstance.name ?? instance.Name;
|
|
instance.PinCode = updatedInstance.pinCode ?? instance.PinCode;
|
|
instance.IsPushNotification = updatedInstance.isPushNotification ?? instance.IsPushNotification;
|
|
instance.IsMobile = updatedInstance.isMobile ?? instance.IsMobile;
|
|
instance.IsTablet = updatedInstance.isTablet ?? instance.IsTablet;
|
|
instance.IsWeb = updatedInstance.isWeb ?? instance.IsWeb;
|
|
instance.IsVR = updatedInstance.isVR ?? instance.IsVR;
|
|
instance.IsAssistant = updatedInstance.isAssistant ?? instance.IsAssistant;
|
|
|
|
if (updatedInstance.guideName != null)
|
|
instance.GuideName = updatedInstance.guideName;
|
|
if (updatedInstance.guidePersonaPrompt != null)
|
|
instance.GuidePersonaPrompt = updatedInstance.guidePersonaPrompt;
|
|
if (updatedInstance.guideVoiceId != null)
|
|
instance.GuideVoiceId = updatedInstance.guideVoiceId;
|
|
if (updatedInstance.isVisitorQuestionCollectionEnabled != null)
|
|
instance.IsVisitorQuestionCollectionEnabled = updatedInstance.isVisitorQuestionCollectionEnabled.Value;
|
|
if (updatedInstance.guideFallbackMessages != null)
|
|
instance.GuideFallbackMessages = updatedInstance.guideFallbackMessages;
|
|
|
|
var previousPlanId = instance.SubscriptionPlanId;
|
|
var previousAiTokens = instance.AiTokensPerMonth;
|
|
|
|
if (updatedInstance.subscriptionPlanId == "")
|
|
instance.SubscriptionPlanId = null;
|
|
else if (updatedInstance.subscriptionPlanId != null)
|
|
instance.SubscriptionPlanId = updatedInstance.subscriptionPlanId;
|
|
|
|
// CreateInstance recopie les valeurs du plan, pas Update : changer un client de
|
|
// Starter à Premium ne lui donnait donc ni stockage ni jetons IA supplémentaires.
|
|
// L'endpoint /quota masquait la moitié du problème en retombant sur le plan à la
|
|
// lecture, mais AiController lit `instance.AiTokensPerMonth` — un client passé à
|
|
// un plan payant restait à 0, donc sans IA.
|
|
if (instance.SubscriptionPlanId != previousPlanId)
|
|
ApplyPlanQuotas(instance);
|
|
|
|
//OldInstance instanceModified = _instanceService.Update(updatedInstance.Id, instance);
|
|
_myInfoMateDbContext.SaveChanges();
|
|
|
|
// Le contenu déjà créé n'a jamais été indexé tant que l'instance n'avait pas
|
|
// droit à l'IA : sans ce rattrapage, le client paie un guide qui ne connaît rien.
|
|
if (previousAiTokens <= 0 && instance.AiTokensPerMonth > 0)
|
|
{
|
|
var backfilledInstanceId = instance.Id;
|
|
_jobs.Enqueue<IIngestionService>(s => s.BackfillInstanceAsync(backfilledInstanceId));
|
|
}
|
|
|
|
var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
|
|
|
|
return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
|
|
}
|
|
catch (ArgumentNullException ex)
|
|
{
|
|
return new BadRequestObjectResult(ex.Message) {};
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
return new NotFoundObjectResult(ex.Message) {};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Instance by web slug (public, used by visitapp-web)
|
|
/// </summary>
|
|
/// <param name="slug">Web slug of the instance</param>
|
|
[AllowAnonymous]
|
|
[ProducesResponseType(typeof(InstanceDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpGet("slug/{slug}")]
|
|
public ObjectResult GetInstanceBySlug(string slug)
|
|
{
|
|
try
|
|
{
|
|
Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.WebSlug == slug);
|
|
|
|
if (instance == null)
|
|
throw new KeyNotFoundException("Instance was not found");
|
|
|
|
var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
|
|
|
|
return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
return new NotFoundObjectResult(ex.Message) { };
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Instance by pincode
|
|
/// </summary>
|
|
/// <param name="pinCode">Code pin</param>
|
|
[AllowAnonymous]
|
|
[ProducesResponseType(typeof(InstanceDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpGet("byPin")]
|
|
public ObjectResult GetInstanceByPinCode([FromQuery] string pinCode)
|
|
{
|
|
try
|
|
{
|
|
//OldInstance instance = _instanceService.GetByPinCode(pinCode);
|
|
Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.PinCode == pinCode);
|
|
|
|
if (instance == null)
|
|
throw new KeyNotFoundException("Instance was not found");
|
|
|
|
var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
|
|
|
|
return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
return new NotFoundObjectResult(ex.Message) { };
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Bootstrap: get (or create) an API key for a Flutter app by PIN code
|
|
/// </summary>
|
|
/// <param name="pinCode">Instance PIN code</param>
|
|
/// <param name="appType">App type (VisitApp, TabletApp, Other)</param>
|
|
[AllowAnonymous]
|
|
[ProducesResponseType(typeof(object), 200)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpGet("app-key")]
|
|
public async Task<ObjectResult> GetAppKeyByPin([FromQuery] string pinCode, [FromQuery] ApiKeyAppType appType)
|
|
{
|
|
try
|
|
{
|
|
var instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.PinCode == pinCode);
|
|
if (instance == null)
|
|
return new NotFoundObjectResult("Instance not found");
|
|
|
|
var key = await _apiKeyService.GetOrCreateByPinAsync(instance.Id, appType);
|
|
return new OkObjectResult(new { key, instanceId = instance.Id });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get quota usage for an instance
|
|
/// </summary>
|
|
/// <param name="id">Id instance</param>
|
|
[Authorize(Policy = ManagerService.Service.Security.Policies.Viewer)]
|
|
[ProducesResponseType(typeof(InstanceQuotaDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpGet("{id}/quota")]
|
|
public ObjectResult GetQuota(string id)
|
|
{
|
|
try
|
|
{
|
|
var instance = _myInfoMateDbContext.Instances
|
|
.FirstOrDefault(i => i.Id == id);
|
|
|
|
if (instance == null)
|
|
return new NotFoundObjectResult("Instance not found");
|
|
|
|
var storageUsed = _myInfoMateDbContext.Resources
|
|
.Where(r => r.InstanceId == id)
|
|
.Sum(r => (long?)r.SizeBytes) ?? 0;
|
|
|
|
var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
|
|
var aiUsed = instance.AiUsageMonthKey == monthKey ? instance.AiTokensThisMonth : 0;
|
|
|
|
var storageQuota = instance.StorageQuotaBytes;
|
|
var aiQuota = instance.AiTokensPerMonth;
|
|
|
|
if ((storageQuota == 0 || aiQuota == 0) && instance.SubscriptionPlanId != null)
|
|
{
|
|
var plan = _myInfoMateDbContext.SubscriptionPlans.FirstOrDefault(p => p.Id == instance.SubscriptionPlanId);
|
|
if (plan != null)
|
|
{
|
|
// Même résolveur que le pré-vol de ResourceController : le chiffre
|
|
// affiché ici et celui qui bloque un téléversement doivent être le même.
|
|
storageQuota = StorageQuota.Resolve(storageQuota, plan.StorageQuotaBytes);
|
|
if (aiQuota == 0) aiQuota = plan.AiTokensPerMonth;
|
|
}
|
|
}
|
|
|
|
return new OkObjectResult(new InstanceQuotaDTO
|
|
{
|
|
storageUsedBytes = storageUsed,
|
|
storageQuotaBytes = storageQuota,
|
|
aiTokensUsed = aiUsed,
|
|
aiTokensPerMonth = aiQuota,
|
|
aiTokensPerQuestion = ResolveTokensPerQuestion(id)
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
private const long DefaultAiTokensPerQuestion = 10_000;
|
|
private const int MinQuestionSampleSize = 20;
|
|
|
|
/// <summary>
|
|
/// Coût moyen d'une question, en jetons. Mesuré sur les questions réellement posées par
|
|
/// l'instance ; à défaut, l'hypothèse de la grille tarifaire (Premium = 20 M de jetons
|
|
/// pour ~2 000 questions, cf. le seed de MyInfoMateDbContext).
|
|
///
|
|
/// ⚠️ Le seuil d'échantillon n'est pas de la prudence gratuite : une seule question dont
|
|
/// la réponse cite un long article suffirait à doubler la moyenne, et le gestionnaire
|
|
/// verrait son crédit restant changer de moitié d'un rafraîchissement à l'autre.
|
|
/// </summary>
|
|
private long ResolveTokensPerQuestion(string instanceId)
|
|
{
|
|
var sample = _myInfoMateDbContext.VisitorQuestions
|
|
.Where(q => q.InstanceId == instanceId && q.TokensUsed > 0)
|
|
.Select(q => q.TokensUsed)
|
|
.ToList();
|
|
|
|
if (sample.Count < MinQuestionSampleSize)
|
|
return DefaultAiTokensPerQuestion;
|
|
|
|
return (long)Math.Round(sample.Average());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete an instance
|
|
/// </summary>
|
|
/// <param name="id">Id of instance to delete</param>
|
|
[ProducesResponseType(typeof(string), 202)]
|
|
[ProducesResponseType(typeof(string), 400)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpDelete("{id}")]
|
|
public ObjectResult DeleteInstance(string id)
|
|
{
|
|
try
|
|
{
|
|
if (id == null)
|
|
throw new ArgumentNullException("instance param is null");
|
|
|
|
//OldInstance instance = _instanceService.GetById(id);
|
|
Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.Id == id);
|
|
|
|
if (instance == null)
|
|
throw new KeyNotFoundException("instance does not exist");
|
|
|
|
// Delete all user in instance
|
|
//List<OldUser> users = _userService.GetByInstanceId(instance.Id);
|
|
List<User> users = _myInfoMateDbContext.Users.Where(u => u.InstanceId == instance.Id).ToList();
|
|
|
|
foreach (var user in users)
|
|
{
|
|
//_userService.Remove(user.Id);
|
|
_myInfoMateDbContext.Users.Remove(user);
|
|
}
|
|
|
|
//_instanceService.Remove(id);
|
|
_myInfoMateDbContext.Instances.Remove(instance);
|
|
|
|
_myInfoMateDbContext.SaveChanges();
|
|
|
|
return new ObjectResult("The instance has been deleted") { StatusCode = 202 };
|
|
|
|
}
|
|
catch (ArgumentNullException ex)
|
|
{
|
|
return new BadRequestObjectResult(ex.Message) { };
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
return new NotFoundObjectResult(ex.Message) { };
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Generate (or regenerate) WebSlug and PublicApiKey for an existing instance
|
|
/// </summary>
|
|
/// <param name="id">Id of the instance</param>
|
|
[ProducesResponseType(typeof(InstanceDTO), 200)]
|
|
[ProducesResponseType(typeof(string), 404)]
|
|
[ProducesResponseType(typeof(string), 500)]
|
|
[HttpPost("{id}/generate-web-keys")]
|
|
public ObjectResult GenerateWebKeys(string id)
|
|
{
|
|
try
|
|
{
|
|
var instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.Id == id);
|
|
if (instance == null)
|
|
return new NotFoundObjectResult("Instance not found");
|
|
|
|
if (string.IsNullOrEmpty(instance.WebSlug))
|
|
instance.WebSlug = SlugHelper.GenerateUniqueSlug(_myInfoMateDbContext, instance.Name);
|
|
|
|
instance.PublicApiKey = "ap_" + Convert.ToBase64String(
|
|
System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
|
|
.Replace("+", "-").Replace("/", "_").TrimEnd('=');
|
|
|
|
_myInfoMateDbContext.SaveChanges();
|
|
|
|
var applicationInstances = _myInfoMateDbContext.ApplicationInstances.Where(ai => ai.InstanceId == instance.Id).ToList();
|
|
return new OkObjectResult(instance.ToDTO(applicationInstances.Select(ai => ai.ToDTO(_myInfoMateDbContext)).ToList()));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
|
}
|
|
}
|
|
}
|
|
}
|