diff --git a/Tiku.Api/Configuration/ApiPresentationExtensions.cs b/Tiku.Api/Configuration/ApiPresentationExtensions.cs index b6d766a..afce631 100644 --- a/Tiku.Api/Configuration/ApiPresentationExtensions.cs +++ b/Tiku.Api/Configuration/ApiPresentationExtensions.cs @@ -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(); }); services.AddProblemDetails(); + services.AddScoped(); return services; } diff --git a/Tiku.Api/Controllers/TenantAdminActorResolver.cs b/Tiku.Api/Controllers/TenantAdminActorResolver.cs new file mode 100644 index 0000000..f882595 --- /dev/null +++ b/Tiku.Api/Controllers/TenantAdminActorResolver.cs @@ -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"); + } + } +} diff --git a/Tiku.Api/Controllers/TenantAdminDashboardController.cs b/Tiku.Api/Controllers/TenantAdminDashboardController.cs new file mode 100644 index 0000000..75da41d --- /dev/null +++ b/Tiku.Api/Controllers/TenantAdminDashboardController.cs @@ -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(StatusCodes.Status200OK)] + public async Task> GetOverview(CancellationToken cancellationToken) + { + return Ok(await dashboardService.GetOverviewAsync(actorResolver.Resolve(), cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantAdminDirectController.cs b/Tiku.Api/Controllers/TenantAdminDirectController.cs deleted file mode 100644 index 2e5d2dd..0000000 --- a/Tiku.Api/Controllers/TenantAdminDirectController.cs +++ /dev/null @@ -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(StatusCodes.Status200OK)] - public async Task> GetOverview(CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetOverviewAsync(ResolveActor(), cancellationToken)); - } - - [HttpGet("classes")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询租户班级")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetClasses( - [FromQuery] TenantAdminClassQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetClassesAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("classes")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新租户班级")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertClass( - UpsertTenantAdminClassDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertClassAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpPost("classes/disable")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("停用租户班级")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> DisableClass( - [FromBody] Guid classId, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.DisableClassAsync(ResolveActor(), classId, cancellationToken)); - } - - [HttpGet("classes/members")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询班级成员")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetClassMembers( - [FromQuery] TenantAdminClassMemberQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetClassMembersAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("classes/members")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新班级成员")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertClassMember( - UpsertTenantAdminClassMemberDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertClassMemberAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpPost("classes/members/remove")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("移除班级成员")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> RemoveClassMember( - RemoveTenantAdminClassMemberDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.RemoveClassMemberAsync(ResolveActor(), request.ClassMemberId, - cancellationToken)); - } - - [HttpGet("students")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询租户学生")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetStudents( - [FromQuery] TenantAdminStudentQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetStudentsAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("students")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新租户学生档案")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertStudent( - UpsertTenantAdminStudentDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertStudentAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpPost("students/status")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("更新租户学生状态")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpdateStudentStatus( - UpdateTenantAdminStudentStatusDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpdateStudentStatusAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpPost("students/import/preview")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("预览学生批量导入")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> PreviewStudentImport( - TenantAdminStudentImportDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.PreviewStudentImportAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpPost("students/import")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("提交学生批量导入")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> 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(StatusCodes.Status200OK)] - public async Task> BulkAssignClass( - TenantAdminBulkAssignClassDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.BulkAssignClassAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpPost("students/bulk-status")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("批量调整学生状态")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> BulkStudentStatus( - TenantAdminBulkStatusDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.BulkUpdateStudentStatusAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("students/supervision/rules")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询学习督导规则")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> SupervisionRules( - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetSupervisionRulesAsync(ResolveActor(), cancellationToken)); - } - - [HttpPut("students/supervision/rules")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新学习督导规则")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertSupervisionRule( - UpsertTenantSupervisionRuleDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertSupervisionRuleAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("students/supervision/preview")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("预览学习风险学生")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> PreviewSupervision(CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.PreviewSupervisionAsync(ResolveActor(), cancellationToken)); - } - - [HttpPost("students/supervision/generate")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("批量生成学习督导跟进任务")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GenerateSupervision( - TenantSupervisionGenerateDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GenerateSupervisionFollowupsAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("student-followups/report")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询学生跟进统计报表")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> FollowupReport(CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetFollowupReportAsync(ResolveActor(), cancellationToken)); - } - - [HttpGet("feedbacks/report")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询反馈处理统计报表")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> FeedbackReport(CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetFeedbackReportAsync(ResolveActor(), cancellationToken)); - } - - [HttpGet("points/risk-report")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询积分风险统计报表")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> PointRiskReport(CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetPointRiskReportAsync(ResolveActor(), cancellationToken)); - } - - [HttpGet("student-notes")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询学生备注")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetStudentNotes( - [FromQuery] TenantAdminStudentActivityQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetStudentNotesAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("student-notes")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新学生备注")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertStudentNote( - UpsertTenantAdminStudentNoteDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertStudentNoteAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("student-followups")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询学生跟进")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetStudentFollowups( - [FromQuery] TenantAdminStudentActivityQueryDto query, - CancellationToken cancellationToken) - { - return Ok( - await tenantAdminService.GetStudentFollowupsAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("student-followups")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新学生跟进")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertStudentFollowup( - UpsertTenantAdminStudentFollowupDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertStudentFollowupAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("members")] - [Authorize(Policy = BackendPermissions.TenantStaffManage)] - [EndpointSummary("查询租户成员")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetMembers( - [FromQuery] TenantAdminMemberQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetMembersAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("members")] - [Authorize(Policy = BackendPermissions.TenantStaffManage)] - [EndpointSummary("新增或更新租户成员")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertMember( - UpsertTenantAdminMemberDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertMemberAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpPost("members/disable")] - [Authorize(Policy = BackendPermissions.TenantStaffManage)] - [EndpointSummary("停用租户成员并撤销会话")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> 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 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>(StatusCodes.Status200OK)] - public async Task>> GetAuditLogs( - [FromQuery] TenantAdminAuditLogQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetAuditLogsAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("branding")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("更新租户品牌信息")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertBranding( - UpsertTenantBrandingDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertBrandingAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpPut("settings")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("更新租户公开设置与功能开关")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertSettings( - UpsertTenantSettingsDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertSettingsAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpGet("theme-templates")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("查询可用租户主题模板")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetThemeTemplates( - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetThemeTemplatesAsync(ResolveActor(), cancellationToken)); - } - - [HttpGet("theme")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("查询租户当前主题与草稿")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetTheme( - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetThemeAsync(ResolveActor(), cancellationToken)); - } - - [HttpPost("theme/preview")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("生成租户主题草稿")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> PreviewTheme( - PreviewTenantThemeDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.PreviewThemeAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpPost("theme/publish")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("发布租户主题")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> PublishTheme( - PublishTenantThemeDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.PublishThemeAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpGet("domains")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("查询租户域名")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetDomains(CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetDomainsAsync(ResolveActor(), cancellationToken)); - } - - [HttpPost("domains")] - [Authorize(Policy = BackendPermissions.TenantSettingsManage)] - [EndpointSummary("添加租户域名")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> CreateDomain( - CreateTenantDomainDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.CreateDomainAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpGet("auth-providers")] - [Authorize(Policy = BackendPermissions.TenantProviderManage)] - [EndpointSummary("查询租户登录 Provider 公开配置")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetAuthProviders( - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetAuthProvidersAsync(ResolveActor(), cancellationToken)); - } - - [HttpPut("auth-providers")] - [Authorize(Policy = BackendPermissions.TenantProviderManage)] - [EndpointSummary("新增或更新租户登录 Provider")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertAuthProvider( - UpsertTenantIdentityProviderDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertAuthProviderAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("badges")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询租户勋章")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetBadges( - [FromQuery] TenantAdminBadgeQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetBadgesAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("badges")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新租户勋章")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertBadge( - UpsertTenantAdminBadgeDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertBadgeAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpGet("badge-grants")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询勋章发放记录")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetBadgeGrants( - [FromQuery] TenantAdminBadgeGrantQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetBadgeGrantsAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPost("badge-grants")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("向租户成员发放勋章")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GrantBadge( - GrantTenantAdminBadgeDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GrantBadgeAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpGet("notifications")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询用户站内通知")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetNotifications( - [FromQuery] TenantAdminNotificationQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetNotificationsAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPut("notifications")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("新增或更新用户站内通知")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> UpsertNotification( - UpsertTenantAdminNotificationDto request, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.UpsertNotificationAsync(ResolveActor(), request.ToCommand(), - cancellationToken)); - } - - [HttpGet("feedbacks")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("查询用户反馈")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> GetFeedbacks( - [FromQuery] TenantAdminFeedbackQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await tenantAdminService.GetFeedbacksAsync(ResolveActor(), query.ToFilter(), cancellationToken)); - } - - [HttpPost("feedbacks/status")] - [Authorize(Policy = BackendPermissions.TenantStudentManage)] - [EndpointSummary("处理用户反馈")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public async Task>> 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"); - } - } -} \ No newline at end of file diff --git a/Tiku.Api/Controllers/TenantClassController.cs b/Tiku.Api/Controllers/TenantClassController.cs new file mode 100644 index 0000000..50a3f43 --- /dev/null +++ b/Tiku.Api/Controllers/TenantClassController.cs @@ -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(StatusCodes.Status200OK)] + public async Task> GetClasses( + [FromQuery] TenantAdminClassQueryDto query, + CancellationToken cancellationToken) + { + return Ok(await classService.GetClassesAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken)); + } + + [HttpPut("classes")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("新增或更新租户班级")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertClass( + UpsertTenantAdminClassDto request, + CancellationToken cancellationToken) + { + return Ok(await classService.UpsertClassAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpPost("classes/disable")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("停用租户班级")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> DisableClass( + [FromBody] Guid classId, + CancellationToken cancellationToken) + { + return Ok(await classService.DisableClassAsync(actorResolver.Resolve(), classId, cancellationToken)); + } + + [HttpGet("classes/members")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询班级成员")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> RemoveClassMember( + RemoveTenantAdminClassMemberDto request, + CancellationToken cancellationToken) + { + return Ok(await classService.RemoveClassMemberAsync(actorResolver.Resolve(), request.ClassMemberId, + cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantDomainProviderController.cs b/Tiku.Api/Controllers/TenantDomainProviderController.cs new file mode 100644 index 0000000..5846506 --- /dev/null +++ b/Tiku.Api/Controllers/TenantDomainProviderController.cs @@ -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>(StatusCodes.Status200OK)] + public async Task>> GetDomains(CancellationToken cancellationToken) + { + return Ok(await domainProviderService.GetDomainsAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpPost("domains")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("添加租户域名")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> GetAuthProviders( + CancellationToken cancellationToken) + { + return Ok(await domainProviderService.GetAuthProvidersAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpPut("auth-providers")] + [Authorize(Policy = BackendPermissions.TenantProviderManage)] + [EndpointSummary("新增或更新租户登录 Provider")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertAuthProvider( + UpsertTenantIdentityProviderDto request, + CancellationToken cancellationToken) + { + return Ok(await domainProviderService.UpsertAuthProviderAsync(actorResolver.Resolve(), request.ToCommand(), + cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantEngagementController.cs b/Tiku.Api/Controllers/TenantEngagementController.cs new file mode 100644 index 0000000..31d982f --- /dev/null +++ b/Tiku.Api/Controllers/TenantEngagementController.cs @@ -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>(StatusCodes.Status200OK)] + public async Task>> GetBadges( + [FromQuery] TenantAdminBadgeQueryDto query, + CancellationToken cancellationToken) + { + return Ok(await engagementService.GetBadgesAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken)); + } + + [HttpPut("badges")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("新增或更新租户勋章")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertBadge( + UpsertTenantAdminBadgeDto request, + CancellationToken cancellationToken) + { + return Ok(await engagementService.UpsertBadgeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpGet("badge-grants")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询勋章发放记录")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> GrantBadge( + GrantTenantAdminBadgeDto request, + CancellationToken cancellationToken) + { + return Ok(await engagementService.GrantBadgeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpGet("notifications")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询用户站内通知")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> GetNotifications( + [FromQuery] TenantAdminNotificationQueryDto query, + CancellationToken cancellationToken) + { + return Ok(await engagementService.GetNotificationsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken)); + } + + [HttpPut("notifications")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("新增或更新用户站内通知")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertNotification( + UpsertTenantAdminNotificationDto request, + CancellationToken cancellationToken) + { + return Ok(await engagementService.UpsertNotificationAsync(actorResolver.Resolve(), request.ToCommand(), + cancellationToken)); + } + + [HttpGet("feedbacks")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询用户反馈")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> UpdateFeedback( + UpdateTenantAdminFeedbackDto request, + CancellationToken cancellationToken) + { + return Ok(await engagementService.UpdateFeedbackAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantMemberAccessController.cs b/Tiku.Api/Controllers/TenantMemberAccessController.cs new file mode 100644 index 0000000..b077a1e --- /dev/null +++ b/Tiku.Api/Controllers/TenantMemberAccessController.cs @@ -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>(StatusCodes.Status200OK)] + public async Task>> GetMembers( + [FromQuery] TenantAdminMemberQueryDto query, + CancellationToken cancellationToken) + { + return Ok(await memberAccessService.GetMembersAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken)); + } + + [HttpPut("members")] + [Authorize(Policy = BackendPermissions.TenantStaffManage)] + [EndpointSummary("新增或更新租户成员")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertMember( + UpsertTenantAdminMemberDto request, + CancellationToken cancellationToken) + { + return Ok(await memberAccessService.UpsertMemberAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpPost("members/disable")] + [Authorize(Policy = BackendPermissions.TenantStaffManage)] + [EndpointSummary("停用租户成员并撤销会话")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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 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(); + } +} diff --git a/Tiku.Api/Controllers/TenantSiteSettingsController.cs b/Tiku.Api/Controllers/TenantSiteSettingsController.cs new file mode 100644 index 0000000..cca0177 --- /dev/null +++ b/Tiku.Api/Controllers/TenantSiteSettingsController.cs @@ -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>(StatusCodes.Status200OK)] + public async Task>> GetAuditLogs( + [FromQuery] TenantAdminAuditLogQueryDto query, + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.GetAuditLogsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken)); + } + + [HttpPut("branding")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("更新租户品牌信息")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertBranding( + UpsertTenantBrandingDto request, + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.UpsertBrandingAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpPut("settings")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("更新租户公开设置与功能开关")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertSettings( + UpsertTenantSettingsDto request, + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.UpsertSettingsAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpGet("theme-templates")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("查询可用租户主题模板")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> GetThemeTemplates( + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.GetThemeTemplatesAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpGet("theme")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("查询租户当前主题与草稿")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> GetTheme( + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.GetThemeAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpPost("theme/preview")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("生成租户主题草稿")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> PreviewTheme( + PreviewTenantThemeDto request, + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.PreviewThemeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpPost("theme/publish")] + [Authorize(Policy = BackendPermissions.TenantSettingsManage)] + [EndpointSummary("发布租户主题")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> PublishTheme( + PublishTenantThemeDto request, + CancellationToken cancellationToken) + { + return Ok(await siteSettingsService.PublishThemeAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantStudentController.cs b/Tiku.Api/Controllers/TenantStudentController.cs new file mode 100644 index 0000000..a747c3f --- /dev/null +++ b/Tiku.Api/Controllers/TenantStudentController.cs @@ -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(StatusCodes.Status200OK)] + public async Task> GetStudents( + [FromQuery] TenantAdminStudentQueryDto query, + CancellationToken cancellationToken) + { + return Ok(await studentService.GetStudentsAsync(actorResolver.Resolve(), query.ToFilter(), cancellationToken)); + } + + [HttpPut("students")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("新增或更新租户学生档案")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> UpsertStudent( + UpsertTenantAdminStudentDto request, + CancellationToken cancellationToken) + { + return Ok(await studentService.UpsertStudentAsync(actorResolver.Resolve(), request.ToCommand(), cancellationToken)); + } + + [HttpPost("students/status")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("更新租户学生状态")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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(StatusCodes.Status200OK)] + public async Task> PreviewStudentImport( + TenantAdminStudentImportDto request, + CancellationToken cancellationToken) + { + return Ok(await studentService.PreviewStudentImportAsync(actorResolver.Resolve(), request.ToCommand(), + cancellationToken)); + } + + [HttpPost("students/import")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("提交学生批量导入")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> 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(StatusCodes.Status200OK)] + public async Task> 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(StatusCodes.Status200OK)] + public async Task> BulkStudentStatus( + TenantAdminBulkStatusDto request, + CancellationToken cancellationToken) + { + return Ok(await studentService.BulkUpdateStudentStatusAsync(actorResolver.Resolve(), request.ToCommand(), + cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantStudentEngagementController.cs b/Tiku.Api/Controllers/TenantStudentEngagementController.cs new file mode 100644 index 0000000..7c6563b --- /dev/null +++ b/Tiku.Api/Controllers/TenantStudentEngagementController.cs @@ -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>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> UpsertStudentNote( + UpsertTenantAdminStudentNoteDto request, + CancellationToken cancellationToken) + { + return Ok(await studentEngagementService.UpsertStudentNoteAsync(actorResolver.Resolve(), request.ToCommand(), + cancellationToken)); + } + + [HttpGet("student-followups")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询学生跟进")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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>(StatusCodes.Status200OK)] + public async Task>> UpsertStudentFollowup( + UpsertTenantAdminStudentFollowupDto request, + CancellationToken cancellationToken) + { + return Ok(await studentEngagementService.UpsertStudentFollowupAsync(actorResolver.Resolve(), request.ToCommand(), + cancellationToken)); + } +} + diff --git a/Tiku.Api/Controllers/TenantSupervisionController.cs b/Tiku.Api/Controllers/TenantSupervisionController.cs new file mode 100644 index 0000000..f7b4737 --- /dev/null +++ b/Tiku.Api/Controllers/TenantSupervisionController.cs @@ -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>(StatusCodes.Status200OK)] + public async Task>> SupervisionRules( + CancellationToken cancellationToken) + { + return Ok(await supervisionService.GetSupervisionRulesAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpPut("students/supervision/rules")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("新增或更新学习督导规则")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public async Task>> 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(StatusCodes.Status200OK)] + public async Task> PreviewSupervision(CancellationToken cancellationToken) + { + return Ok(await supervisionService.PreviewSupervisionAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpPost("students/supervision/generate")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("批量生成学习督导跟进任务")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> 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(StatusCodes.Status200OK)] + public async Task> FollowupReport(CancellationToken cancellationToken) + { + return Ok(await supervisionService.GetFollowupReportAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpGet("feedbacks/report")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询反馈处理统计报表")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> FeedbackReport(CancellationToken cancellationToken) + { + return Ok(await supervisionService.GetFeedbackReportAsync(actorResolver.Resolve(), cancellationToken)); + } + + [HttpGet("points/risk-report")] + [Authorize(Policy = BackendPermissions.TenantStudentManage)] + [EndpointSummary("查询积分风险统计报表")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> PointRiskReport(CancellationToken cancellationToken) + { + return Ok(await supervisionService.GetPointRiskReportAsync(actorResolver.Resolve(), cancellationToken)); + } +} + diff --git a/Tiku.Application/TenantAdmin/ITenantAdminDirectService.cs b/Tiku.Application/TenantAdmin/TenantAdminServices.cs similarity index 95% rename from Tiku.Application/TenantAdmin/ITenantAdminDirectService.cs rename to Tiku.Application/TenantAdmin/TenantAdminServices.cs index 26a51a1..ded9820 100644 --- a/Tiku.Application/TenantAdmin/ITenantAdminDirectService.cs +++ b/Tiku.Application/TenantAdmin/TenantAdminServices.cs @@ -3,12 +3,15 @@ using Tiku.Application.Content; namespace Tiku.Application.TenantAdmin; -public interface ITenantAdminDirectService +public interface ITenantAdminDashboardService { Task GetOverviewAsync( TenantAdminActor actor, CancellationToken cancellationToken = default); +} +public interface ITenantClassService +{ Task GetClassesAsync( TenantAdminActor actor, TenantAdminClassFilter filter, @@ -38,7 +41,10 @@ public interface ITenantAdminDirectService TenantAdminActor actor, Guid classMemberId, CancellationToken cancellationToken = default); +} +public interface ITenantStudentService +{ Task GetStudentsAsync( TenantAdminActor actor, TenantAdminStudentFilter filter, @@ -73,7 +79,10 @@ public interface ITenantAdminDirectService TenantAdminActor actor, TenantAdminBulkStatusCommand command, CancellationToken cancellationToken = default); +} +public interface ITenantSupervisionService +{ Task> GetSupervisionRulesAsync( TenantAdminActor actor, CancellationToken cancellationToken = default); @@ -103,7 +112,10 @@ public interface ITenantAdminDirectService Task GetPointRiskReportAsync( TenantAdminActor actor, CancellationToken cancellationToken = default); +} +public interface ITenantStudentEngagementService +{ Task> GetStudentNotesAsync( TenantAdminActor actor, TenantAdminStudentActivityFilter filter, @@ -123,7 +135,10 @@ public interface ITenantAdminDirectService TenantAdminActor actor, UpsertTenantAdminStudentFollowupCommand command, CancellationToken cancellationToken = default); +} +public interface ITenantMemberAccessService +{ Task> GetMembersAsync( TenantAdminActor actor, TenantAdminMemberFilter filter, @@ -138,7 +153,10 @@ public interface ITenantAdminDirectService TenantAdminActor actor, Guid membershipId, CancellationToken cancellationToken = default); +} +public interface ITenantSiteSettingsService +{ Task> GetAuditLogsAsync( TenantAdminActor actor, TenantAdminAuditLogFilter filter, @@ -171,7 +189,10 @@ public interface ITenantAdminDirectService TenantAdminActor actor, PublishTenantThemeCommand command, CancellationToken cancellationToken = default); +} +public interface ITenantDomainProviderService +{ Task> GetDomainsAsync( TenantAdminActor actor, CancellationToken cancellationToken = default); @@ -189,7 +210,10 @@ public interface ITenantAdminDirectService TenantAdminActor actor, UpsertTenantIdentityProviderCommand command, CancellationToken cancellationToken = default); +} +public interface ITenantEngagementService +{ Task> GetBadgesAsync( TenantAdminActor actor, TenantAdminBadgeFilter filter, @@ -229,4 +253,4 @@ public interface ITenantAdminDirectService TenantAdminActor actor, UpdateTenantAdminFeedbackCommand command, CancellationToken cancellationToken = default); -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/Modules/TenantAdminModule.cs b/Tiku.Infrastructure/Modules/TenantAdminModule.cs index 5ae6264..1b3a1f7 100644 --- a/Tiku.Infrastructure/Modules/TenantAdminModule.cs +++ b/Tiku.Infrastructure/Modules/TenantAdminModule.cs @@ -12,7 +12,16 @@ internal static class TenantAdminModule { internal static IServiceCollection AddTenantAdminModule(this IServiceCollection services) { - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Tiku.Infrastructure/TenantAdmin/Classes/TenantAdminDirectService.Classes.cs b/Tiku.Infrastructure/TenantAdmin/Classes/TenantClassService.cs similarity index 98% rename from Tiku.Infrastructure/TenantAdmin/Classes/TenantAdminDirectService.Classes.cs rename to Tiku.Infrastructure/TenantAdmin/Classes/TenantClassService.cs index 48ff817..56cc325 100644 --- a/Tiku.Infrastructure/TenantAdmin/Classes/TenantAdminDirectService.Classes.cs +++ b/Tiku.Infrastructure/TenantAdmin/Classes/TenantClassService.cs @@ -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 GetClassesAsync( TenantAdminActor actor, @@ -261,4 +262,4 @@ public sealed partial class TenantAdminDirectService await dbContext.SaveChangesAsync(cancellationToken); return new ContentManagementResult(ToClassMemberItem(item, ToUserSummary(user))); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/Dashboard/TenantAdminDirectService.Dashboard.cs b/Tiku.Infrastructure/TenantAdmin/Dashboard/TenantAdminDashboardService.cs similarity index 96% rename from Tiku.Infrastructure/TenantAdmin/Dashboard/TenantAdminDirectService.Dashboard.cs rename to Tiku.Infrastructure/TenantAdmin/Dashboard/TenantAdminDashboardService.cs index 681ee81..7201039 100644 --- a/Tiku.Infrastructure/TenantAdmin/Dashboard/TenantAdminDirectService.Dashboard.cs +++ b/Tiku.Infrastructure/TenantAdmin/Dashboard/TenantAdminDashboardService.cs @@ -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 GetOverviewAsync( TenantAdminActor actor, @@ -98,4 +99,4 @@ public sealed partial class TenantAdminDirectService openReconciliationIssueCount, now); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantDomainProviderService.cs b/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantDomainProviderService.cs new file mode 100644 index 0000000..9515c4f --- /dev/null +++ b/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantDomainProviderService.cs @@ -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> 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(items); + } + + public async Task> 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(ToDomainItem(item)); + } + + public async Task> GetAuthProvidersAsync( + TenantAdminActor actor, + CancellationToken cancellationToken = default) + { + await RequireAllDataScopeAsync(actor, cancellationToken); + var items = await providerConfigService.GetProvidersAsync( + actor.TenantId, + TenantExternalProviderCapability.Identity, + cancellationToken: cancellationToken); + return new CatalogList(items.Select(ToAuthProviderItem).ToArray()); + } + + public async Task> 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(ToAuthProviderItem(item)); + } +} + diff --git a/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantAdminDirectService.DomainsAndEngagement.cs b/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantEngagementService.cs similarity index 81% rename from Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantAdminDirectService.DomainsAndEngagement.cs rename to Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantEngagementService.cs index 0ffcd0b..5b510c4 100644 --- a/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantAdminDirectService.DomainsAndEngagement.cs +++ b/Tiku.Infrastructure/TenantAdmin/DomainsAndEngagement/TenantEngagementService.cs @@ -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> 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(items); - } - - public async Task> 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(ToDomainItem(item)); - } - - public async Task> GetAuthProvidersAsync( - TenantAdminActor actor, - CancellationToken cancellationToken = default) - { - await RequireAllDataScopeAsync(actor, cancellationToken); - var items = await providerConfigService.GetProvidersAsync( - actor.TenantId, - TenantExternalProviderCapability.Identity, - cancellationToken: cancellationToken); - return new CatalogList(items.Select(ToAuthProviderItem).ToArray()); - } - - public async Task> 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(ToAuthProviderItem(item)); - } - public async Task> GetBadgesAsync( TenantAdminActor actor, TenantAdminBadgeFilter filter, @@ -455,4 +364,5 @@ public sealed partial class TenantAdminDirectService : null; return new ContentManagementResult(ToFeedbackItem(report, user)); } -} \ No newline at end of file +} + diff --git a/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminDirectService.DataAccess.cs b/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.DataAccess.cs similarity index 92% rename from Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminDirectService.DataAccess.cs rename to Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.DataAccess.cs index 26a7dae..0d39498 100644 --- a/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminDirectService.DataAccess.cs +++ b/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.DataAccess.cs @@ -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 ResolveUserAsync(UserLookupCommand command, string primaryRole, + protected async Task ResolveUserAsync(UserLookupCommand command, string primaryRole, CancellationToken cancellationToken) { User? user = null; @@ -61,7 +61,7 @@ public sealed partial class TenantAdminDirectService return user; } - private async Task EnsureMembershipAsync( + protected async Task 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 IsUserCountedForMetricAsync( + protected Task 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 EnsureStudentProfileAsync( + protected async Task EnsureStudentProfileAsync( Guid tenantId, Guid userId, Guid? regionId, @@ -169,7 +169,7 @@ public sealed partial class TenantAdminDirectService return profile; } - private async Task> BuildStudentImportPreviewAsync( + protected async Task> BuildStudentImportPreviewAsync( TenantAdminActor actor, CurrentDataScope scope, TenantAdminStudentImportCommand command, @@ -215,7 +215,7 @@ public sealed partial class TenantAdminDirectService return items; } - private async Task UpsertClassMemberCoreAsync( + protected async Task UpsertClassMemberCoreAsync( TenantAdminActor actor, Guid classId, Guid userId, @@ -249,7 +249,7 @@ public sealed partial class TenantAdminDirectService return item; } - private async Task> GetSupervisionRulesCoreAsync( + protected async Task> GetSupervisionRulesCoreAsync( Guid tenantId, CancellationToken cancellationToken) { @@ -264,7 +264,7 @@ public sealed partial class TenantAdminDirectService return JsonSerializer.Deserialize(rulesElement.GetRawText()) ?? []; } - private async Task SaveSupervisionRulesCoreAsync( + protected async Task SaveSupervisionRulesCoreAsync( Guid tenantId, IReadOnlyCollection rules, CancellationToken cancellationToken) @@ -284,7 +284,7 @@ public sealed partial class TenantAdminDirectService settings.AdminFeatureFlags = JsonSerializer.SerializeToElement(existing); } - private async Task> BuildSupervisionRiskStudentsAsync( + protected async Task> 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 RequireDataScopeAsync( + protected async Task 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( + protected async Task AssertReferenceAsync( 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 ResolveTenantEntityAsync( + protected async Task ResolveTenantEntityAsync( DbSet 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); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminDirectService.MappingAndValidation.cs b/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.MappingAndValidation.cs similarity index 86% rename from Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminDirectService.MappingAndValidation.cs rename to Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.MappingAndValidation.cs index 6a3c269..c26adbe 100644 --- a/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminDirectService.MappingAndValidation.cs +++ b/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.MappingAndValidation.cs @@ -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(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(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(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(string? value, TEnum fallback, string code) + protected static TEnum ParseEnum(string? value, TEnum fallback, string code) where TEnum : struct, Enum { return string.IsNullOrWhiteSpace(value) ? fallback : ParseEnum(value, code); } - private static TEnum ParseEnum(string value, string code) + protected static TEnum ParseEnum(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); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.cs b/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.cs new file mode 100644 index 0000000..de36a97 --- /dev/null +++ b/Tiku.Infrastructure/TenantAdmin/Foundation/TenantAdminFoundation.cs @@ -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; +} diff --git a/Tiku.Infrastructure/TenantAdmin/MembersAndAccess/TenantAdminDirectService.MembersAndAccess.cs b/Tiku.Infrastructure/TenantAdmin/MembersAndAccess/TenantMemberAccessService.cs similarity index 98% rename from Tiku.Infrastructure/TenantAdmin/MembersAndAccess/TenantAdminDirectService.MembersAndAccess.cs rename to Tiku.Infrastructure/TenantAdmin/MembersAndAccess/TenantMemberAccessService.cs index fd08ef3..cd8e1ed 100644 --- a/Tiku.Infrastructure/TenantAdmin/MembersAndAccess/TenantAdminDirectService.MembersAndAccess.cs +++ b/Tiku.Infrastructure/TenantAdmin/MembersAndAccess/TenantMemberAccessService.cs @@ -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> GetMembersAsync( TenantAdminActor actor, @@ -195,4 +196,4 @@ public sealed partial class TenantAdminDirectService .SingleAsync(item => item.Id == membership.UserId, cancellationToken); return new ContentManagementResult(ToMemberItem(membership, user)); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantAdminDirectService.SiteSettings.cs b/Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantSiteSettingsService.cs similarity index 98% rename from Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantAdminDirectService.SiteSettings.cs rename to Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantSiteSettingsService.cs index c2fe845..e11e413 100644 --- a/Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantAdminDirectService.SiteSettings.cs +++ b/Tiku.Infrastructure/TenantAdmin/SiteSettings/TenantSiteSettingsService.cs @@ -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> GetAuditLogsAsync( TenantAdminActor actor, @@ -242,4 +243,4 @@ public sealed partial class TenantAdminDirectService await dbContext.SaveChangesAsync(cancellationToken); return new ContentManagementResult(ToThemeItem(item)); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/StudentEngagement/TenantAdminDirectService.StudentEngagement.cs b/Tiku.Infrastructure/TenantAdmin/StudentEngagement/TenantStudentEngagementService.cs similarity index 98% rename from Tiku.Infrastructure/TenantAdmin/StudentEngagement/TenantAdminDirectService.StudentEngagement.cs rename to Tiku.Infrastructure/TenantAdmin/StudentEngagement/TenantStudentEngagementService.cs index 9400469..741ae13 100644 --- a/Tiku.Infrastructure/TenantAdmin/StudentEngagement/TenantAdminDirectService.StudentEngagement.cs +++ b/Tiku.Infrastructure/TenantAdmin/StudentEngagement/TenantStudentEngagementService.cs @@ -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> GetStudentNotesAsync( TenantAdminActor actor, @@ -208,4 +209,4 @@ public sealed partial class TenantAdminDirectService await dbContext.SaveChangesAsync(cancellationToken); return new ContentManagementResult(ToFollowupItem(item)); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/Students/TenantAdminDirectService.Students.cs b/Tiku.Infrastructure/TenantAdmin/Students/TenantStudentService.cs similarity index 99% rename from Tiku.Infrastructure/TenantAdmin/Students/TenantAdminDirectService.Students.cs rename to Tiku.Infrastructure/TenantAdmin/Students/TenantStudentService.cs index 2244df4..3c5d037 100644 --- a/Tiku.Infrastructure/TenantAdmin/Students/TenantAdminDirectService.Students.cs +++ b/Tiku.Infrastructure/TenantAdmin/Students/TenantStudentService.cs @@ -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 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); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/Supervision/TenantAdminDirectService.Supervision.cs b/Tiku.Infrastructure/TenantAdmin/Supervision/TenantSupervisionService.cs similarity index 98% rename from Tiku.Infrastructure/TenantAdmin/Supervision/TenantAdminDirectService.Supervision.cs rename to Tiku.Infrastructure/TenantAdmin/Supervision/TenantSupervisionService.cs index ad4ce42..a8690eb 100644 --- a/Tiku.Infrastructure/TenantAdmin/Supervision/TenantAdminDirectService.Supervision.cs +++ b/Tiku.Infrastructure/TenantAdmin/Supervision/TenantSupervisionService.cs @@ -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> GetSupervisionRulesAsync( TenantAdminActor actor, @@ -173,4 +174,4 @@ public sealed partial class TenantAdminDirectService cancellationToken); return new TenantPointRiskReport(negativeScoreUsers, highClaimUsers, cancelledExchangeOrders); } -} \ No newline at end of file +} diff --git a/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs b/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs deleted file mode 100644 index c15ce60..0000000 --- a/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs +++ /dev/null @@ -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 -{ -} \ No newline at end of file diff --git a/Tiku.IntegrationTests/ArchitectureBoundaryTests.cs b/Tiku.IntegrationTests/ArchitectureBoundaryTests.cs index 9ccae4d..71c240e 100644 --- a/Tiku.IntegrationTests/ArchitectureBoundaryTests.cs +++ b/Tiku.IntegrationTests/ArchitectureBoundaryTests.cs @@ -35,7 +35,6 @@ public sealed class ArchitectureBoundaryTests var root = FindRepositoryRoot(); var legacyLineBudgets = new Dictionary(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,