Add support for web + misc

This commit is contained in:
Thomas Fransolet 2026-05-07 16:49:56 +02:00
parent 00f702b3da
commit 9633ae43b7
12 changed files with 1822 additions and 10 deletions

View File

@ -137,7 +137,11 @@ namespace ManagerService.Controllers
if (_myInfoMateDbContext.Instances.Any(i => i.Name == instance.Name)) if (_myInfoMateDbContext.Instances.Any(i => i.Name == instance.Name))
throw new InvalidOperationException("This name is already used"); 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.Instances.Add(instance);
_myInfoMateDbContext.SaveChanges(); _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> /// <summary>
/// Get Instance by pincode /// Get Instance by pincode
/// </summary> /// </summary>
@ -385,5 +421,59 @@ namespace ManagerService.Controllers
return new ObjectResult(ex.Message) { StatusCode = 500 }; 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;
}
} }
} }

View File

@ -197,7 +197,7 @@ namespace ManagerService.Controllers
switch (section.Type) switch (section.Type)
{ {
case SectionType.Agenda: 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>(); List<EventAgendaDTO> eventAgendaDTOs = new List<EventAgendaDTO>();
foreach (var eventAgenda in eventAgendas) 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).Programme = sectionEvent.Programme; // TODO test ! Need dto ?
(dto as SectionEventDTO).GlobalMapAnnotations = sectionEvent.GlobalMapAnnotations?.Select(ma => ma.ToDTO()).ToList() ?? new(); (dto as SectionEventDTO).GlobalMapAnnotations = sectionEvent.GlobalMapAnnotations?.Select(ma => ma.ToDTO()).ToList() ?? new();
break; 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: case SectionType.Game:
Resource resource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (dto as GameDTO).puzzleImageId); Resource resource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (dto as GameDTO).puzzleImageId);
(dto as GameDTO).puzzleImage = resource.ToDTO(); (dto as GameDTO).puzzleImage = resource.ToDTO();
@ -267,6 +281,20 @@ namespace ManagerService.Controllers
var subDTO = SectionFactory.ToDTO(subSection); var subDTO = SectionFactory.ToDTO(subSection);
switch (subSection.Type) 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: case SectionType.Game:
Resource resourceSub = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (subDTO as GameDTO).puzzleImageId); Resource resourceSub = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == (subDTO as GameDTO).puzzleImageId);
(subDTO as GameDTO).puzzleImage = resourceSub?.ToDTO(); (subDTO as GameDTO).puzzleImage = resourceSub?.ToDTO();

View File

@ -18,6 +18,10 @@ namespace ManagerService.DTOs
public bool? isAssistant { get; set; } public bool? isAssistant { get; set; }
public string? webSlug { get; set; }
public string? publicApiKey { get; set; }
public string? subscriptionPlanId { get; set; } public string? subscriptionPlanId { get; set; }
public SubscriptionPlanDTO? subscriptionPlan { get; set; } public SubscriptionPlanDTO? subscriptionPlan { get; set; }
public int? aiRequestsThisMonth { get; set; } public int? aiRequestsThisMonth { get; set; }

View File

@ -72,8 +72,8 @@ namespace ManagerService.DTOs
// Handle YYYYMMDD format (e.g. "20260327") // Handle YYYYMMDD format (e.g. "20260327")
if (dateStr.Length == 8 && long.TryParse(dateStr, out _)) if (dateStr.Length == 8 && long.TryParse(dateStr, out _))
if (DateTime.TryParseExact(dateStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var dt8)) if (DateTime.TryParseExact(dateStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var dt8))
return dt8; return DateTime.SpecifyKind(dt8, DateTimeKind.Utc);
if (DateTime.TryParse(dateStr, out var dt)) return dt; if (DateTime.TryParse(dateStr, out var dt)) return DateTime.SpecifyKind(dt, DateTimeKind.Utc);
return null; return null;
} }
} }

View File

