add church profile.
ci-cd-vm / ci-cd (push) Successful in 2m31s

This commit is contained in:
Chris Chen
2026-06-24 08:21:31 -07:00
parent 99585a1c0e
commit e88ea7917f
29 changed files with 1240 additions and 72 deletions
@@ -2,7 +2,6 @@ using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using ROLAC.API.DTOs.Notifications;
using ROLAC.API.Services.Notifications;
@@ -22,14 +21,14 @@ public sealed class LineWebhookController : ControllerBase
private readonly ILineNotificationService _line;
private readonly IMessageChannel _channel;
private readonly LineOptions _options;
private readonly INotificationSettingsService _settings;
public LineWebhookController(
ILineNotificationService line, IMessageChannel channel, IOptions<LineOptions> options)
ILineNotificationService line, IMessageChannel channel, INotificationSettingsService settings)
{
_line = line;
_channel = channel;
_options = options.Value;
_settings = settings;
}
[HttpPost("webhook")]
@@ -40,7 +39,7 @@ public sealed class LineWebhookController : ControllerBase
var rawBody = await reader.ReadToEndAsync(ct);
var signature = Request.Headers["X-Line-Signature"].FirstOrDefault();
if (!LineSignature.IsValid(_options.ChannelSecret, Encoding.UTF8.GetBytes(rawBody), signature))
if (!LineSignature.IsValid(_settings.GetLine().ChannelSecret, Encoding.UTF8.GetBytes(rawBody), signature))
return BadRequest();
var payload = JsonSerializer.Deserialize<LineWebhookPayload>(rawBody, JsonOpts);
@@ -0,0 +1,105 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ROLAC.API.Authorization;
using ROLAC.API.DTOs.Settings;
using ROLAC.API.Services;
using ROLAC.API.Services.Logging;
using ROLAC.API.Services.Notifications;
namespace ROLAC.API.Controllers;
/// <summary>
/// Site-wide and notification (SMTP/Line) settings, surfaced by the Church Profile → Site /
/// Notification tabs. Gated by the <c>Settings</c> permission module (super_admin bypasses).
/// </summary>
[ApiController]
[Route("api/settings")]
[Authorize]
public class SettingsController : ControllerBase
{
private readonly ISettingsService _settings;
private readonly IEmailService _email;
private readonly ILineNotificationService _line;
private readonly CurrentUserAccessor _currentUser;
public SettingsController(
ISettingsService settings,
IEmailService email,
ILineNotificationService line,
CurrentUserAccessor currentUser)
{
_settings = settings;
_email = email;
_line = line;
_currentUser = currentUser;
}
// ── Site settings ────────────────────────────────────────────────────────
[HttpGet("site")]
[HasPermission(Modules.Settings, PermissionActions.Read)]
public async Task<IActionResult> GetSite() => Ok(await _settings.GetSiteAsync());
[HttpPut("site")]
[HasPermission(Modules.Settings, PermissionActions.Write)]
public async Task<IActionResult> UpdateSite([FromBody] UpdateSiteSettingRequest request)
{
await _settings.UpdateSiteAsync(request);
return NoContent();
}
// ── Notification settings ──────────────────────────────────────────────────
[HttpGet("notification")]
[HasPermission(Modules.Settings, PermissionActions.Read)]
public async Task<IActionResult> GetNotification()
{
var dto = await _settings.GetNotificationAsync();
dto.WebhookUrl = $"{Request.Scheme}://{Request.Host}/api/line/webhook";
return Ok(dto);
}
[HttpPut("notification")]
[HasPermission(Modules.Settings, PermissionActions.Write)]
public async Task<IActionResult> UpdateNotification([FromBody] UpdateNotificationSettingRequest request)
{
await _settings.UpdateNotificationAsync(request);
return NoContent();
}
[HttpPost("notification/test-email")]
[HasPermission(Modules.Settings, PermissionActions.Write)]
public async Task<IActionResult> TestEmail([FromBody] TestEmailRequest request, CancellationToken ct)
{
var to = string.IsNullOrWhiteSpace(request.ToAddress) ? _currentUser.Email : request.ToAddress;
if (string.IsNullOrWhiteSpace(to))
return BadRequest(new { message = "No recipient — provide an address or set an email on your account." });
var result = await _email.SendAsync(new EmailMessage(
MemberIds: Array.Empty<int>(),
Addresses: new[] { to },
Subject: "ROLAC test email / 測試郵件",
HtmlBody: "<p>This is a test email from ROLAC notification settings.</p>"
+ "<p>這是來自 ROLAC 通知設定的測試郵件。</p>",
SentByUserId: _currentUser.UserIdOrSystem), ct);
return Ok(result);
}
[HttpPost("notification/test-line")]
[HasPermission(Modules.Settings, PermissionActions.Write)]
public async Task<IActionResult> TestLine([FromBody] TestLineRequest request, CancellationToken ct)
{
if (request.MemberId is null && request.GroupId is null)
return BadRequest(new { message = "Choose a bound member or group to receive the test." });
var result = await _line.SendLineAsync(
body: "ROLAC 測試訊息 / This is a test Line message from ROLAC.",
memberIds: request.MemberId is { } m ? new[] { m } : Array.Empty<int>(),
groupIds: request.GroupId is { } g ? new[] { g } : Array.Empty<int>(),
sentByUserId: _currentUser.UserIdOrSystem,
ct);
return Ok(result);
}
}