refactor(tenant-admin): split capability services
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Tiku.Api.Controllers;
|
||||
using Tiku.Api.OpenApi;
|
||||
using Tiku.Api.Security;
|
||||
|
||||
@@ -21,6 +22,7 @@ internal static class ApiPresentationExtensions
|
||||
options.AddOperationTransformer<AuthenticationOperationTagsTransformer>();
|
||||
});
|
||||
services.AddProblemDetails();
|
||||
services.AddScoped<TenantAdminActorResolver>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
21
Tiku.Api/Controllers/TenantAdminActorResolver.cs
Normal file
21
Tiku.Api/Controllers/TenantAdminActorResolver.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
public sealed class TenantAdminActorResolver(
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant)
|
||||
{
|
||||
internal TenantAdminActor Resolve()
|
||||
{
|
||||
try
|
||||
{
|
||||
return TenantAdminActor.FromResolvedIdentity(currentTenant.TenantId, currentUser.UserId);
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
throw new TenantAdminDirectException(exception.Message, "tenant_admin_access_denied");
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Tiku.Api/Controllers/TenantAdminDashboardController.cs
Normal file
29
Tiku.Api/Controllers/TenantAdminDashboardController.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantAdminDashboardController(
|
||||
ITenantAdminDashboardService dashboardService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("overview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantDashboardView)]
|
||||
[EndpointSummary("查询租户后台运营概览")]
|
||||
[ProducesResponseType<TenantAdminOverviewItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminOverviewItem>> GetOverview(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await dashboardService.GetOverviewAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,568 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantAdminDirectController(
|
||||
ITenantAdminDirectService tenantAdminService,
|
||||
IAuthAdministrationService authAdministrationService,
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant) : ControllerBase
|
||||
{
|
||||
[HttpGet("overview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantDashboardView)]
|
||||
[EndpointSummary("查询租户后台运营概览")]
|
||||
[ProducesResponseType<TenantAdminOverviewItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminOverviewItem>> GetOverview(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetOverviewAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("classes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户班级")]
|
||||
[ProducesResponseType<TenantAdminClassList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminClassList>> GetClasses(
|
||||
[FromQuery] TenantAdminClassQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetClassesAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("classes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新租户班级")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassItem>>> UpsertClass(
|
||||
UpsertTenantAdminClassDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertClassAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("classes/disable")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("停用租户班级")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassItem>>> DisableClass(
|
||||
[FromBody] Guid classId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.DisableClassAsync(ResolveActor(), classId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("classes/members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询班级成员")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminClassMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminClassMemberItem>>> GetClassMembers(
|
||||
[FromQuery] TenantAdminClassMemberQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetClassMembersAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("classes/members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新班级成员")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassMemberItem>>> UpsertClassMember(
|
||||
UpsertTenantAdminClassMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertClassMemberAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("classes/members/remove")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("移除班级成员")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassMemberItem>>> RemoveClassMember(
|
||||
RemoveTenantAdminClassMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.RemoveClassMemberAsync(ResolveActor(), request.ClassMemberId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("students")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户学生")]
|
||||
[ProducesResponseType<TenantAdminStudentList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentList>> GetStudents(
|
||||
[FromQuery] TenantAdminStudentQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetStudentsAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("students")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新租户学生档案")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentItem>>> UpsertStudent(
|
||||
UpsertTenantAdminStudentDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertStudentAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("更新租户学生状态")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentStatusItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentStatusItem>>> UpdateStudentStatus(
|
||||
UpdateTenantAdminStudentStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpdateStudentStatusAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/import/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("预览学生批量导入")]
|
||||
[ProducesResponseType<TenantAdminStudentImportPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentImportPreview>> PreviewStudentImport(
|
||||
TenantAdminStudentImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.PreviewStudentImportAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/import")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("提交学生批量导入")]
|
||||
[ProducesResponseType<TenantAdminStudentImportResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentImportResult>> ImportStudents(
|
||||
TenantAdminStudentImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.ImportStudentsAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/bulk-assign-class")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量分班")]
|
||||
[ProducesResponseType<TenantAdminBulkOperationResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminBulkOperationResult>> BulkAssignClass(
|
||||
TenantAdminBulkAssignClassDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.BulkAssignClassAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/bulk-status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量调整学生状态")]
|
||||
[ProducesResponseType<TenantAdminBulkOperationResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminBulkOperationResult>> BulkStudentStatus(
|
||||
TenantAdminBulkStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.BulkUpdateStudentStatusAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("students/supervision/rules")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学习督导规则")]
|
||||
[ProducesResponseType<CatalogList<TenantSupervisionRuleItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantSupervisionRuleItem>>> SupervisionRules(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetSupervisionRulesAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("students/supervision/rules")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学习督导规则")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantSupervisionRuleItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantSupervisionRuleItem>>> UpsertSupervisionRule(
|
||||
UpsertTenantSupervisionRuleDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertSupervisionRuleAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("students/supervision/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("预览学习风险学生")]
|
||||
[ProducesResponseType<TenantSupervisionPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSupervisionPreview>> PreviewSupervision(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.PreviewSupervisionAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/supervision/generate")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量生成学习督导跟进任务")]
|
||||
[ProducesResponseType<TenantSupervisionGenerateResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSupervisionGenerateResult>> GenerateSupervision(
|
||||
TenantSupervisionGenerateDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GenerateSupervisionFollowupsAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-followups/report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生跟进统计报表")]
|
||||
[ProducesResponseType<TenantFollowupReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantFollowupReport>> FollowupReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetFollowupReportAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("feedbacks/report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询反馈处理统计报表")]
|
||||
[ProducesResponseType<TenantFeedbackReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantFeedbackReport>> FeedbackReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetFeedbackReportAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("points/risk-report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询积分风险统计报表")]
|
||||
[ProducesResponseType<TenantPointRiskReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointRiskReport>> PointRiskReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetPointRiskReportAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-notes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生备注")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminStudentNoteItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminStudentNoteItem>>> GetStudentNotes(
|
||||
[FromQuery] TenantAdminStudentActivityQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetStudentNotesAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("student-notes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学生备注")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentNoteItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentNoteItem>>> UpsertStudentNote(
|
||||
UpsertTenantAdminStudentNoteDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertStudentNoteAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-followups")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生跟进")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminStudentFollowupItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminStudentFollowupItem>>> GetStudentFollowups(
|
||||
[FromQuery] TenantAdminStudentActivityQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(
|
||||
await tenantAdminService.GetStudentFollowupsAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("student-followups")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学生跟进")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentFollowupItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentFollowupItem>>> UpsertStudentFollowup(
|
||||
UpsertTenantAdminStudentFollowupDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertStudentFollowupAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("查询租户成员")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminMemberItem>>> GetMembers(
|
||||
[FromQuery] TenantAdminMemberQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetMembersAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("新增或更新租户成员")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminMemberItem>>> UpsertMember(
|
||||
UpsertTenantAdminMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertMemberAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("members/disable")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("停用租户成员并撤销会话")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminMemberItem>>> DisableMember(
|
||||
DisableTenantAdminMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.DisableMemberAsync(ResolveActor(), request.MembershipId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("members/{userId:guid}/password-reset")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("为租户成员设置一次性临时密码")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<IActionResult> ResetMemberPassword(
|
||||
Guid userId,
|
||||
AdministrativePasswordResetDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var actor = ResolveActor();
|
||||
await authAdministrationService.ResetPasswordAsync(
|
||||
new AdministrativePasswordResetRequest(
|
||||
actor.UserId,
|
||||
userId,
|
||||
actor.TenantId,
|
||||
request.TemporaryPassword,
|
||||
request.Reason),
|
||||
cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("audit-logs")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("查询租户审计日志")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminAuditLogItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminAuditLogItem>>> GetAuditLogs(
|
||||
[FromQuery] TenantAdminAuditLogQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetAuditLogsAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("branding")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("更新租户品牌信息")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantBrandingItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantBrandingItem>>> UpsertBranding(
|
||||
UpsertTenantBrandingDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertBrandingAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("settings")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("更新租户公开设置与功能开关")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantSettingsItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantSettingsItem>>> UpsertSettings(
|
||||
UpsertTenantSettingsDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertSettingsAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("theme-templates")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("查询可用租户主题模板")]
|
||||
[ProducesResponseType<CatalogList<TenantThemeTemplateItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantThemeTemplateItem>>> GetThemeTemplates(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetThemeTemplatesAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("theme")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("查询租户当前主题与草稿")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantThemeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantThemeItem>>> GetTheme(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetThemeAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("theme/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("生成租户主题草稿")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantThemeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantThemeItem>>> PreviewTheme(
|
||||
PreviewTenantThemeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.PreviewThemeAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("theme/publish")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("发布租户主题")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantThemeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantThemeItem>>> PublishTheme(
|
||||
PublishTenantThemeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.PublishThemeAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("domains")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("查询租户域名")]
|
||||
[ProducesResponseType<CatalogList<TenantDomainItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantDomainItem>>> GetDomains(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetDomainsAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("domains")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("添加租户域名")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantDomainItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantDomainItem>>> CreateDomain(
|
||||
CreateTenantDomainDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.CreateDomainAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("auth-providers")]
|
||||
[Authorize(Policy = BackendPermissions.TenantProviderManage)]
|
||||
[EndpointSummary("查询租户登录 Provider 公开配置")]
|
||||
[ProducesResponseType<CatalogList<TenantIdentityProviderItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantIdentityProviderItem>>> GetAuthProviders(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetAuthProvidersAsync(ResolveActor(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("auth-providers")]
|
||||
[Authorize(Policy = BackendPermissions.TenantProviderManage)]
|
||||
[EndpointSummary("新增或更新租户登录 Provider")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantIdentityProviderItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantIdentityProviderItem>>> UpsertAuthProvider(
|
||||
UpsertTenantIdentityProviderDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertAuthProviderAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("badges")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户勋章")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminBadgeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminBadgeItem>>> GetBadges(
|
||||
[FromQuery] TenantAdminBadgeQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetBadgesAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("badges")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新租户勋章")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminBadgeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminBadgeItem>>> UpsertBadge(
|
||||
UpsertTenantAdminBadgeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertBadgeAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("badge-grants")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询勋章发放记录")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminBadgeGrantItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminBadgeGrantItem>>> GetBadgeGrants(
|
||||
[FromQuery] TenantAdminBadgeGrantQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetBadgeGrantsAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("badge-grants")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("向租户成员发放勋章")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminBadgeGrantItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminBadgeGrantItem>>> GrantBadge(
|
||||
GrantTenantAdminBadgeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GrantBadgeAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("notifications")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询用户站内通知")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminNotificationItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminNotificationItem>>> GetNotifications(
|
||||
[FromQuery] TenantAdminNotificationQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetNotificationsAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("notifications")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新用户站内通知")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminNotificationItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminNotificationItem>>> UpsertNotification(
|
||||
UpsertTenantAdminNotificationDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpsertNotificationAsync(ResolveActor(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("feedbacks")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询用户反馈")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminFeedbackItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminFeedbackItem>>> GetFeedbacks(
|
||||
[FromQuery] TenantAdminFeedbackQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.GetFeedbacksAsync(ResolveActor(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("feedbacks/status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("处理用户反馈")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminFeedbackItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminFeedbackItem>>> UpdateFeedback(
|
||||
UpdateTenantAdminFeedbackDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await tenantAdminService.UpdateFeedbackAsync(ResolveActor(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
private TenantAdminActor ResolveActor()
|
||||
{
|
||||
try
|
||||
{
|
||||
return TenantAdminActor.FromResolvedIdentity(currentTenant.TenantId, currentUser.UserId);
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
throw new TenantAdminDirectException(exception.Message, "tenant_admin_access_denied");
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Tiku.Api/Controllers/TenantClassController.cs
Normal file
88
Tiku.Api/Controllers/TenantClassController.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantClassController(
|
||||
ITenantClassService classService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("classes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户班级")]
|
||||
[ProducesResponseType<TenantAdminClassList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminClassList>> GetClasses(
|
||||
[FromQuery] TenantAdminClassQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await classService.GetClassesAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("classes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新租户班级")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassItem>>> UpsertClass(
|
||||
UpsertTenantAdminClassDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await classService.UpsertClassAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("classes/disable")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("停用租户班级")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassItem>>> DisableClass(
|
||||
[FromBody] Guid classId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await classService.DisableClassAsync(actorResolver.Resolve(), classId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("classes/members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询班级成员")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminClassMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminClassMemberItem>>> GetClassMembers(
|
||||
[FromQuery] TenantAdminClassMemberQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await classService.GetClassMembersAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("classes/members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新班级成员")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassMemberItem>>> UpsertClassMember(
|
||||
UpsertTenantAdminClassMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await classService.UpsertClassMemberAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("classes/members/remove")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("移除班级成员")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminClassMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminClassMemberItem>>> RemoveClassMember(
|
||||
RemoveTenantAdminClassMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await classService.RemoveClassMemberAsync(actorResolver.Resolve(), request.ClassMemberId,
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
62
Tiku.Api/Controllers/TenantDomainProviderController.cs
Normal file
62
Tiku.Api/Controllers/TenantDomainProviderController.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantDomainProviderController(
|
||||
ITenantDomainProviderService domainProviderService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("domains")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("查询租户域名")]
|
||||
[ProducesResponseType<CatalogList<TenantDomainItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantDomainItem>>> GetDomains(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await domainProviderService.GetDomainsAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("domains")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("添加租户域名")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantDomainItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantDomainItem>>> CreateDomain(
|
||||
CreateTenantDomainDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await domainProviderService.CreateDomainAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("auth-providers")]
|
||||
[Authorize(Policy = BackendPermissions.TenantProviderManage)]
|
||||
[EndpointSummary("查询租户登录 Provider 公开配置")]
|
||||
[ProducesResponseType<CatalogList<TenantIdentityProviderItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantIdentityProviderItem>>> GetAuthProviders(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await domainProviderService.GetAuthProvidersAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("auth-providers")]
|
||||
[Authorize(Policy = BackendPermissions.TenantProviderManage)]
|
||||
[EndpointSummary("新增或更新租户登录 Provider")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantIdentityProviderItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantIdentityProviderItem>>> UpsertAuthProvider(
|
||||
UpsertTenantIdentityProviderDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await domainProviderService.UpsertAuthProviderAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
109
Tiku.Api/Controllers/TenantEngagementController.cs
Normal file
109
Tiku.Api/Controllers/TenantEngagementController.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantEngagementController(
|
||||
ITenantEngagementService engagementService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("badges")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户勋章")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminBadgeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminBadgeItem>>> GetBadges(
|
||||
[FromQuery] TenantAdminBadgeQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.GetBadgesAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("badges")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新租户勋章")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminBadgeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminBadgeItem>>> UpsertBadge(
|
||||
UpsertTenantAdminBadgeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.UpsertBadgeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("badge-grants")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询勋章发放记录")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminBadgeGrantItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminBadgeGrantItem>>> GetBadgeGrants(
|
||||
[FromQuery] TenantAdminBadgeGrantQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.GetBadgeGrantsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("badge-grants")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("向租户成员发放勋章")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminBadgeGrantItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminBadgeGrantItem>>> GrantBadge(
|
||||
GrantTenantAdminBadgeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.GrantBadgeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("notifications")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询用户站内通知")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminNotificationItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminNotificationItem>>> GetNotifications(
|
||||
[FromQuery] TenantAdminNotificationQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.GetNotificationsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("notifications")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新用户站内通知")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminNotificationItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminNotificationItem>>> UpsertNotification(
|
||||
UpsertTenantAdminNotificationDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.UpsertNotificationAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("feedbacks")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询用户反馈")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminFeedbackItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminFeedbackItem>>> GetFeedbacks(
|
||||
[FromQuery] TenantAdminFeedbackQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.GetFeedbacksAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("feedbacks/status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("处理用户反馈")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminFeedbackItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminFeedbackItem>>> UpdateFeedback(
|
||||
UpdateTenantAdminFeedbackDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await engagementService.UpdateFeedbackAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
74
Tiku.Api/Controllers/TenantMemberAccessController.cs
Normal file
74
Tiku.Api/Controllers/TenantMemberAccessController.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantMemberAccessController(
|
||||
ITenantMemberAccessService memberAccessService,
|
||||
IAuthAdministrationService authAdministrationService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("查询租户成员")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminMemberItem>>> GetMembers(
|
||||
[FromQuery] TenantAdminMemberQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await memberAccessService.GetMembersAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("members")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("新增或更新租户成员")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminMemberItem>>> UpsertMember(
|
||||
UpsertTenantAdminMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await memberAccessService.UpsertMemberAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("members/disable")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("停用租户成员并撤销会话")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminMemberItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminMemberItem>>> DisableMember(
|
||||
DisableTenantAdminMemberDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await memberAccessService.DisableMemberAsync(actorResolver.Resolve(), request.MembershipId, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("members/{userId:guid}/password-reset")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("为租户成员设置一次性临时密码")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<IActionResult> ResetMemberPassword(
|
||||
Guid userId,
|
||||
AdministrativePasswordResetDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var actor = actorResolver.Resolve();
|
||||
await authAdministrationService.ResetPasswordAsync(
|
||||
new AdministrativePasswordResetRequest(
|
||||
actor.UserId,
|
||||
userId,
|
||||
actor.TenantId,
|
||||
request.TemporaryPassword,
|
||||
request.Reason),
|
||||
cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
95
Tiku.Api/Controllers/TenantSiteSettingsController.cs
Normal file
95
Tiku.Api/Controllers/TenantSiteSettingsController.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantSiteSettingsController(
|
||||
ITenantSiteSettingsService siteSettingsService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("audit-logs")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStaffManage)]
|
||||
[EndpointSummary("查询租户审计日志")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminAuditLogItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminAuditLogItem>>> GetAuditLogs(
|
||||
[FromQuery] TenantAdminAuditLogQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.GetAuditLogsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("branding")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("更新租户品牌信息")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantBrandingItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantBrandingItem>>> UpsertBranding(
|
||||
UpsertTenantBrandingDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.UpsertBrandingAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("settings")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("更新租户公开设置与功能开关")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantSettingsItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantSettingsItem>>> UpsertSettings(
|
||||
UpsertTenantSettingsDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.UpsertSettingsAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("theme-templates")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("查询可用租户主题模板")]
|
||||
[ProducesResponseType<CatalogList<TenantThemeTemplateItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantThemeTemplateItem>>> GetThemeTemplates(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.GetThemeTemplatesAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("theme")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("查询租户当前主题与草稿")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantThemeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantThemeItem>>> GetTheme(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.GetThemeAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("theme/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("生成租户主题草稿")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantThemeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantThemeItem>>> PreviewTheme(
|
||||
PreviewTenantThemeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.PreviewThemeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("theme/publish")]
|
||||
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
|
||||
[EndpointSummary("发布租户主题")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantThemeItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantThemeItem>>> PublishTheme(
|
||||
PublishTenantThemeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await siteSettingsService.PublishThemeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
101
Tiku.Api/Controllers/TenantStudentController.cs
Normal file
101
Tiku.Api/Controllers/TenantStudentController.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantStudentController(
|
||||
ITenantStudentService studentService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("students")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询租户学生")]
|
||||
[ProducesResponseType<TenantAdminStudentList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentList>> GetStudents(
|
||||
[FromQuery] TenantAdminStudentQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.GetStudentsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("students")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新租户学生档案")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentItem>>> UpsertStudent(
|
||||
UpsertTenantAdminStudentDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.UpsertStudentAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("更新租户学生状态")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentStatusItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentStatusItem>>> UpdateStudentStatus(
|
||||
UpdateTenantAdminStudentStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.UpdateStudentStatusAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/import/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("预览学生批量导入")]
|
||||
[ProducesResponseType<TenantAdminStudentImportPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentImportPreview>> PreviewStudentImport(
|
||||
TenantAdminStudentImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.PreviewStudentImportAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/import")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("提交学生批量导入")]
|
||||
[ProducesResponseType<TenantAdminStudentImportResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminStudentImportResult>> ImportStudents(
|
||||
TenantAdminStudentImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.ImportStudentsAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/bulk-assign-class")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量分班")]
|
||||
[ProducesResponseType<TenantAdminBulkOperationResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminBulkOperationResult>> BulkAssignClass(
|
||||
TenantAdminBulkAssignClassDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.BulkAssignClassAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/bulk-status")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量调整学生状态")]
|
||||
[ProducesResponseType<TenantAdminBulkOperationResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdminBulkOperationResult>> BulkStudentStatus(
|
||||
TenantAdminBulkStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentService.BulkUpdateStudentStatusAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
67
Tiku.Api/Controllers/TenantStudentEngagementController.cs
Normal file
67
Tiku.Api/Controllers/TenantStudentEngagementController.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantStudentEngagementController(
|
||||
ITenantStudentEngagementService studentEngagementService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("student-notes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生备注")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminStudentNoteItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminStudentNoteItem>>> GetStudentNotes(
|
||||
[FromQuery] TenantAdminStudentActivityQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentEngagementService.GetStudentNotesAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("student-notes")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学生备注")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentNoteItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentNoteItem>>> UpsertStudentNote(
|
||||
UpsertTenantAdminStudentNoteDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentEngagementService.UpsertStudentNoteAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-followups")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生跟进")]
|
||||
[ProducesResponseType<CatalogList<TenantAdminStudentFollowupItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantAdminStudentFollowupItem>>> GetStudentFollowups(
|
||||
[FromQuery] TenantAdminStudentActivityQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(
|
||||
await studentEngagementService.GetStudentFollowupsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("student-followups")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学生跟进")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantAdminStudentFollowupItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantAdminStudentFollowupItem>>> UpsertStudentFollowup(
|
||||
UpsertTenantAdminStudentFollowupDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await studentEngagementService.UpsertStudentFollowupAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
90
Tiku.Api/Controllers/TenantSupervisionController.cs
Normal file
90
Tiku.Api/Controllers/TenantSupervisionController.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-运营管理")]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant")]
|
||||
public sealed class TenantSupervisionController(
|
||||
ITenantSupervisionService supervisionService,
|
||||
TenantAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("students/supervision/rules")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学习督导规则")]
|
||||
[ProducesResponseType<CatalogList<TenantSupervisionRuleItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CatalogList<TenantSupervisionRuleItem>>> SupervisionRules(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.GetSupervisionRulesAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("students/supervision/rules")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("新增或更新学习督导规则")]
|
||||
[ProducesResponseType<ContentManagementResult<TenantSupervisionRuleItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ContentManagementResult<TenantSupervisionRuleItem>>> UpsertSupervisionRule(
|
||||
UpsertTenantSupervisionRuleDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.UpsertSupervisionRuleAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("students/supervision/preview")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("预览学习风险学生")]
|
||||
[ProducesResponseType<TenantSupervisionPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSupervisionPreview>> PreviewSupervision(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.PreviewSupervisionAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("students/supervision/generate")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("批量生成学习督导跟进任务")]
|
||||
[ProducesResponseType<TenantSupervisionGenerateResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSupervisionGenerateResult>> GenerateSupervision(
|
||||
TenantSupervisionGenerateDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.GenerateSupervisionFollowupsAsync(actorResolver.Resolve(), request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("student-followups/report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询学生跟进统计报表")]
|
||||
[ProducesResponseType<TenantFollowupReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantFollowupReport>> FollowupReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.GetFollowupReportAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("feedbacks/report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询反馈处理统计报表")]
|
||||
[ProducesResponseType<TenantFeedbackReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantFeedbackReport>> FeedbackReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.GetFeedbackReportAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("points/risk-report")]
|
||||
[Authorize(Policy = BackendPermissions.TenantStudentManage)]
|
||||
[EndpointSummary("查询积分风险统计报表")]
|
||||
[ProducesResponseType<TenantPointRiskReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointRiskReport>> PointRiskReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await supervisionService.GetPointRiskReportAsync(actorResolver.Resolve(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ using Tiku.Application.Content;
|
||||
|
||||
namespace Tiku.Application.TenantAdmin;
|
||||
|
||||
public interface ITenantAdminDirectService
|
||||
public interface ITenantAdminDashboardService
|
||||
{
|
||||
Task<TenantAdminOverviewItem> GetOverviewAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantClassService
|
||||
{
|
||||
Task<TenantAdminClassList> GetClassesAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminClassFilter filter,
|
||||
@@ -38,7 +41,10 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
Guid classMemberId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantStudentService
|
||||
{
|
||||
Task<TenantAdminStudentList> GetStudentsAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminStudentFilter filter,
|
||||
@@ -73,7 +79,10 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
TenantAdminBulkStatusCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantSupervisionService
|
||||
{
|
||||
Task<CatalogList<TenantSupervisionRuleItem>> GetSupervisionRulesAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
@@ -103,7 +112,10 @@ public interface ITenantAdminDirectService
|
||||
Task<TenantPointRiskReport> GetPointRiskReportAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantStudentEngagementService
|
||||
{
|
||||
Task<CatalogList<TenantAdminStudentNoteItem>> GetStudentNotesAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminStudentActivityFilter filter,
|
||||
@@ -123,7 +135,10 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
UpsertTenantAdminStudentFollowupCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantMemberAccessService
|
||||
{
|
||||
Task<CatalogList<TenantAdminMemberItem>> GetMembersAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminMemberFilter filter,
|
||||
@@ -138,7 +153,10 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
Guid membershipId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantSiteSettingsService
|
||||
{
|
||||
Task<CatalogList<TenantAdminAuditLogItem>> GetAuditLogsAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminAuditLogFilter filter,
|
||||
@@ -171,7 +189,10 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
PublishTenantThemeCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantDomainProviderService
|
||||
{
|
||||
Task<CatalogList<TenantDomainItem>> GetDomainsAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
@@ -189,7 +210,10 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
UpsertTenantIdentityProviderCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ITenantEngagementService
|
||||
{
|
||||
Task<CatalogList<TenantAdminBadgeItem>> GetBadgesAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminBadgeFilter filter,
|
||||
@@ -229,4 +253,4 @@ public interface ITenantAdminDirectService
|
||||
TenantAdminActor actor,
|
||||
UpdateTenantAdminFeedbackCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,16 @@ internal static class TenantAdminModule
|
||||
{
|
||||
internal static IServiceCollection AddTenantAdminModule(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ITenantAdminDirectService, TenantAdminDirectService>();
|
||||
services.AddScoped<TenantAdminServiceDependencies>();
|
||||
services.AddScoped<ITenantAdminDashboardService, TenantAdminDashboardService>();
|
||||
services.AddScoped<ITenantClassService, TenantClassService>();
|
||||
services.AddScoped<ITenantStudentService, TenantStudentService>();
|
||||
services.AddScoped<ITenantSupervisionService, TenantSupervisionService>();
|
||||
services.AddScoped<ITenantMemberAccessService, TenantMemberAccessService>();
|
||||
services.AddScoped<ITenantSiteSettingsService, TenantSiteSettingsService>();
|
||||
services.AddScoped<ITenantDomainProviderService, TenantDomainProviderService>();
|
||||
services.AddScoped<ITenantEngagementService, TenantEngagementService>();
|
||||
services.AddScoped<ITenantStudentEngagementService, TenantStudentEngagementService>();
|
||||
services.AddScoped<ITenantOnboardingService, TenantOnboardingService>();
|
||||
services.AddScoped<ITenantLifecycleService, TenantLifecycleService>();
|
||||
services.AddScoped<IBackgroundJobHandler, StatisticsAggregationJobHandler>();
|
||||
|
||||
@@ -10,7 +10,8 @@ using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantClassService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantClassService
|
||||
{
|
||||
public async Task<TenantAdminClassList> GetClassesAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -261,4 +262,4 @@ public sealed partial class TenantAdminDirectService
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantAdminClassMemberItem>(ToClassMemberItem(item, ToUserSummary(user)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ using ReconciliationIssueStatus = Tiku.Domain.Commerce.ReconciliationIssueStatus
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantAdminDashboardService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantAdminDashboardService
|
||||
{
|
||||
public async Task<TenantAdminOverviewItem> GetOverviewAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -98,4 +99,4 @@ public sealed partial class TenantAdminDirectService
|
||||
openReconciliationIssueCount,
|
||||
now);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Catalog;
|
||||
using Tiku.Application.Content;
|
||||
using Tiku.Application.Notifications;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
using Tiku.Domain.Learning;
|
||||
using Tiku.Domain.Operations;
|
||||
using Tiku.Domain.Tenancy;
|
||||
using Tiku.Infrastructure.Security;
|
||||
using Tiku.Infrastructure.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
internal sealed class TenantDomainProviderService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantDomainProviderService
|
||||
{
|
||||
public async Task<CatalogList<TenantDomainItem>> GetDomainsAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
var items = await dbContext.TenantDomains.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId)
|
||||
.OrderByDescending(item => item.IsPrimary)
|
||||
.ThenBy(item => item.CreatedAt)
|
||||
.Select(item => ToDomainItem(item))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new CatalogList<TenantDomainItem>(items);
|
||||
}
|
||||
|
||||
public async Task<ContentManagementResult<TenantDomainItem>> CreateDomainAsync(
|
||||
TenantAdminActor actor,
|
||||
CreateTenantDomainCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
TenantDomain generated;
|
||||
try
|
||||
{
|
||||
generated = TenantDomainProvisioning.CreatePrimary(actor.TenantId, command.Host);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
throw new TenantAdminDirectException(exception.Message, "invalid_domain_host");
|
||||
}
|
||||
|
||||
var host = generated.Host;
|
||||
if (command.IsPrimary)
|
||||
{
|
||||
var primaryDomains = await dbContext.TenantDomains
|
||||
.Where(item => item.TenantId == actor.TenantId && item.IsPrimary)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
foreach (var domain in primaryDomains) domain.IsPrimary = false;
|
||||
}
|
||||
|
||||
var item = new TenantDomain
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
Host = host,
|
||||
DomainType = ParseEnum(command.DomainType, TenantDomainType.Custom, "invalid_domain_type"),
|
||||
Status = TenantDomainStatus.Pending,
|
||||
IsPrimary = command.IsPrimary,
|
||||
VerificationToken = generated.VerificationToken
|
||||
};
|
||||
dbContext.TenantDomains.Add(item);
|
||||
await AddAuditAsync(actor, "tenant.domain.created", "tenant_domains", item.Id, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantDomainItem>(ToDomainItem(item));
|
||||
}
|
||||
|
||||
public async Task<CatalogList<TenantIdentityProviderItem>> GetAuthProvidersAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
var items = await providerConfigService.GetProvidersAsync(
|
||||
actor.TenantId,
|
||||
TenantExternalProviderCapability.Identity,
|
||||
cancellationToken: cancellationToken);
|
||||
return new CatalogList<TenantIdentityProviderItem>(items.Select(ToAuthProviderItem).ToArray());
|
||||
}
|
||||
|
||||
public async Task<ContentManagementResult<TenantIdentityProviderItem>> UpsertAuthProviderAsync(
|
||||
TenantAdminActor actor,
|
||||
UpsertTenantIdentityProviderCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(command.Provider);
|
||||
var item = await providerConfigService.UpsertProviderAsync(
|
||||
actor.TenantId,
|
||||
new UpsertTenantExternalProviderCommand(
|
||||
TenantExternalProviderCapability.Identity,
|
||||
command.Provider,
|
||||
ParseEnum(command.Status, TenantExternalProviderStatus.Disabled, "invalid_auth_provider_status"),
|
||||
command.DisplayName,
|
||||
command.SecretRef,
|
||||
command.Priority,
|
||||
command.ConfigPublic,
|
||||
JsonObjectOrDefault(default)),
|
||||
cancellationToken);
|
||||
|
||||
await AddAuditAsync(actor, "tenant.auth_provider.upserted", "tenant_external_providers", item.Id,
|
||||
cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantIdentityProviderItem>(ToAuthProviderItem(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,100 +13,9 @@ using Tiku.Infrastructure.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantEngagementService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantEngagementService
|
||||
{
|
||||
public async Task<CatalogList<TenantDomainItem>> GetDomainsAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
var items = await dbContext.TenantDomains.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId)
|
||||
.OrderByDescending(item => item.IsPrimary)
|
||||
.ThenBy(item => item.CreatedAt)
|
||||
.Select(item => ToDomainItem(item))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new CatalogList<TenantDomainItem>(items);
|
||||
}
|
||||
|
||||
public async Task<ContentManagementResult<TenantDomainItem>> CreateDomainAsync(
|
||||
TenantAdminActor actor,
|
||||
CreateTenantDomainCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
TenantDomain generated;
|
||||
try
|
||||
{
|
||||
generated = TenantDomainProvisioning.CreatePrimary(actor.TenantId, command.Host);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
throw new TenantAdminDirectException(exception.Message, "invalid_domain_host");
|
||||
}
|
||||
|
||||
var host = generated.Host;
|
||||
if (command.IsPrimary)
|
||||
{
|
||||
var primaryDomains = await dbContext.TenantDomains
|
||||
.Where(item => item.TenantId == actor.TenantId && item.IsPrimary)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
foreach (var domain in primaryDomains) domain.IsPrimary = false;
|
||||
}
|
||||
|
||||
var item = new TenantDomain
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
Host = host,
|
||||
DomainType = ParseEnum(command.DomainType, TenantDomainType.Custom, "invalid_domain_type"),
|
||||
Status = TenantDomainStatus.Pending,
|
||||
IsPrimary = command.IsPrimary,
|
||||
VerificationToken = generated.VerificationToken
|
||||
};
|
||||
dbContext.TenantDomains.Add(item);
|
||||
await AddAuditAsync(actor, "tenant.domain.created", "tenant_domains", item.Id, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantDomainItem>(ToDomainItem(item));
|
||||
}
|
||||
|
||||
public async Task<CatalogList<TenantIdentityProviderItem>> GetAuthProvidersAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
var items = await providerConfigService.GetProvidersAsync(
|
||||
actor.TenantId,
|
||||
TenantExternalProviderCapability.Identity,
|
||||
cancellationToken: cancellationToken);
|
||||
return new CatalogList<TenantIdentityProviderItem>(items.Select(ToAuthProviderItem).ToArray());
|
||||
}
|
||||
|
||||
public async Task<ContentManagementResult<TenantIdentityProviderItem>> UpsertAuthProviderAsync(
|
||||
TenantAdminActor actor,
|
||||
UpsertTenantIdentityProviderCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RequireAllDataScopeAsync(actor, cancellationToken);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(command.Provider);
|
||||
var item = await providerConfigService.UpsertProviderAsync(
|
||||
actor.TenantId,
|
||||
new UpsertTenantExternalProviderCommand(
|
||||
TenantExternalProviderCapability.Identity,
|
||||
command.Provider,
|
||||
ParseEnum(command.Status, TenantExternalProviderStatus.Disabled, "invalid_auth_provider_status"),
|
||||
command.DisplayName,
|
||||
command.SecretRef,
|
||||
command.Priority,
|
||||
command.ConfigPublic,
|
||||
JsonObjectOrDefault(default)),
|
||||
cancellationToken);
|
||||
|
||||
await AddAuditAsync(actor, "tenant.auth_provider.upserted", "tenant_external_providers", item.Id,
|
||||
cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantIdentityProviderItem>(ToAuthProviderItem(item));
|
||||
}
|
||||
|
||||
public async Task<CatalogList<TenantAdminBadgeItem>> GetBadgesAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantAdminBadgeFilter filter,
|
||||
@@ -455,4 +364,5 @@ public sealed partial class TenantAdminDirectService
|
||||
: null;
|
||||
return new ContentManagementResult<TenantAdminFeedbackItem>(ToFeedbackItem(report, user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal abstract partial class TenantAdminServiceBase
|
||||
{
|
||||
private async Task<User> ResolveUserAsync(UserLookupCommand command, string primaryRole,
|
||||
protected async Task<User> ResolveUserAsync(UserLookupCommand command, string primaryRole,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
User? user = null;
|
||||
@@ -61,7 +61,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return user;
|
||||
}
|
||||
|
||||
private async Task<TenantMembership> EnsureMembershipAsync(
|
||||
protected async Task<TenantMembership> EnsureMembershipAsync(
|
||||
Guid tenantId,
|
||||
Guid userId,
|
||||
TenantRole role,
|
||||
@@ -105,14 +105,14 @@ public sealed partial class TenantAdminDirectService
|
||||
return membership;
|
||||
}
|
||||
|
||||
private static string QuotaMetricForRole(TenantRole role)
|
||||
protected static string QuotaMetricForRole(TenantRole role)
|
||||
{
|
||||
return role == TenantRole.Student
|
||||
? SaasQuotaMetricCatalog.StudentCount
|
||||
: SaasQuotaMetricCatalog.StaffCount;
|
||||
}
|
||||
|
||||
private Task<bool> IsUserCountedForMetricAsync(
|
||||
protected Task<bool> IsUserCountedForMetricAsync(
|
||||
Guid tenantId,
|
||||
Guid userId,
|
||||
string metricCode,
|
||||
@@ -130,7 +130,7 @@ public sealed partial class TenantAdminDirectService
|
||||
: query.AnyAsync(item => item.Role != TenantRole.Student, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<StudentProfile> EnsureStudentProfileAsync(
|
||||
protected async Task<StudentProfile> EnsureStudentProfileAsync(
|
||||
Guid tenantId,
|
||||
Guid userId,
|
||||
Guid? regionId,
|
||||
@@ -169,7 +169,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return profile;
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<TenantAdminStudentImportPreviewItem>> BuildStudentImportPreviewAsync(
|
||||
protected async Task<IReadOnlyCollection<TenantAdminStudentImportPreviewItem>> BuildStudentImportPreviewAsync(
|
||||
TenantAdminActor actor,
|
||||
CurrentDataScope scope,
|
||||
TenantAdminStudentImportCommand command,
|
||||
@@ -215,7 +215,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return items;
|
||||
}
|
||||
|
||||
private async Task<TenantClassMember> UpsertClassMemberCoreAsync(
|
||||
protected async Task<TenantClassMember> UpsertClassMemberCoreAsync(
|
||||
TenantAdminActor actor,
|
||||
Guid classId,
|
||||
Guid userId,
|
||||
@@ -249,7 +249,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return item;
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<TenantSupervisionRuleItem>> GetSupervisionRulesCoreAsync(
|
||||
protected async Task<IReadOnlyCollection<TenantSupervisionRuleItem>> GetSupervisionRulesCoreAsync(
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -264,7 +264,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return JsonSerializer.Deserialize<TenantSupervisionRuleItem[]>(rulesElement.GetRawText()) ?? [];
|
||||
}
|
||||
|
||||
private async Task SaveSupervisionRulesCoreAsync(
|
||||
protected async Task SaveSupervisionRulesCoreAsync(
|
||||
Guid tenantId,
|
||||
IReadOnlyCollection<TenantSupervisionRuleItem> rules,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -284,7 +284,7 @@ public sealed partial class TenantAdminDirectService
|
||||
settings.AdminFeatureFlags = JsonSerializer.SerializeToElement(existing);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<TenantSupervisionRiskStudentItem>> BuildSupervisionRiskStudentsAsync(
|
||||
protected async Task<IReadOnlyCollection<TenantSupervisionRiskStudentItem>> BuildSupervisionRiskStudentsAsync(
|
||||
TenantAdminActor actor,
|
||||
CurrentDataScope scope,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -348,7 +348,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task AssertStudentAsync(Guid tenantId, Guid userId, CancellationToken cancellationToken)
|
||||
protected async Task AssertStudentAsync(Guid tenantId, Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var exists = await dbContext.TenantMemberships.AnyAsync(
|
||||
item => item.TenantId == tenantId && item.UserId == userId && item.Role == TenantRole.Student,
|
||||
@@ -356,7 +356,7 @@ public sealed partial class TenantAdminDirectService
|
||||
if (!exists) throw new TenantAdminDirectException("Student was not found.", "student_not_found");
|
||||
}
|
||||
|
||||
private async Task AssertStudentAsync(
|
||||
protected async Task AssertStudentAsync(
|
||||
TenantAdminActor actor,
|
||||
CurrentDataScope scope,
|
||||
Guid userId,
|
||||
@@ -383,7 +383,7 @@ public sealed partial class TenantAdminDirectService
|
||||
if (!exists) throw new TenantAdminDirectException("Student was not found.", "student_not_found");
|
||||
}
|
||||
|
||||
private async Task AssertTenantMemberAsync(Guid tenantId, Guid? userId, CancellationToken cancellationToken)
|
||||
protected async Task AssertTenantMemberAsync(Guid tenantId, Guid? userId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!userId.HasValue) return;
|
||||
|
||||
@@ -393,13 +393,13 @@ public sealed partial class TenantAdminDirectService
|
||||
if (!exists) throw new TenantAdminDirectException("Tenant member was not found.", "tenant_member_not_found");
|
||||
}
|
||||
|
||||
private async Task RevokeSessionsAsync(Guid tenantId, Guid userId, CancellationToken cancellationToken)
|
||||
protected async Task RevokeSessionsAsync(Guid tenantId, Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
await sessionStore.RevokeRealmAsync(
|
||||
userId, AuthRealm.Tenant, tenantId, "membership_disabled", cancellationToken);
|
||||
}
|
||||
|
||||
private async Task EnsureTenantOwnerBackendRoleAsync(
|
||||
protected async Task EnsureTenantOwnerBackendRoleAsync(
|
||||
Guid tenantId,
|
||||
Guid userId,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -470,7 +470,7 @@ public sealed partial class TenantAdminDirectService
|
||||
});
|
||||
}
|
||||
|
||||
private async Task EnsureBrandingThemeAsync(
|
||||
protected async Task EnsureBrandingThemeAsync(
|
||||
Guid tenantId,
|
||||
JsonElement theme,
|
||||
JsonElement publicAssets,
|
||||
@@ -496,7 +496,7 @@ public sealed partial class TenantAdminDirectService
|
||||
branding.PublicAssets = MergeJsonObjects(branding.PublicAssets, publicAssets);
|
||||
}
|
||||
|
||||
private async Task AssertClassAsync(Guid tenantId, Guid? classId, CancellationToken cancellationToken)
|
||||
protected async Task AssertClassAsync(Guid tenantId, Guid? classId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!classId.HasValue) return;
|
||||
|
||||
@@ -506,7 +506,7 @@ public sealed partial class TenantAdminDirectService
|
||||
if (!exists) throw new TenantAdminDirectException("Class was not found.", "class_not_found");
|
||||
}
|
||||
|
||||
private async Task AssertClassAsync(
|
||||
protected async Task AssertClassAsync(
|
||||
TenantAdminActor actor,
|
||||
CurrentDataScope scope,
|
||||
Guid? classId,
|
||||
@@ -527,7 +527,7 @@ public sealed partial class TenantAdminDirectService
|
||||
if (!exists) throw new TenantAdminDirectException("Class was not found.", "class_not_found");
|
||||
}
|
||||
|
||||
private async Task<CurrentDataScope> RequireDataScopeAsync(
|
||||
protected async Task<CurrentDataScope> RequireDataScopeAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -538,7 +538,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return access.DataScope;
|
||||
}
|
||||
|
||||
private async Task RequireAllDataScopeAsync(
|
||||
protected async Task RequireAllDataScopeAsync(
|
||||
TenantAdminActor actor,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -547,7 +547,7 @@ public sealed partial class TenantAdminDirectService
|
||||
throw new TenantAdminDirectException("Tenant-wide resource was not found.", "tenant_resource_not_found");
|
||||
}
|
||||
|
||||
private async Task AssertReferenceAsync<TEntity>(
|
||||
protected async Task AssertReferenceAsync<TEntity>(
|
||||
Guid tenantId,
|
||||
Guid? id,
|
||||
string code,
|
||||
@@ -561,7 +561,7 @@ public sealed partial class TenantAdminDirectService
|
||||
if (!exists) throw new TenantAdminDirectException("Referenced entity was not found in this tenant.", code);
|
||||
}
|
||||
|
||||
private async Task<TEntity?> ResolveTenantEntityAsync<TEntity>(
|
||||
protected async Task<TEntity?> ResolveTenantEntityAsync<TEntity>(
|
||||
DbSet<TEntity> set,
|
||||
Guid tenantId,
|
||||
Guid? id,
|
||||
@@ -583,7 +583,7 @@ public sealed partial class TenantAdminDirectService
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task AddAuditAsync(
|
||||
protected async Task AddAuditAsync(
|
||||
TenantAdminActor actor,
|
||||
string action,
|
||||
string targetType,
|
||||
@@ -601,4 +601,4 @@ public sealed partial class TenantAdminDirectService
|
||||
});
|
||||
await Task.CompletedTask.WaitAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal abstract partial class TenantAdminServiceBase
|
||||
{
|
||||
private static TenantAdminClassItem ToClassItem(TenantClass item, string? regionName, int studentCount,
|
||||
protected static TenantAdminClassItem ToClassItem(TenantClass item, string? regionName, int studentCount,
|
||||
int staffCount)
|
||||
{
|
||||
return new TenantAdminClassItem(
|
||||
@@ -32,7 +32,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminClassMemberItem ToClassMemberItem(TenantClassMember item, TenantAdminUserSummary user)
|
||||
protected static TenantAdminClassMemberItem ToClassMemberItem(TenantClassMember item, TenantAdminUserSummary user)
|
||||
{
|
||||
return new TenantAdminClassMemberItem(
|
||||
item.Id,
|
||||
@@ -46,7 +46,7 @@ public sealed partial class TenantAdminDirectService
|
||||
user);
|
||||
}
|
||||
|
||||
private static TenantAdminStudentItem ToStudentItem(
|
||||
protected static TenantAdminStudentItem ToStudentItem(
|
||||
TenantMembership membership,
|
||||
User user,
|
||||
StudentProfile? profile,
|
||||
@@ -80,7 +80,7 @@ public sealed partial class TenantAdminDirectService
|
||||
membership.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminUserSummary ToUserSummary(User user)
|
||||
protected static TenantAdminUserSummary ToUserSummary(User user)
|
||||
{
|
||||
return new TenantAdminUserSummary(
|
||||
user.Id,
|
||||
@@ -92,7 +92,7 @@ public sealed partial class TenantAdminDirectService
|
||||
user.PrimaryRole);
|
||||
}
|
||||
|
||||
private static TenantAdminStudentNoteItem ToNoteItem(TenantStudentNote item)
|
||||
protected static TenantAdminStudentNoteItem ToNoteItem(TenantStudentNote item)
|
||||
{
|
||||
return new TenantAdminStudentNoteItem(
|
||||
item.Id,
|
||||
@@ -108,7 +108,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminStudentFollowupItem ToFollowupItem(TenantStudentFollowup item)
|
||||
protected static TenantAdminStudentFollowupItem ToFollowupItem(TenantStudentFollowup item)
|
||||
{
|
||||
return new TenantAdminStudentFollowupItem(
|
||||
item.Id,
|
||||
@@ -130,7 +130,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminMemberItem ToMemberItem(TenantMembership membership, User user)
|
||||
protected static TenantAdminMemberItem ToMemberItem(TenantMembership membership, User user)
|
||||
{
|
||||
return new TenantAdminMemberItem(
|
||||
membership.Id,
|
||||
@@ -143,7 +143,7 @@ public sealed partial class TenantAdminDirectService
|
||||
membership.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantBrandingItem ToBrandingItem(TenantBranding item)
|
||||
protected static TenantBrandingItem ToBrandingItem(TenantBranding item)
|
||||
{
|
||||
return new TenantBrandingItem(
|
||||
item.TenantId,
|
||||
@@ -160,7 +160,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantSettingsItem ToSettingsItem(TenantSettings item)
|
||||
protected static TenantSettingsItem ToSettingsItem(TenantSettings item)
|
||||
{
|
||||
return new TenantSettingsItem(
|
||||
item.TenantId,
|
||||
@@ -170,7 +170,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantThemeItem ToThemeItem(TenantThemeConfig item)
|
||||
protected static TenantThemeItem ToThemeItem(TenantThemeConfig item)
|
||||
{
|
||||
return new TenantThemeItem(
|
||||
item.TenantId,
|
||||
@@ -187,7 +187,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantDomainItem ToDomainItem(TenantDomain item)
|
||||
protected static TenantDomainItem ToDomainItem(TenantDomain item)
|
||||
{
|
||||
return new TenantDomainItem(
|
||||
item.Id,
|
||||
@@ -205,7 +205,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantIdentityProviderItem ToAuthProviderItem(TenantExternalProviderItem item)
|
||||
protected static TenantIdentityProviderItem ToAuthProviderItem(TenantExternalProviderItem item)
|
||||
{
|
||||
return new TenantIdentityProviderItem(
|
||||
item.Id,
|
||||
@@ -219,7 +219,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminBadgeItem ToBadgeItem(Badge item)
|
||||
protected static TenantAdminBadgeItem ToBadgeItem(Badge item)
|
||||
{
|
||||
return new TenantAdminBadgeItem(
|
||||
item.Id,
|
||||
@@ -240,7 +240,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminBadgeGrantItem ToBadgeGrantItem(UserBadge grant, User? user, User? grantedBy,
|
||||
protected static TenantAdminBadgeGrantItem ToBadgeGrantItem(UserBadge grant, User? user, User? grantedBy,
|
||||
Badge? badge)
|
||||
{
|
||||
return new TenantAdminBadgeGrantItem(
|
||||
@@ -262,7 +262,7 @@ public sealed partial class TenantAdminDirectService
|
||||
grant.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminNotificationItem ToNotificationItem(UserNotification item)
|
||||
protected static TenantAdminNotificationItem ToNotificationItem(UserNotification item)
|
||||
{
|
||||
return new TenantAdminNotificationItem(
|
||||
item.Id,
|
||||
@@ -284,7 +284,7 @@ public sealed partial class TenantAdminDirectService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantAdminFeedbackItem ToFeedbackItem(Report report, User? user)
|
||||
protected static TenantAdminFeedbackItem ToFeedbackItem(Report report, User? user)
|
||||
{
|
||||
return new TenantAdminFeedbackItem(
|
||||
report.Id,
|
||||
@@ -308,22 +308,22 @@ public sealed partial class TenantAdminDirectService
|
||||
report.UpdatedAt);
|
||||
}
|
||||
|
||||
private static int ResolveLimit(int? limit)
|
||||
protected static int ResolveLimit(int? limit)
|
||||
{
|
||||
return Math.Clamp(limit ?? 100, 1, 500);
|
||||
}
|
||||
|
||||
private static string? Normalize(string? value)
|
||||
protected static string? Normalize(string? value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
|
||||
private static string NormalizeCode(string value)
|
||||
protected static string NormalizeCode(string value)
|
||||
{
|
||||
return value.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string? MaskPhone(string? phone)
|
||||
protected static string? MaskPhone(string? phone)
|
||||
{
|
||||
var value = Normalize(phone);
|
||||
return value is { Length: >= 7 }
|
||||
@@ -331,12 +331,12 @@ public sealed partial class TenantAdminDirectService
|
||||
: value;
|
||||
}
|
||||
|
||||
private static JsonElement JsonObjectOrDefault(JsonElement value)
|
||||
protected static JsonElement JsonObjectOrDefault(JsonElement value)
|
||||
{
|
||||
return value.ValueKind == JsonValueKind.Object ? value.Clone() : JsonDefaults.Object();
|
||||
}
|
||||
|
||||
private static JsonElement PermissionObject(JsonElement value)
|
||||
protected static JsonElement PermissionObject(JsonElement value)
|
||||
{
|
||||
var result = new Dictionary<string, bool>(StringComparer.Ordinal);
|
||||
if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return JsonDefaults.Object();
|
||||
@@ -358,7 +358,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return JsonSerializer.SerializeToElement(result);
|
||||
}
|
||||
|
||||
private static JsonElement AccessMap(JsonElement value, string codePrefix)
|
||||
protected static JsonElement AccessMap(JsonElement value, string codePrefix)
|
||||
{
|
||||
var result = new Dictionary<string, bool>(StringComparer.Ordinal);
|
||||
if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return JsonDefaults.Object();
|
||||
@@ -380,7 +380,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return JsonSerializer.SerializeToElement(result);
|
||||
}
|
||||
|
||||
private static JsonElement DataScope(JsonElement value)
|
||||
protected static JsonElement DataScope(JsonElement value)
|
||||
{
|
||||
if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return JsonDefaults.Object();
|
||||
|
||||
@@ -404,7 +404,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return value.Clone();
|
||||
}
|
||||
|
||||
private async Task AssertGrantableAsync(
|
||||
protected async Task AssertGrantableAsync(
|
||||
TenantAdminActor actor,
|
||||
TenantRole role,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -428,7 +428,7 @@ public sealed partial class TenantAdminDirectService
|
||||
"tenant_owner_required");
|
||||
}
|
||||
|
||||
private static string RoleToPrimaryRole(TenantRole role)
|
||||
protected static string RoleToPrimaryRole(TenantRole role)
|
||||
{
|
||||
return role switch
|
||||
{
|
||||
@@ -443,7 +443,7 @@ public sealed partial class TenantAdminDirectService
|
||||
};
|
||||
}
|
||||
|
||||
private static string NormalizeRoleCode(string value)
|
||||
protected static string NormalizeRoleCode(string value)
|
||||
{
|
||||
var code = new string(value.Trim().ToLowerInvariant()
|
||||
.Select(character => char.IsAsciiLetterOrDigit(character) || character is '_' or '-' ? character : '-')
|
||||
@@ -457,7 +457,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return code;
|
||||
}
|
||||
|
||||
private static string NormalizeDomain(string value)
|
||||
protected static string NormalizeDomain(string value)
|
||||
{
|
||||
var host = Normalize(value)?.ToLowerInvariant()
|
||||
.TrimEnd('.') ?? throw new TenantAdminDirectException("Domain host is required.", "domain_host_required");
|
||||
@@ -469,7 +469,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return host;
|
||||
}
|
||||
|
||||
private static JsonElement MergeJsonObjects(JsonElement first, JsonElement second)
|
||||
protected static JsonElement MergeJsonObjects(JsonElement first, JsonElement second)
|
||||
{
|
||||
var result = new Dictionary<string, JsonElement>(StringComparer.Ordinal);
|
||||
if (first.ValueKind == JsonValueKind.Object)
|
||||
@@ -483,7 +483,7 @@ public sealed partial class TenantAdminDirectService
|
||||
return JsonSerializer.SerializeToElement(result);
|
||||
}
|
||||
|
||||
private static void AssertNoSecrets(JsonElement value, string code)
|
||||
protected static void AssertNoSecrets(JsonElement value, string code)
|
||||
{
|
||||
if (value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) return;
|
||||
|
||||
@@ -503,14 +503,14 @@ public sealed partial class TenantAdminDirectService
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPermissionKey(string key)
|
||||
protected static bool IsPermissionKey(string key)
|
||||
{
|
||||
return key.Length <= 100 &&
|
||||
key.Contains(':', StringComparison.Ordinal) &&
|
||||
key.All(character => char.IsAsciiLetterOrDigit(character) || character is ':' or '*');
|
||||
}
|
||||
|
||||
private static bool IsAccessKey(string key)
|
||||
protected static bool IsAccessKey(string key)
|
||||
{
|
||||
return key.Length <= 100 &&
|
||||
key.Length > 0 &&
|
||||
@@ -518,13 +518,13 @@ public sealed partial class TenantAdminDirectService
|
||||
key.All(character => char.IsAsciiLetterOrDigit(character) || character is '_' or '.' or ':' or '-');
|
||||
}
|
||||
|
||||
private static TEnum ParseEnum<TEnum>(string? value, TEnum fallback, string code)
|
||||
protected static TEnum ParseEnum<TEnum>(string? value, TEnum fallback, string code)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? fallback : ParseEnum<TEnum>(value, code);
|
||||
}
|
||||
|
||||
private static TEnum ParseEnum<TEnum>(string value, string code)
|
||||
protected static TEnum ParseEnum<TEnum>(string value, string code)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
var normalized = value.Replace("_", string.Empty, StringComparison.Ordinal)
|
||||
@@ -535,4 +535,4 @@ public sealed partial class TenantAdminDirectService
|
||||
|
||||
throw new TenantAdminDirectException("Enum value was invalid.", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Notifications;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
internal sealed record TenantAdminServiceDependencies(
|
||||
TikuDbContext DbContext,
|
||||
ITenantExternalProviderConfigService ProviderConfigService,
|
||||
INotificationProvider NotificationProvider,
|
||||
ICurrentAccessContext CurrentAccessContext,
|
||||
IAuthSessionStore SessionStore,
|
||||
IFeatureAccessService FeatureAccessService,
|
||||
IAuthorizationStateInvalidator AuthorizationStateInvalidator);
|
||||
|
||||
internal abstract partial class TenantAdminServiceBase(TenantAdminServiceDependencies dependencies)
|
||||
{
|
||||
protected TikuDbContext dbContext { get; } = dependencies.DbContext;
|
||||
protected ITenantExternalProviderConfigService providerConfigService { get; } = dependencies.ProviderConfigService;
|
||||
protected INotificationProvider notificationProvider { get; } = dependencies.NotificationProvider;
|
||||
protected ICurrentAccessContext currentAccessContext { get; } = dependencies.CurrentAccessContext;
|
||||
protected IAuthSessionStore sessionStore { get; } = dependencies.SessionStore;
|
||||
protected IFeatureAccessService featureAccessService { get; } = dependencies.FeatureAccessService;
|
||||
|
||||
protected IAuthorizationStateInvalidator authorizationStateInvalidator { get; } =
|
||||
dependencies.AuthorizationStateInvalidator;
|
||||
}
|
||||
@@ -8,7 +8,8 @@ using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantMemberAccessService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantMemberAccessService
|
||||
{
|
||||
public async Task<CatalogList<TenantAdminMemberItem>> GetMembersAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -195,4 +196,4 @@ public sealed partial class TenantAdminDirectService
|
||||
.SingleAsync(item => item.Id == membership.UserId, cancellationToken);
|
||||
return new ContentManagementResult<TenantAdminMemberItem>(ToMemberItem(membership, user));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantSiteSettingsService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantSiteSettingsService
|
||||
{
|
||||
public async Task<CatalogList<TenantAdminAuditLogItem>> GetAuditLogsAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -242,4 +243,4 @@ public sealed partial class TenantAdminDirectService
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantThemeItem>(ToThemeItem(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantStudentEngagementService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantStudentEngagementService
|
||||
{
|
||||
public async Task<CatalogList<TenantAdminStudentNoteItem>> GetStudentNotesAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -208,4 +209,4 @@ public sealed partial class TenantAdminDirectService
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new ContentManagementResult<TenantAdminStudentFollowupItem>(ToFollowupItem(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantStudentService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantStudentService
|
||||
{
|
||||
public async Task<TenantAdminStudentList> GetStudentsAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -373,4 +374,4 @@ public sealed partial class TenantAdminDirectService
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return new TenantAdminBulkOperationResult(command.UserIds.Count, succeeded, failed.Count, failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,8 @@ using PointExchangeOrderStatus = Tiku.Domain.Commerce.PointExchangeOrderStatus;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService
|
||||
internal sealed class TenantSupervisionService(TenantAdminServiceDependencies dependencies)
|
||||
: TenantAdminServiceBase(dependencies), ITenantSupervisionService
|
||||
{
|
||||
public async Task<CatalogList<TenantSupervisionRuleItem>> GetSupervisionRulesAsync(
|
||||
TenantAdminActor actor,
|
||||
@@ -173,4 +174,4 @@ public sealed partial class TenantAdminDirectService
|
||||
cancellationToken);
|
||||
return new TenantPointRiskReport(negativeScoreUsers, highClaimUsers, cancelledExchangeOrders);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Notifications;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Application.TenantAdmin;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.TenantAdmin;
|
||||
|
||||
public sealed partial class TenantAdminDirectService(
|
||||
TikuDbContext dbContext,
|
||||
ITenantExternalProviderConfigService providerConfigService,
|
||||
INotificationProvider notificationProvider,
|
||||
ICurrentAccessContext currentAccessContext,
|
||||
IAuthSessionStore sessionStore,
|
||||
IFeatureAccessService featureAccessService,
|
||||
IAuthorizationStateInvalidator authorizationStateInvalidator) : ITenantAdminDirectService
|
||||
{
|
||||
}
|
||||
@@ -35,7 +35,6 @@ public sealed class ArchitectureBoundaryTests
|
||||
var root = FindRepositoryRoot();
|
||||
var legacyLineBudgets = new Dictionary<string, int>(StringComparer.Ordinal)
|
||||
{
|
||||
["TenantAdminDirectService"] = 3190,
|
||||
["DirectContentService"] = 2015,
|
||||
["CommerceAdminService"] = 1842,
|
||||
["LearningActivityService"] = 1781,
|
||||
@@ -168,11 +167,14 @@ public sealed class ArchitectureBoundaryTests
|
||||
{
|
||||
var root = FindRepositoryRoot();
|
||||
var files = new[]
|
||||
{
|
||||
Path.Combine(root, "Tiku.Infrastructure", "Growth", "CommissionService.cs"),
|
||||
Path.Combine(root, "Tiku.Infrastructure", "Growth", "ReferralService.cs"),
|
||||
Path.Combine(root, "Tiku.Api", "Controllers", "TenantAdminDirectController.cs")
|
||||
};
|
||||
{
|
||||
Path.Combine(root, "Tiku.Infrastructure", "Growth", "CommissionService.cs"),
|
||||
Path.Combine(root, "Tiku.Infrastructure", "Growth", "ReferralService.cs")
|
||||
}
|
||||
.Concat(Directory.EnumerateFiles(
|
||||
Path.Combine(root, "Tiku.Api", "Controllers"),
|
||||
"Tenant*Controller.cs",
|
||||
SearchOption.AllDirectories));
|
||||
|
||||
AssertNoForbiddenSymbols(
|
||||
root,
|
||||
|
||||
Reference in New Issue
Block a user