feat: enforce tenant isolation and shared question bank

This commit is contained in:
2026-07-27 16:59:12 +08:00
parent 28e9a9fa41
commit db4c7b4496
137 changed files with 6402 additions and 112274 deletions

View File

@@ -10,11 +10,11 @@ namespace Tiku.Api.Contracts;
public sealed class PasswordLoginDto
{
/// <summary>
/// 租户 ID。登录阶段允许客户端指定租户登录后的业务接口以 JWT/session 解析出的当前租户为准
/// 平台控制域名登录时使用的租户代码;自定义域名登录可省略
/// </summary>
[Required]
[Description("租户 ID。登录阶段允许客户端指定租户登录后的业务接口以 JWT/session 解析出的当前租户为准。")]
public Guid TenantId { get; set; }
[StringLength(100)]
[Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")]
public string? TenantCode { get; set; }
/// <summary>
/// 手机号,建议前端提交规范化后的中国大陆手机号。
@@ -39,11 +39,11 @@ public sealed class PasswordLoginDto
public sealed class SmsLoginDto
{
/// <summary>
/// 租户 ID
/// 平台控制域名登录时使用的租户代码;自定义域名登录可省略
/// </summary>
[Required]
[Description("租户 ID。")]
public Guid TenantId { get; set; }
[StringLength(100)]
[Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")]
public string? TenantCode { get; set; }
/// <summary>
/// 中国大陆手机号。
@@ -68,11 +68,11 @@ public sealed class SmsLoginDto
public sealed class OAuthCodeDto
{
/// <summary>
/// 租户 ID
/// 平台控制域名登录时使用的租户代码;自定义域名登录可省略
/// </summary>
[Required]
[Description("租户 ID。")]
public Guid TenantId { get; set; }
[StringLength(100)]
[Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")]
public string? TenantCode { get; set; }
/// <summary>
/// OAuth 平台返回的一次性授权 code。

View File

@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Tiku.Application.Content;
using Tiku.Application.QuestionBanks;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
namespace Tiku.Api.Contracts;
@@ -252,6 +254,9 @@ public sealed class CollectionQuestionDto
[Required]
public Guid QuestionId { get; set; }
[Required]
public QuestionSource Source { get; set; } = QuestionSource.Tenant;
[StringLength(100)]
public string? SectionKey { get; set; }
@@ -265,7 +270,13 @@ public sealed class CollectionQuestionDto
public CollectionQuestionCommand ToCommand()
{
return new CollectionQuestionCommand(QuestionId, SectionKey, Order, Score, Required, Metadata);
return new CollectionQuestionCommand(
new QuestionLocator(Source, QuestionId),
SectionKey,
Order,
Score,
Required,
Metadata);
}
}

View File

@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Tiku.Application.Learning;
using Tiku.Application.QuestionBanks;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
namespace Tiku.Api.Contracts;
@@ -107,9 +109,7 @@ public sealed class SubmitPracticeSessionDto
public sealed class SubmitAnswerDto
{
[Required]
public Guid QuestionId { get; set; }
public Guid? PracticeSessionId { get; set; }
public Guid SessionQuestionId { get; set; }
public IReadOnlyCollection<string>? SelectedOptions { get; set; }
@@ -121,8 +121,7 @@ public sealed class SubmitAnswerDto
public SubmitAnswerCommand ToCommand()
{
return new SubmitAnswerCommand(
QuestionId,
PracticeSessionId,
SessionQuestionId,
SelectedOptions,
AnswerText,
SelfJudgedCorrect);
@@ -134,11 +133,14 @@ public sealed class QuestionActionDto
[Required]
public Guid QuestionId { get; set; }
[Required]
public QuestionSource Source { get; set; } = QuestionSource.Tenant;
public bool? Favorite { get; set; }
public QuestionActionCommand ToCommand()
{
return new QuestionActionCommand(QuestionId, Favorite);
return new QuestionActionCommand(new QuestionLocator(Source, QuestionId), Favorite);
}
}

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Tiku.Application.QuestionBanks;
using Tiku.Domain.Content;
namespace Tiku.Api.Contracts;
@@ -24,6 +25,8 @@ public sealed class QuestionBankQueryDto
public Guid? CollectionId { get; set; }
public QuestionSource? Source { get; set; }
[StringLength(50)]
public string? Type { get; set; }
@@ -54,7 +57,8 @@ public sealed class QuestionBankQueryDto
ParseQuestionIds(),
Type,
Keyword,
Limit);
Limit,
Source);
}
private Guid[] ParseQuestionIds()

View File

@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Tiku.Application.Catalog;
using Tiku.Domain.Catalog;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
namespace Tiku.Api.Contracts;
public sealed class CreateTaxonomyNodeDto
{
public Guid? ParentId { get; set; }
public QuestionSource? ParentSource { get; set; }
[Required]
public TaxonomyNodeType NodeType { get; set; }
[Required, StringLength(100)]
public string Code { get; set; } = string.Empty;
[Required, StringLength(300)]
public string Name { get; set; } = string.Empty;
public int SortOrder { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public CreateTaxonomyNodeCommand ToCommand() => new(
ParentId,
ParentSource,
NodeType,
Code,
Name,
SortOrder,
Metadata);
}

View File

@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Tiku.Application.Tenancy;
using Tiku.Domain.Common;
namespace Tiku.Api.Contracts;
public sealed class SaveTenantFrontendConfigDraftDto
{
public JsonElement Branding { get; set; } = JsonDefaults.Object();
public JsonElement Theme { get; set; } = JsonDefaults.Object();
public JsonElement Features { get; set; } = JsonDefaults.Object();
public JsonElement Navigation { get; set; } = JsonDefaults.Array();
public JsonElement HomeModules { get; set; } = JsonDefaults.Array();
public TenantFrontendConfigDraft ToDraft() => new(
Branding,
Theme,
Features,
Navigation,
HomeModules);
}
public sealed class PublishTenantFrontendConfigDto
{
[Range(1, int.MaxValue)]
public int ExpectedVersion { get; set; }
}