using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Application.App.Subscriptions.Commands;
using TakeoutSaaS.Application.App.Subscriptions.Dto;
using TakeoutSaaS.Application.App.Subscriptions.Queries;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
///
/// 订阅管理。
///
[ApiVersion("1.0")]
[Authorize]
[Route("api/admin/v{version:apiVersion}/subscriptions")]
public sealed class SubscriptionsController(IMediator mediator) : BaseApiController
{
///
/// 分页查询订阅列表(支持按状态、套餐、到期时间筛选)。
///
/// 查询条件。
/// 取消标记。
/// 订阅分页结果。
[HttpGet]
[PermissionAuthorize("subscription:read")]
[ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
public async Task>> List(
[FromQuery] GetSubscriptionListQuery query,
CancellationToken cancellationToken)
{
// 1. 查询订阅分页
var result = await mediator.Send(query, cancellationToken);
// 2. 返回结果
return ApiResponse>.Ok(result);
}
///
/// 查看订阅详情(含套餐信息、配额使用、变更历史)。
///
/// 订阅 ID。
/// 取消标记。
/// 订阅详情或未找到。
[HttpGet("{subscriptionId:long}")]
[PermissionAuthorize("subscription:read")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
public async Task> Detail(
long subscriptionId,
CancellationToken cancellationToken)
{
// 1. 查询订阅详情
var result = await mediator.Send(new GetSubscriptionDetailQuery { SubscriptionId = subscriptionId }, cancellationToken);
// 2. 返回查询结果或 404
return result is null
? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在")
: ApiResponse.Ok(result);
}
///
/// 更新订阅基础信息(备注、自动续费等)。
///
/// 订阅 ID。
/// 更新命令。
/// 取消标记。
/// 更新后的订阅详情或未找到。
[HttpPut("{subscriptionId:long}")]
[PermissionAuthorize("subscription:update")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
public async Task> Update(
long subscriptionId,
[FromBody, Required] UpdateSubscriptionCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定路由 ID
command = command with { SubscriptionId = subscriptionId };
// 2. 执行更新
var result = await mediator.Send(command, cancellationToken);
// 3. 返回更新结果或 404
return result is null
? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在")
: ApiResponse.Ok(result);
}
///
/// 延期订阅(增加订阅时长)。
///
/// 订阅 ID。
/// 延期命令。
/// 取消标记。
/// 延期后的订阅详情或未找到。
[HttpPost("{subscriptionId:long}/extend")]
[PermissionAuthorize("subscription:extend")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
public async Task> Extend(
long subscriptionId,
[FromBody, Required] ExtendSubscriptionCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定路由 ID
command = command with { SubscriptionId = subscriptionId };
// 2. 执行延期
var result = await mediator.Send(command, cancellationToken);
// 3. 返回延期结果或 404
return result is null
? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在")
: ApiResponse.Ok(result);
}
///
/// 变更套餐(支持立即生效或下周期生效)。
///
/// 订阅 ID。
/// 变更套餐命令。
/// 取消标记。
/// 变更后的订阅详情或未找到。
[HttpPost("{subscriptionId:long}/change-plan")]
[PermissionAuthorize("subscription:change-plan")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
public async Task> ChangePlan(
long subscriptionId,
[FromBody, Required] ChangeSubscriptionPlanCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定路由 ID
command = command with { SubscriptionId = subscriptionId };
// 2. 执行套餐变更
var result = await mediator.Send(command, cancellationToken);
// 3. 返回变更结果或 404
return result is null
? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在")
: ApiResponse.Ok(result);
}
///
/// 变更订阅状态。
///
/// 订阅 ID。
/// 状态变更命令。
/// 取消标记。
/// 变更后的订阅详情或未找到。
[HttpPost("{subscriptionId:long}/status")]
[PermissionAuthorize("subscription:update-status")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
public async Task> UpdateStatus(
long subscriptionId,
[FromBody, Required] UpdateSubscriptionStatusCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定路由 ID
command = command with { SubscriptionId = subscriptionId };
// 2. 执行状态变更
var result = await mediator.Send(command, cancellationToken);
// 3. 返回变更结果或 404
return result is null
? ApiResponse.Error(StatusCodes.Status404NotFound, "订阅不存在")
: ApiResponse.Ok(result);
}
///
/// 批量延期订阅。
///
/// 批量延期命令。
/// 取消标记。
/// 批量延期结果。
[HttpPost("batch-extend")]
[PermissionAuthorize("subscription:batch-extend")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
public async Task> BatchExtend(
[FromBody, Required] BatchExtendSubscriptionsCommand command,
CancellationToken cancellationToken)
{
var result = await mediator.Send(command, cancellationToken);
return ApiResponse.Ok(result);
}
///
/// 批量发送续费提醒。
///
/// 批量发送提醒命令。
/// 取消标记。
/// 批量发送提醒结果。
[HttpPost("batch-remind")]
[PermissionAuthorize("subscription:batch-remind")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
public async Task> BatchRemind(
[FromBody, Required] BatchSendReminderCommand command,
CancellationToken cancellationToken)
{
var result = await mediator.Send(command, cancellationToken);
return ApiResponse.Ok(result);
}
}