@ -36,6 +36,10 @@ namespace ManagerService.Data
public bool IsAssistant { get; set; } public bool IsAssistant { get; set; }
public string? WebSlug { get; set; }
public string? PublicApiKey { get; set; }
public string? SubscriptionPlanId { get; set; } public string? SubscriptionPlanId { get; set; }
[ForeignKey("SubscriptionPlanId")] [ForeignKey("SubscriptionPlanId")]
@ -71,6 +75,8 @@ namespace ManagerService.Data
isWeb = IsWeb, isWeb = IsWeb,
isVR = IsVR, isVR = IsVR,
isAssistant = IsAssistant, isAssistant = IsAssistant,
webSlug = WebSlug,
publicApiKey = PublicApiKey,
subscriptionPlanId = SubscriptionPlanId, subscriptionPlanId = SubscriptionPlanId,
subscriptionPlan = SubscriptionPlan?.ToDTO(), subscriptionPlan = SubscriptionPlan?.ToDTO(),
aiRequestsThisMonth = AiRequestsThisMonth, aiRequestsThisMonth = AiRequestsThisMonth,
@ -96,6 +102,10 @@ namespace ManagerService.Data
IsWeb = instanceDTO.isWeb ?? false; IsWeb = instanceDTO.isWeb ?? false;
IsVR = instanceDTO.isVR ?? false; IsVR = instanceDTO.isVR ?? false;
IsAssistant = instanceDTO.isAssistant ?? false; IsAssistant = instanceDTO.isAssistant ?? false;
if (instanceDTO.webSlug != null)
WebSlug = instanceDTO.webSlug;
if (instanceDTO.publicApiKey != null)
PublicApiKey = instanceDTO.publicApiKey;
if (instanceDTO.subscriptionPlanId != null) if (instanceDTO.subscriptionPlanId != null)
SubscriptionPlanId = instanceDTO.subscriptionPlanId; SubscriptionPlanId = instanceDTO.subscriptionPlanId;
if (instanceDTO.storageQuotaBytes.HasValue) if (instanceDTO.storageQuotaBytes.HasValue)

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -382,6 +382,9 @@ namespace ManagerService.Migrations
b.Property<string>("PinCode") b.Property<string>("PinCode")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("PublicApiKey")
.HasColumnType("text");
b.Property<int>("StatsHistoryDays") b.Property<int>("StatsHistoryDays")
.HasColumnType("integer"); .HasColumnType("integer");
@ -391,6 +394,9 @@ namespace ManagerService.Migrations
b.Property<string>("SubscriptionPlanId") b.Property<string>("SubscriptionPlanId")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("WebSlug")
.HasColumnType("text");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("SubscriptionPlanId"); b.HasIndex("SubscriptionPlanId");

View File

@ -39,15 +39,31 @@ namespace ManagerService.Security
(k.DateExpiration == null || k.DateExpiration > System.DateTime.UtcNow) && (k.DateExpiration == null || k.DateExpiration > System.DateTime.UtcNow) &&
(k.Key == value || k.KeyHash == keyHash)); (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"); return AuthenticateResult.Fail("Invalid API Key");
instanceId = instance.Id;
appType = "Web";
}
var claims = new[] var claims = new[]
{ {
new Claim(Service.Security.ClaimTypes.InstanceId, apiKey.InstanceId), new Claim(Service.Security.ClaimTypes.InstanceId, instanceId),
new Claim(Service.Security.ClaimTypes.AppType, apiKey.AppType.ToString()), 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.AppRead),
new Claim(Service.Security.ClaimTypes.Permission, Service.Security.Permissions.Viewer), 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)); var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));

View File

