feat: add core persistence model

This commit is contained in:
xiong
2026-07-26 04:52:56 +08:00
parent f5109de879
commit 5dfb68d8cc
33 changed files with 6989 additions and 31 deletions

View File

@@ -0,0 +1,126 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Catalog;
public sealed class Region : AuditableTenantEntity
{
public string? LegacyId { get; set; }
public string Name { get; set; } = string.Empty;
public string? Code { get; set; }
public string? ShortName { get; set; }
public string? FullName { get; set; }
public string? Icon { get; set; }
public string? Pinyin { get; set; }
public int SortOrder { get; set; }
public bool IsHot { get; set; }
public bool IsActive { get; set; } = true;
public JsonElement Config { get; set; } = JsonDefaults.Object();
}
public sealed class RegionModule : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public string? LegacyId { get; set; }
public string Name { get; set; } = string.Empty;
public string? Type { get; set; }
public string? Icon { get; set; }
public string? Color { get; set; }
public string? TextColor { get; set; }
public string? Description { get; set; }
public string? Route { get; set; }
public int SortOrder { get; set; }
public bool IsPrimarySchoolModule { get; set; }
public bool IsActive { get; set; } = true;
}
public sealed class ModuleNode : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public Guid? ModuleId { get; set; }
public Guid? ParentId { get; set; }
public string? LegacyId { get; set; }
public string? LegacyParentId { get; set; }
public string? LegacyModuleId { get; set; }
public ModuleNodeType Type { get; set; } = ModuleNodeType.Custom;
public string Name { get; set; } = string.Empty;
public string? Path { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public enum ModuleNodeType
{
Category,
Subject,
Chapter,
Paper,
School,
Major,
Custom
}
public sealed class School : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public Guid? ModuleId { get; set; }
public string? LegacyId { get; set; }
public string Name { get; set; } = string.Empty;
public string? ProfessionalExamDate { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class Major : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public Guid? SchoolId { get; set; }
public string? LegacyId { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public string? StudyTips { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
}
public sealed class Subject : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public Guid? ModuleId { get; set; }
public Guid? SchoolId { get; set; }
public Guid? MajorId { get; set; }
public Guid? NodeId { get; set; }
public string? LegacyId { get; set; }
public string Name { get; set; } = string.Empty;
public SubjectType? Type { get; set; }
public JsonElement MajorLegacyIds { get; set; } = JsonDefaults.Array();
public string? Icon { get; set; }
public string? Description { get; set; }
public JsonElement Stats { get; set; } = JsonDefaults.Object();
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
}
public enum SubjectType
{
Cultural,
Professional
}
public sealed class Category : AuditableTenantEntity
{
public Guid? SubjectId { get; set; }
public Guid? NodeId { get; set; }
public string? LegacyId { get; set; }
public string Name { get; set; } = string.Empty;
public CategoryType? CategoryType { get; set; }
public int SortOrder { get; set; }
public int? SvipQuestionLimit { get; set; }
public bool IsActive { get; set; } = true;
}
public enum CategoryType
{
Chapter,
Paper
}

View File

@@ -1,6 +0,0 @@
namespace Tiku.Domain;
public class Class1
{
}

View File

@@ -0,0 +1,46 @@
using System.Text.Json;
namespace Tiku.Domain.Common;
public abstract class Entity
{
public Guid Id { get; set; } = Guid.NewGuid();
}
public interface IHasTimestamps
{
DateTimeOffset CreatedAt { get; set; }
DateTimeOffset UpdatedAt { get; set; }
}
public abstract class AuditableEntity : Entity, IHasTimestamps
{
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
}
public abstract class TenantEntity : Entity
{
public Guid TenantId { get; set; }
}
public abstract class AuditableTenantEntity : TenantEntity, IHasTimestamps
{
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
}
public static class JsonDefaults
{
public static JsonElement Object()
{
using var document = JsonDocument.Parse("{}");
return document.RootElement.Clone();
}
public static JsonElement Array()
{
using var document = JsonDocument.Parse("[]");
return document.RootElement.Clone();
}
}

View File

@@ -0,0 +1,20 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Identity;
public sealed class StudentProfile : AuditableTenantEntity
{
public Guid UserId { get; set; }
public string? LegacyUserId { get; set; }
public Guid? RegionId { get; set; }
public Guid? SelectedSchoolId { get; set; }
public Guid? SelectedMajorId { get; set; }
public int QuestionsAnsweredToday { get; set; }
public int MasteredWordsCount { get; set; }
public DateOnly? LastCheckInDate { get; set; }
public JsonElement Stats { get; set; } = JsonDefaults.Object();
public JsonElement Progress { get; set; } = JsonDefaults.Object();
public JsonElement ModuleSelections { get; set; } = JsonDefaults.Object();
public JsonElement RecentActivities { get; set; } = JsonDefaults.Array();
}

View File

@@ -0,0 +1,20 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Identity;
public sealed class User : AuditableEntity
{
public string? LegacyId { get; set; }
public string? Username { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? Name { get; set; }
public string? AvatarUrl { get; set; }
public string PrimaryRole { get; set; } = "student";
public int Score { get; set; }
public DateTimeOffset? LastSeenAt { get; set; }
public string? LegacyPasswordHash { get; set; }
public bool PasswordMigrationRequired { get; set; }
public JsonElement RawProfile { get; set; } = JsonDefaults.Object();
}

View File

@@ -0,0 +1,16 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Identity;
public sealed class UserIdentity : AuditableEntity
{
public Guid UserId { get; set; }
public string Provider { get; set; } = string.Empty;
public string ProviderSubject { get; set; } = string.Empty;
public string? UnionId { get; set; }
public string? OpenId { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public JsonElement SecretPayload { get; set; } = JsonDefaults.Object();
}

View File

@@ -0,0 +1,64 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Learning;
public sealed class PracticeSession : TenantEntity
{
public Guid UserId { get; set; }
public string Mode { get; set; } = "chapter";
public string? TargetType { get; set; }
public Guid? TargetId { get; set; }
public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? FinishedAt { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public sealed class AnswerRecord : TenantEntity
{
public Guid UserId { get; set; }
public Guid? QuestionId { get; set; }
public Guid? QuestionVersionId { get; set; }
public Guid? PracticeSessionId { get; set; }
public string? LegacyId { get; set; }
public string? LegacyQuestionId { get; set; }
public string? LegacyCategoryId { get; set; }
public JsonElement SelectedOptions { get; set; } = JsonDefaults.Array();
public string? AnswerText { get; set; }
public bool? IsCorrect { get; set; }
public DateTimeOffset AnsweredAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
public sealed class FavoriteQuestion
{
public Guid TenantId { get; set; }
public Guid UserId { get; set; }
public Guid QuestionId { get; set; }
public string Source { get; set; } = "imported";
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}
public sealed class WrongQuestion
{
public Guid TenantId { get; set; }
public Guid UserId { get; set; }
public Guid QuestionId { get; set; }
public int WrongCount { get; set; } = 1;
public DateTimeOffset LastWrongAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? ResolvedAt { get; set; }
}
public sealed class RecentPractice : AuditableTenantEntity
{
public Guid? UserId { get; set; }
public string? LegacyId { get; set; }
public string? PracticeType { get; set; }
public string? TargetLegacyId { get; set; }
public string? TargetName { get; set; }
public int Progress { get; set; }
public string? Color { get; set; }
public DateTimeOffset? LastAccessAt { get; set; }
public DateTimeOffset? LastPracticeAt { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}

View File

@@ -0,0 +1,71 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.QuestionBanks;
public sealed class QuestionBank : AuditableTenantEntity
{
public Guid? RegionId { get; set; }
public string Name { get; set; } = string.Empty;
public QuestionBankScope SourceScope { get; set; } = QuestionBankScope.Tenant;
public QuestionBankStatus Status { get; set; } = QuestionBankStatus.Active;
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public enum QuestionBankScope
{
Platform,
Tenant
}
public enum QuestionBankStatus
{
Active,
Archived
}
public sealed class Question : AuditableTenantEntity
{
public Guid? QuestionBankId { get; set; }
public Guid? SubjectId { get; set; }
public Guid? CategoryId { get; set; }
public Guid? NodeId { get; set; }
public string? LegacyId { get; set; }
public string? LegacySubjectId { get; set; }
public string? LegacyCategoryId { get; set; }
public string? LegacyNodeId { get; set; }
public string Type { get; set; } = "choice";
public string? TypeLabel { get; set; }
public int? Difficulty { get; set; }
public JsonElement Tags { get; set; } = JsonDefaults.Array();
public string? MediaUrl { get; set; }
public bool HasVideoExplanation { get; set; }
public QuestionStatus Status { get; set; } = QuestionStatus.Published;
public Guid? CurrentVersionId { get; set; }
}
public enum QuestionStatus
{
Draft,
Published,
Archived
}
public sealed class QuestionVersion : Entity
{
public Guid TenantId { get; set; }
public Guid QuestionId { get; set; }
public int VersionNo { get; set; } = 1;
public string? Content { get; set; }
public JsonElement Options { get; set; } = JsonDefaults.Array();
public int? CorrectOptionIndex { get; set; }
public JsonElement CorrectOptionIndices { get; set; } = JsonDefaults.Array();
public string? AnswerText { get; set; }
public string? Explanation { get; set; }
public JsonElement SubQuestions { get; set; } = JsonDefaults.Array();
public string? CodeLang { get; set; }
public string? CodeTemplate { get; set; }
public string? SourceHash { get; set; }
public Guid? CreatedBy { get; set; }
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}

View File

@@ -0,0 +1,40 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Tenancy;
public sealed class Tenant : AuditableEntity
{
public string Slug { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? LegalName { get; set; }
public TenantStatus Status { get; set; } = TenantStatus.Active;
public TenantMode Mode { get; set; } = TenantMode.Saas;
public BillingStatus BillingStatus { get; set; } = BillingStatus.Trial;
public Guid? OwnerUserId { get; set; }
public string? LegacyId { get; set; }
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
}
public enum TenantStatus
{
Draft,
Active,
Suspended,
Archived
}
public enum TenantMode
{
PlatformOwned,
Saas,
Dedicated
}
public enum BillingStatus
{
Trial,
Active,
PastDue,
Cancelled
}

View File

@@ -0,0 +1,32 @@
using System.Text.Json;
using Tiku.Domain.Common;
namespace Tiku.Domain.Tenancy;
public sealed class TenantMembership : AuditableTenantEntity
{
public Guid UserId { get; set; }
public TenantRole Role { get; set; } = TenantRole.Student;
public MembershipStatus Status { get; set; } = MembershipStatus.Active;
public JsonElement Permissions { get; set; } = JsonDefaults.Object();
public string? LegacyRole { get; set; }
}
public enum TenantRole
{
PlatformAdmin,
TenantOwner,
TenantAdmin,
TenantOperator,
Teacher,
Sales,
Agent,
Student
}
public enum MembershipStatus
{
Active,
Invited,
Disabled
}