using HrynCo.NotificationService.DAL.Abstract.PushChannels; using HrynCo.NotificationService.DAL.Abstract.Repositories; using HrynCo.NotificationService.Services.PushChannels; using HrynCo.NotificationService.Web.Controllers.Admin.ViewModels; using MediatR; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using HrynCo.NotificationService.Worker.Services.Push; namespace HrynCo.NotificationService.Web.Controllers.Admin; [Route("admin/push-channels")] public sealed class AdminPushChannelsController( IPushChannelRepository channels, INotificationServiceRepository services, IMediator mediator, PushChannelTestService tester) : Controller { [HttpPost("test-authorization")] [ValidateAntiForgeryToken] [RequestSizeLimit(131072)] public async Task TestAuthorization([FromBody] PushChannelTestSettings? settings, CancellationToken ct) { if (settings is null || !ModelState.IsValid) return BadRequest(new { success = false, message = "Invalid test settings." }); var result = await tester.TestAsync(settings, false, ct); return Json(new { success = result.Error is null, message = result.Error ?? "OAuth access token obtained. FCM send permission and device delivery have not been verified. No push was sent." }); } [HttpPost("test-send")] [ValidateAntiForgeryToken] [RequestSizeLimit(131072)] public async Task TestSend([FromBody] PushChannelTestSettings? settings, CancellationToken ct) { if (settings is null || !ModelState.IsValid) return BadRequest(new { success = false, message = "Invalid test settings." }); var result = await tester.TestAsync(settings, true, ct); return Json(new { success = result.Error is null, message = result.Error ?? "Firebase accepted the test message. This does not confirm delivery or display on the device." }); } [HttpGet("")] public async Task Index(CancellationToken ct) => View(await channels.GetAllAsync(ct)); [HttpGet("create")] public Task Create(CancellationToken ct) => EditorViewAsync(new PushChannelEditViewModel(), ct); [HttpGet("{id:guid}")] public async Task Edit(Guid id, CancellationToken ct) { var channel = await channels.GetByIdAsync(id, ct); if (channel is null) return NotFound(); return await EditorViewAsync(new PushChannelEditViewModel { Id = channel.Id, ServiceId = channel.ServiceId, FirebaseProjectId = channel.FirebaseProjectId, IsActive = channel.IsActive }, ct); } [HttpPost("save")] [ValidateAntiForgeryToken] public async Task Save(PushChannelEditViewModel model, CancellationToken ct) { if (!ModelState.IsValid) return await EditorViewAsync(model, ct); var result = await mediator.Send(new SavePushChannelCommand { Id = model.Id, ServiceId = model.ServiceId!.Value, FirebaseProjectId = model.FirebaseProjectId, CredentialsJson = model.CredentialsJson, IsActive = model.IsActive }, ct); if (result.IsSuccess) return RedirectToAction(nameof(Index)); ModelState.AddModelError(string.Empty, result.Error?.Message ?? "Could not save the push channel."); return await EditorViewAsync(model, ct); } private async Task EditorViewAsync(PushChannelEditViewModel model, CancellationToken ct) { model.CredentialsJson = null; // Razor tag helpers prefer ModelState over the model, so clear the attempted secret too. ModelState.Remove(nameof(model.CredentialsJson)); model.Services = (await services.GetAllAsync(ct)) .Select(s => new SelectListItem(s.Name, s.Id.ToString())).ToList(); return View("Edit", model); } }