@ -51,6 +51,7 @@ namespace ManagerService.Services
var section = db.Sections.OfType<SectionAgenda>() var section = db.Sections.OfType<SectionAgenda>()
.Include(sa => sa.EventAgendas) .Include(sa => sa.EventAgendas)
.ThenInclude(ea => ea.Resource)
.FirstOrDefault(sa => sa.Id == sectionAgendaId); .FirstOrDefault(sa => sa.Id == sectionAgendaId);
if (section == null || !section.IsOnlineAgenda || section.AgendaResourceIds == null) if (section == null || !section.IsOnlineAgenda || section.AgendaResourceIds == null)
@ -115,6 +116,25 @@ namespace ManagerService.Services
existing.Website = remote.website; existing.Website = remote.website;
existing.IdVideoYoutube = remote.id_video_youtube; existing.IdVideoYoutube = remote.id_video_youtube;
existing.IsSynced = true; 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;
}
} }
} }

View File

@ -384,6 +384,7 @@ namespace ManagerService.Services
order = agenda.Order, order = agenda.Order,
imageId = agenda.ImageId, imageId = agenda.ImageId,
imageSource = agenda.ImageSource, imageSource = agenda.ImageSource,
isActive = agenda.IsActive,
isSubSection = agenda.IsSubSection, isSubSection = agenda.IsSubSection,
parentId = agenda.ParentId, parentId = agenda.ParentId,
isBeacon = agenda.IsBeacon, isBeacon = agenda.IsBeacon,
@ -409,6 +410,7 @@ namespace ManagerService.Services
order = article.Order, order = article.Order,
imageId = article.ImageId, imageId = article.ImageId,
imageSource = article.ImageSource, imageSource = article.ImageSource,
isActive = article.IsActive,
isSubSection = article.IsSubSection, isSubSection = article.IsSubSection,
parentId = article.ParentId, parentId = article.ParentId,
isBeacon = article.IsBeacon, isBeacon = article.IsBeacon,
@ -435,6 +437,7 @@ namespace ManagerService.Services
order = sectionEvent.Order, order = sectionEvent.Order,
imageId = sectionEvent.ImageId, imageId = sectionEvent.ImageId,
imageSource = sectionEvent.ImageSource, imageSource = sectionEvent.ImageSource,
isActive = sectionEvent.IsActive,
isSubSection = sectionEvent.IsSubSection, isSubSection = sectionEvent.IsSubSection,
parentId = sectionEvent.ParentId, parentId = sectionEvent.ParentId,
isBeacon = sectionEvent.IsBeacon, isBeacon = sectionEvent.IsBeacon,
@ -462,6 +465,7 @@ namespace ManagerService.Services
order = map.Order, order = map.Order,
imageId = map.ImageId, imageId = map.ImageId,
imageSource = map.ImageSource, imageSource = map.ImageSource,
isActive = map.IsActive,
isSubSection = map.IsSubSection, isSubSection = map.IsSubSection,
parentId = map.ParentId, parentId = map.ParentId,
isBeacon = map.IsBeacon, isBeacon = map.IsBeacon,
@ -493,6 +497,7 @@ namespace ManagerService.Services
order = menu.Order, order = menu.Order,
imageId = menu.ImageId, imageId = menu.ImageId,
imageSource = menu.ImageSource, imageSource = menu.ImageSource,
isActive = menu.IsActive,
isSubSection = menu.IsSubSection, isSubSection = menu.IsSubSection,
parentId = menu.ParentId, parentId = menu.ParentId,
isBeacon = menu.IsBeacon, isBeacon = menu.IsBeacon,
@ -515,6 +520,7 @@ namespace ManagerService.Services
order = pdf.Order, order = pdf.Order,
imageId = pdf.ImageId, imageId = pdf.ImageId,
imageSource = pdf.ImageSource, imageSource = pdf.ImageSource,
isActive = pdf.IsActive,
isSubSection = pdf.IsSubSection, isSubSection = pdf.IsSubSection,
parentId = pdf.ParentId, parentId = pdf.ParentId,
isBeacon = pdf.IsBeacon, isBeacon = pdf.IsBeacon,
@ -537,6 +543,7 @@ namespace ManagerService.Services
order = game.Order, order = game.Order,
imageId = game.ImageId, imageId = game.ImageId,
imageSource = game.ImageSource, imageSource = game.ImageSource,
isActive = game.IsActive,
isSubSection = game.IsSubSection, isSubSection = game.IsSubSection,
parentId = game.ParentId, parentId = game.ParentId,
isBeacon = game.IsBeacon, isBeacon = game.IsBeacon,
@ -565,6 +572,7 @@ namespace ManagerService.Services
order = quiz.Order, order = quiz.Order,
imageId = quiz.ImageId, imageId = quiz.ImageId,
imageSource = quiz.ImageSource, imageSource = quiz.ImageSource,
isActive = quiz.IsActive,
isSubSection = quiz.IsSubSection, isSubSection = quiz.IsSubSection,
parentId = quiz.ParentId, parentId = quiz.ParentId,
isBeacon = quiz.IsBeacon, isBeacon = quiz.IsBeacon,
@ -591,6 +599,7 @@ namespace ManagerService.Services
order = slider.Order, order = slider.Order,
imageId = slider.ImageId, imageId = slider.ImageId,
imageSource = slider.ImageSource, imageSource = slider.ImageSource,
isActive = slider.IsActive,
isSubSection = slider.IsSubSection, isSubSection = slider.IsSubSection,
parentId = slider.ParentId, parentId = slider.ParentId,
isBeacon = slider.IsBeacon, isBeacon = slider.IsBeacon,
@ -613,6 +622,7 @@ namespace ManagerService.Services
order = video.Order, order = video.Order,
imageId = video.ImageId, imageId = video.ImageId,
imageSource = video.ImageSource, imageSource = video.ImageSource,
isActive = video.IsActive,
isSubSection = video.IsSubSection, isSubSection = video.IsSubSection,
parentId = video.ParentId, parentId = video.ParentId,
isBeacon = video.IsBeacon, isBeacon = video.IsBeacon,
@ -635,6 +645,7 @@ namespace ManagerService.Services
order = weather.Order, order = weather.Order,
imageId = weather.ImageId, imageId = weather.ImageId,
imageSource = weather.ImageSource, imageSource = weather.ImageSource,
isActive = weather.IsActive,
isSubSection = weather.IsSubSection, isSubSection = weather.IsSubSection,
parentId = weather.ParentId, parentId = weather.ParentId,
isBeacon = weather.IsBeacon, isBeacon = weather.IsBeacon,
@ -659,6 +670,7 @@ namespace ManagerService.Services
order = web.Order, order = web.Order,
imageId = web.ImageId, imageId = web.ImageId,
imageSource = web.ImageSource, imageSource = web.ImageSource,
isActive = web.IsActive,
isSubSection = web.IsSubSection, isSubSection = web.IsSubSection,
parentId = web.ParentId, parentId = web.ParentId,
isBeacon = web.IsBeacon, isBeacon = web.IsBeacon,

View File

@ -250,23 +250,27 @@ namespace ManagerService
app.UseCors( app.UseCors(
#if DEBUG #if DEBUG
options => options 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() .AllowAnyMethod()
.AllowAnyHeader() .AllowAnyHeader()
.AllowCredentials() .AllowCredentials()
#else #else
options => options 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() .AllowAnyMethod()
.AllowAnyHeader() .AllowAnyHeader()
.AllowCredentials() .AllowCredentials()
#endif #endif
); );
#if DEBUG
app.UseHangfireDashboard("/hangfire");
#else
app.UseHangfireDashboard("/hangfire", new DashboardOptions app.UseHangfireDashboard("/hangfire", new DashboardOptions
{ {
Authorization = new[] { new HangfireDashboardAuthorizationFilter() } Authorization = new[] { new HangfireDashboardAuthorizationFilter() }
}); });
#endif
RecurringJob.AddOrUpdate<AgendaSyncService>( RecurringJob.AddOrUpdate<AgendaSyncService>(
"agenda-sync-daily", "agenda-sync-daily",