Add support for web + misc
This commit is contained in:
parent
00f702b3da
commit
9633ae43b7
@ -137,7 +137,11 @@ namespace ManagerService.Controllers
|
||||
if (_myInfoMateDbContext.Instances.Any(i => i.Name == instance.Name))
|
||||
throw new InvalidOperationException("This name is already used");
|
||||
|
||||
//OldInstance instanceCreated = _instanceService.Create(newInstance);
|
||||
instance.WebSlug = GenerateUniqueSlug(instance.Name);
|
||||
instance.PublicApiKey = "ap_" + Convert.ToBase64String(
|
||||
System.Security.Cryptography.RandomNumberGenerator.GetBytes(32))
|
||||
.Replace("+", "-").Replace("/", "_").TrimEnd('=');
|
||||
|
||||
_myInfoMateDbContext.Instances.Add(instance);
|
||||
_myInfoMateDbContext.SaveChanges();
|
||||
|
||||
@ -218,6 +222,38 @@ namespace ManagerService.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@ -385,5 +421,59 @@ namespace ManagerService.Controllers
|
||||
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 = GenerateUniqueSlug(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 };
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateUniqueSlug(string name)
|
||||
{
|
||||
var slug = name.ToLowerInvariant();
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[àáâãäå]", "a");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[èéêë]", "e");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ìíîï]", "i");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[òóôõö]", "o");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ùúûü]", "u");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ç]", "c");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[ñ]", "n");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[^a-z0-9\s-]", "");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[\s-]+", "-").Trim('-');
|
||||
|
||||
var baseSlug = slug;
|
||||
var counter = 1;
|
||||
while (_myInfoMateDbContext.Instances.Any(i => i.WebSlug == slug))
|
||||
slug = $"{baseSlug}-{counter++}";
|
||||
|
||||
return slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +197,7 @@ namespace ManagerService.Controllers
|
||||
switch (section.Type)
|
||||
{
|
||||
case SectionType.Agenda:
|
||||
var eventAgendas = _myInfoMateDbContext.EventAgendas.Where(ea => ea.SectionAgendaId == section.Id)/*.OrderBy(gp => gp.or)*/.ToList();
|
||||
var eventAgendas = _myInfoMateDbContext.EventAgendas.Where(ea => ea.SectionAgendaId == section.Id).Include(ea => ea.Resource).ToList();
|
||||
List<EventAgendaDTO> eventAgendaDTOs = new List<EventAgendaDTO>();
|
||||
foreach (var eventAgenda in eventAgendas)
|
||||
{
|
||||
@ -213,6 +213,20 @@ namespace ManagerService.Controllers
|
||||
(dto as SectionEventDTO).Programme = sectionEvent.Programme; // TODO test ! Need dto ?
|
||||
(dto as SectionEventDTO).GlobalMapAnnotations = sectionEvent.GlobalMapAnnotations?.Select(ma => ma.ToDTO()).ToList() ?? new();
|
||||
break;
|
||||
case SectionType.Slider:
|
||||
var sliderContents = (dto as SliderDTO).contents;
|
||||
if (sliderContents != null)
|
||||
{
|
||||
foreach (var content in sliderContents)
|
||||
{
|
||||
if (content.resourceId != null && content.resource == null)
|
||||
{
|
||||
var contentResource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == content.resourceId);
|
||||
content.resource = contentResource?.ToDTO();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SectionType.Game:
|
||||
Resource resource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (dto as GameDTO).puzzleImageId);
|
||||
(dto as GameDTO).puzzleImage = resource.ToDTO();
|
||||
@ -267,6 +281,20 @@ namespace ManagerService.Controllers
|
||||
var subDTO = SectionFactory.ToDTO(subSection);
|
||||
switch (subSection.Type)
|
||||
{
|
||||
case SectionType.Slider:
|
||||
var sliderContentsSub = (subDTO as SliderDTO).contents;
|
||||
if (sliderContentsSub != null)
|
||||
{
|
||||
foreach (var content in sliderContentsSub)
|
||||
{
|
||||
if (content.resourceId != null && content.resource == null)
|
||||
{
|
||||
var contentResource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == content.resourceId);
|
||||
content.resource = contentResource?.ToDTO();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SectionType.Game:
|
||||
Resource resourceSub = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (subDTO as GameDTO).puzzleImageId);
|
||||
(subDTO as GameDTO).puzzleImage = resourceSub?.ToDTO();
|
||||
|
||||
@ -18,6 +18,10 @@ namespace ManagerService.DTOs
|
||||
|
||||
public bool? isAssistant { get; set; }
|
||||
|
||||
public string? webSlug { get; set; }
|
||||
|
||||
public string? publicApiKey { get; set; }
|
||||
|
||||
public string? subscriptionPlanId { get; set; }
|
||||
public SubscriptionPlanDTO? subscriptionPlan { get; set; }
|
||||
public int? aiRequestsThisMonth { get; set; }
|
||||
|
||||
@ -72,8 +72,8 @@ namespace ManagerService.DTOs
|
||||
// Handle YYYYMMDD format (e.g. "20260327")
|
||||
if (dateStr.Length == 8 && long.TryParse(dateStr, out _))
|
||||
if (DateTime.TryParseExact(dateStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var dt8))
|
||||
return dt8;
|
||||
if (DateTime.TryParse(dateStr, out var dt)) return dt;
|
||||
return DateTime.SpecifyKind(dt8, DateTimeKind.Utc);
|
||||
if (DateTime.TryParse(dateStr, out var dt)) return DateTime.SpecifyKind(dt, DateTimeKind.Utc);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,10 @@ namespace ManagerService.Data
|
||||
|
||||
public bool IsAssistant { get; set; }
|
||||
|
||||
public string? WebSlug { get; set; }
|
||||
|
||||
public string? PublicApiKey { get; set; }
|
||||
|
||||
public string? SubscriptionPlanId { get; set; }
|
||||
|
||||
[ForeignKey("SubscriptionPlanId")]
|
||||
@ -71,6 +75,8 @@ namespace ManagerService.Data
|
||||
isWeb = IsWeb,
|
||||
isVR = IsVR,
|
||||
isAssistant = IsAssistant,
|
||||
webSlug = WebSlug,
|
||||
publicApiKey = PublicApiKey,
|
||||
subscriptionPlanId = SubscriptionPlanId,
|
||||
subscriptionPlan = SubscriptionPlan?.ToDTO(),
|
||||
aiRequestsThisMonth = AiRequestsThisMonth,
|
||||
@ -96,6 +102,10 @@ namespace ManagerService.Data
|
||||
IsWeb = instanceDTO.isWeb ?? false;
|
||||
IsVR = instanceDTO.isVR ?? false;
|
||||
IsAssistant = instanceDTO.isAssistant ?? false;
|
||||
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)
|
||||
|
||||
1584
ManagerService/Migrations/20260428113203_AddWebSlugAndPublicApiKeyToInstance.Designer.cs
generated
Normal file
1584
ManagerService/Migrations/20260428113203_AddWebSlugAndPublicApiKeyToInstance.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ManagerService.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddWebSlugAndPublicApiKeyToInstance : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PublicApiKey",
|
||||
table: "Instances",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WebSlug",
|
||||
table: "Instances",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublicApiKey",
|
||||
table: "Instances");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WebSlug",
|
||||
table: "Instances");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -382,6 +382,9 @@ namespace ManagerService.Migrations
|
||||
b.Property<string>("PinCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PublicApiKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("StatsHistoryDays")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@ -391,6 +394,9 @@ namespace ManagerService.Migrations
|
||||
b.Property<string>("SubscriptionPlanId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("WebSlug")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SubscriptionPlanId");
|
||||
|
||||
@ -39,15 +39,31 @@ namespace ManagerService.Security
|
||||
(k.DateExpiration == null || k.DateExpiration > System.DateTime.UtcNow) &&
|
||||
(k.Key == value || k.KeyHash == keyHash));
|
||||
|
||||
if (apiKey == null)
|
||||
string instanceId;
|
||||
string appType;
|
||||
|
||||
if (apiKey != null)
|
||||
{
|
||||
instanceId = apiKey.InstanceId;
|
||||
appType = apiKey.AppType.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = await _db.Instances.FirstOrDefaultAsync(i => i.PublicApiKey == value);
|
||||
if (instance == null)
|
||||
return AuthenticateResult.Fail("Invalid API Key");
|
||||
|
||||
instanceId = instance.Id;
|
||||
appType = "Web";
|
||||
}
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(Service.Security.ClaimTypes.InstanceId, apiKey.InstanceId),
|
||||
new Claim(Service.Security.ClaimTypes.AppType, apiKey.AppType.ToString()),
|
||||
new Claim(Service.Security.ClaimTypes.InstanceId, instanceId),
|
||||
new Claim(Service.Security.ClaimTypes.AppType, appType),
|
||||
new Claim(Service.Security.ClaimTypes.Permission, Service.Security.Permissions.AppRead),
|
||||
new Claim(Service.Security.ClaimTypes.Permission, Service.Security.Permissions.Viewer),
|
||||
new Claim(Service.Security.ClaimTypes.Permission, Service.Security.Permissions.ContentEditor),
|
||||
};
|
||||
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
|
||||
|
||||
@ -51,6 +51,7 @@ namespace ManagerService.Services
|
||||
|
||||
var section = db.Sections.OfType<SectionAgenda>()
|
||||
.Include(sa => sa.EventAgendas)
|
||||
.ThenInclude(ea => ea.Resource)
|
||||
.FirstOrDefault(sa => sa.Id == sectionAgendaId);
|
||||
|
||||
if (section == null || !section.IsOnlineAgenda || section.AgendaResourceIds == null)
|
||||
@ -115,6 +116,25 @@ namespace ManagerService.Services
|
||||
existing.Website = remote.website;
|
||||
existing.IdVideoYoutube = remote.id_video_youtube;
|
||||
existing.IsSynced = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(remote.image) && string.IsNullOrEmpty(existing.ResourceId))
|
||||
{
|
||||
var imageResource = db.Resources.FirstOrDefault(r => r.Url == remote.image && r.InstanceId == section.InstanceId);
|
||||
if (imageResource == null)
|
||||
{
|
||||
imageResource = new Resource
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Type = ResourceType.ImageUrl,
|
||||
Label = remote.name ?? "agenda-image",
|
||||
Url = remote.image,
|
||||
InstanceId = section.InstanceId,
|
||||
DateCreation = DateTime.UtcNow,
|
||||
};
|
||||
db.Resources.Add(imageResource);
|
||||
}
|
||||
existing.ResourceId = imageResource.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -384,6 +384,7 @@ namespace ManagerService.Services
|
||||
order = agenda.Order,
|
||||
imageId = agenda.ImageId,
|
||||
imageSource = agenda.ImageSource,
|
||||
isActive = agenda.IsActive,
|
||||
isSubSection = agenda.IsSubSection,
|
||||
parentId = agenda.ParentId,
|
||||
isBeacon = agenda.IsBeacon,
|
||||
@ -409,6 +410,7 @@ namespace ManagerService.Services
|
||||
order = article.Order,
|
||||
imageId = article.ImageId,
|
||||
imageSource = article.ImageSource,
|
||||
isActive = article.IsActive,
|
||||
isSubSection = article.IsSubSection,
|
||||
parentId = article.ParentId,
|
||||
isBeacon = article.IsBeacon,
|
||||
@ -435,6 +437,7 @@ namespace ManagerService.Services
|
||||
order = sectionEvent.Order,
|
||||
imageId = sectionEvent.ImageId,
|
||||
imageSource = sectionEvent.ImageSource,
|
||||
isActive = sectionEvent.IsActive,
|
||||
isSubSection = sectionEvent.IsSubSection,
|
||||
parentId = sectionEvent.ParentId,
|
||||
isBeacon = sectionEvent.IsBeacon,
|
||||
@ -462,6 +465,7 @@ namespace ManagerService.Services
|
||||
order = map.Order,
|
||||
imageId = map.ImageId,
|
||||
imageSource = map.ImageSource,
|
||||
isActive = map.IsActive,
|
||||
isSubSection = map.IsSubSection,
|
||||
parentId = map.ParentId,
|
||||
isBeacon = map.IsBeacon,
|
||||
@ -493,6 +497,7 @@ namespace ManagerService.Services
|
||||
order = menu.Order,
|
||||
imageId = menu.ImageId,
|
||||
imageSource = menu.ImageSource,
|
||||
isActive = menu.IsActive,
|
||||
isSubSection = menu.IsSubSection,
|
||||
parentId = menu.ParentId,
|
||||
isBeacon = menu.IsBeacon,
|
||||
@ -515,6 +520,7 @@ namespace ManagerService.Services
|
||||
order = pdf.Order,
|
||||
imageId = pdf.ImageId,
|
||||
imageSource = pdf.ImageSource,
|
||||
isActive = pdf.IsActive,
|
||||
isSubSection = pdf.IsSubSection,
|
||||
parentId = pdf.ParentId,
|
||||
isBeacon = pdf.IsBeacon,
|
||||
@ -537,6 +543,7 @@ namespace ManagerService.Services
|
||||
order = game.Order,
|
||||
imageId = game.ImageId,
|
||||
imageSource = game.ImageSource,
|
||||
isActive = game.IsActive,
|
||||
isSubSection = game.IsSubSection,
|
||||
parentId = game.ParentId,
|
||||
isBeacon = game.IsBeacon,
|
||||
@ -565,6 +572,7 @@ namespace ManagerService.Services
|
||||
order = quiz.Order,
|
||||
imageId = quiz.ImageId,
|
||||
imageSource = quiz.ImageSource,
|
||||
isActive = quiz.IsActive,
|
||||
isSubSection = quiz.IsSubSection,
|
||||
parentId = quiz.ParentId,
|
||||
isBeacon = quiz.IsBeacon,
|
||||
@ -591,6 +599,7 @@ namespace ManagerService.Services
|
||||
order = slider.Order,
|
||||
imageId = slider.ImageId,
|
||||
imageSource = slider.ImageSource,
|
||||
isActive = slider.IsActive,
|
||||
isSubSection = slider.IsSubSection,
|
||||
parentId = slider.ParentId,
|
||||
isBeacon = slider.IsBeacon,
|
||||
@ -613,6 +622,7 @@ namespace ManagerService.Services
|
||||
order = video.Order,
|
||||
imageId = video.ImageId,
|
||||
imageSource = video.ImageSource,
|
||||
isActive = video.IsActive,
|
||||
isSubSection = video.IsSubSection,
|
||||
parentId = video.ParentId,
|
||||
isBeacon = video.IsBeacon,
|
||||
@ -635,6 +645,7 @@ namespace ManagerService.Services
|
||||
order = weather.Order,
|
||||
imageId = weather.ImageId,
|
||||
imageSource = weather.ImageSource,
|
||||
isActive = weather.IsActive,
|
||||
isSubSection = weather.IsSubSection,
|
||||
parentId = weather.ParentId,
|
||||
isBeacon = weather.IsBeacon,
|
||||
@ -659,6 +670,7 @@ namespace ManagerService.Services
|
||||
order = web.Order,
|
||||
imageId = web.ImageId,
|
||||
imageSource = web.ImageSource,
|
||||
isActive = web.IsActive,
|
||||
isSubSection = web.IsSubSection,
|
||||
parentId = web.ParentId,
|
||||
isBeacon = web.IsBeacon,
|
||||
|
||||
@ -250,23 +250,27 @@ namespace ManagerService
|
||||
app.UseCors(
|
||||
#if DEBUG
|
||||
options => options
|
||||
.SetIsOriginAllowed(origin => string.IsNullOrEmpty(origin) || origin == "http://localhost:9090")
|
||||
.SetIsOriginAllowed(origin => string.IsNullOrEmpty(origin) || origin == "http://localhost:9090" || origin == "http://localhost:3000")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials()
|
||||
#else
|
||||
options => options
|
||||
.SetIsOriginAllowed(origin => string.IsNullOrEmpty(origin) || origin == "https://manager.mymuseum.be" || origin == "https://manager.myinfomate.be" || origin == "https://visitnamur.myinfomate.be" || origin == "https://fortsaintheribert.myinfomate.be")
|
||||
.SetIsOriginAllowed(origin => string.IsNullOrEmpty(origin) || origin == "https://manager.mymuseum.be" || origin == "https://manager.myinfomate.be" || origin == "https://visitnamur.myinfomate.be" || origin == "https://fortsaintheribert.myinfomate.be" || origin == "https://app.myinfomate.be")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials()
|
||||
#endif
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
app.UseHangfireDashboard("/hangfire");
|
||||
#else
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
||||
{
|
||||
Authorization = new[] { new HangfireDashboardAuthorizationFilter() }
|
||||
});
|
||||
#endif
|
||||
|
||||
RecurringJob.AddOrUpdate<AgendaSyncService>(
|
||||
"agenda-sync-daily",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user