963d02a265
Iran-safe push for the Koja Android app (Cafe Bazaar / Myket / direct APK):
Backend
- PushDevice entity + EF migration; idempotent device register/unregister.
- IPushSender / PusheNotificationSender (Pushe REST) — SendToTopic for
marketing (city-{slug}) and saved-café (cafe-{slug}) pushes, SendToTokens
for targeted order/reservation updates.
- Public register/unregister endpoints + authorized topic broadcast.
App
- capacitor.config.ts (native WebView loads the live PWA via server.url).
- push.ts Pushe glue: topic helpers + backend device registration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
2.4 KiB
C#
68 lines
2.4 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using Meezi.API.Models.Public;
|
|
using Meezi.API.Services;
|
|
using Meezi.Core.Interfaces;
|
|
using Meezi.Shared;
|
|
|
|
namespace Meezi.API.Controllers;
|
|
|
|
/// <summary>
|
|
/// Push-notification endpoints for the native (Capacitor + Pushe) app.
|
|
///
|
|
/// POST /api/public/push/register — anonymous device registration
|
|
/// POST /api/public/push/unregister — anonymous device removal
|
|
/// POST /api/push/broadcast — authorized topic broadcast (marketing /
|
|
/// saved-café alerts)
|
|
/// </summary>
|
|
[ApiController]
|
|
public class PushController : ControllerBase
|
|
{
|
|
private readonly IPushDeviceService _devices;
|
|
private readonly IPushSender _sender;
|
|
|
|
public PushController(IPushDeviceService devices, IPushSender sender)
|
|
{
|
|
_devices = devices;
|
|
_sender = sender;
|
|
}
|
|
|
|
[HttpPost("api/public/push/register")]
|
|
[AllowAnonymous]
|
|
[EnableRateLimiting("public-read")]
|
|
public async Task<IActionResult> Register(
|
|
[FromBody] RegisterPushDeviceRequest request, CancellationToken ct)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.Token))
|
|
return BadRequest(new ApiResponse<object>(false, null,
|
|
new ApiError("INVALID_TOKEN", "Device token is required.")));
|
|
|
|
await _devices.RegisterAsync(request, ct);
|
|
return Ok(new ApiResponse<object>(true, new { registered = true }));
|
|
}
|
|
|
|
[HttpPost("api/public/push/unregister")]
|
|
[AllowAnonymous]
|
|
[EnableRateLimiting("public-read")]
|
|
public async Task<IActionResult> Unregister(
|
|
[FromBody] UnregisterPushDeviceRequest request, CancellationToken ct)
|
|
{
|
|
await _devices.UnregisterAsync(request.Token, ct);
|
|
return Ok(new ApiResponse<object>(true, new { unregistered = true }));
|
|
}
|
|
|
|
[HttpPost("api/push/broadcast")]
|
|
[Authorize]
|
|
public async Task<IActionResult> Broadcast(
|
|
[FromBody] BroadcastPushRequest request, CancellationToken ct)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.Topic))
|
|
return BadRequest(new ApiResponse<object>(false, null,
|
|
new ApiError("INVALID_TOPIC", "Topic is required.")));
|
|
|
|
await _sender.SendToTopicAsync(request.Topic, request.Title, request.Body, request.DeepLink, ct);
|
|
return Ok(new ApiResponse<object>(true, new { sent = true }));
|
|
}
|
|
}
|