feat: add catalog readonly endpoints

This commit is contained in:
xiong
2026-07-26 14:02:04 +08:00
parent 41a8a5ff81
commit d5379affba
10 changed files with 962 additions and 53 deletions

View File

@@ -0,0 +1,145 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Tiku.Api.Contracts;
using Tiku.Application.Catalog;
using Tiku.Application.Security;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/catalog")]
public sealed class CatalogController(
ICatalogQueryService catalogQueryService,
ICurrentTenant currentTenant,
TikuDbContext dbContext) : ControllerBase
{
[HttpGet("regions")]
[EndpointSummary("查询可用地区")]
[ProducesResponseType<CatalogList<RegionCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<RegionCatalogItem>>> GetRegions(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetRegionsAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
[HttpGet("region-modules")]
[EndpointSummary("查询地区功能模块")]
[ProducesResponseType<CatalogList<RegionModuleCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<RegionModuleCatalogItem>>> GetRegionModules(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetRegionModulesAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
[HttpGet("module-nodes")]
[EndpointSummary("查询模块导航节点")]
[ProducesResponseType<CatalogList<ModuleNodeCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<ModuleNodeCatalogItem>>> GetModuleNodes(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetModuleNodesAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
[HttpGet("schools")]
[EndpointSummary("查询院校目录")]
[ProducesResponseType<CatalogList<SchoolCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<SchoolCatalogItem>>> GetSchools(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetSchoolsAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
[HttpGet("majors")]
[EndpointSummary("查询专业目录")]
[ProducesResponseType<CatalogList<MajorCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<MajorCatalogItem>>> GetMajors(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetMajorsAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
[HttpGet("subjects")]
[EndpointSummary("查询科目目录")]
[ProducesResponseType<CatalogList<SubjectCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<SubjectCatalogItem>>> GetSubjects(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetSubjectsAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
[HttpGet("categories")]
[HttpGet("question-categories")]
[EndpointSummary("查询题目分类")]
[ProducesResponseType<CatalogList<CategoryCatalogItem>>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CatalogList<CategoryCatalogItem>>> GetCategories(
[FromQuery] CatalogQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await catalogQueryService.GetCategoriesAsync(
query.ToFilter(await ResolveTenantIdAsync(query, cancellationToken)),
cancellationToken));
}
private async Task<Guid> ResolveTenantIdAsync(
CatalogQueryDto query,
CancellationToken cancellationToken)
{
if (currentTenant.TenantId.HasValue)
{
return currentTenant.TenantId.Value;
}
var tenantCode = query.TenantCode ?? Request.Headers["x-tenant-code"].FirstOrDefault();
if (string.IsNullOrWhiteSpace(tenantCode))
{
throw new TenantNotFoundException();
}
var tenantId = await dbContext.Tenants
.Where(tenant =>
tenant.Slug == tenantCode.Trim() &&
tenant.Status == TenantStatus.Active)
.Select(tenant => (Guid?)tenant.Id)
.SingleOrDefaultAsync(cancellationToken);
return tenantId ?? throw new TenantNotFoundException();
}
}
public sealed class TenantNotFoundException : Exception
{
public TenantNotFoundException()
: base("Tenant was not found.")
{
}
}