feat: add student video and profile experience

This commit is contained in:
2026-07-28 15:23:42 +08:00
parent 5e993298e7
commit 7a3cf09a99
31 changed files with 19821 additions and 170 deletions

View File

@@ -0,0 +1,80 @@
using System.Text.Json;
using Tiku.Application.Catalog;
using Tiku.Domain.Content;
namespace Tiku.Application.Assets;
public sealed record VideoPlaybackActor(Guid TenantId, Guid UserId);
public sealed record VideoSearchQuery(
string? Keyword = null,
Guid? SubjectId = null,
int? Limit = null);
public sealed record QuestionVideoQuery(
Guid? QuestionId = null,
IReadOnlyCollection<Guid>? QuestionIds = null,
int? Limit = null);
public sealed record VideoPlayCommand(Guid VideoId, Guid? QuestionId = null);
public sealed record VideoProgressCommand(
Guid VideoId,
Guid? QuestionId,
int PositionSeconds,
int? DurationSeconds,
int? WatchedSeconds,
bool? IsCompleted,
JsonElement Metadata);
public sealed record VideoPlaybackItem(
Guid VideoId,
Guid? QuestionId,
string Title,
string? Description,
string? PlayUrl,
string? ThumbnailUrl,
int? DurationSeconds,
VideoProgressItem? Progress,
JsonElement Metadata);
public sealed record VideoProgressItem(
Guid Id,
Guid VideoId,
Guid? QuestionId,
int PositionSeconds,
int? DurationSeconds,
int WatchedSeconds,
bool IsCompleted,
DateTimeOffset? CompletedAt,
DateTimeOffset LastPlayedAt,
int PlayCount,
JsonElement Metadata);
public interface IVideoPlaybackService
{
Task<CatalogList<VideoExplanationCatalogItem>> SearchAsync(
VideoPlaybackActor actor,
VideoSearchQuery query,
CancellationToken cancellationToken = default);
Task<VideoPlaybackItem> PlayAsync(
VideoPlaybackActor actor,
VideoPlayCommand command,
CancellationToken cancellationToken = default);
Task<VideoProgressItem> ReportProgressAsync(
VideoPlaybackActor actor,
VideoProgressCommand command,
CancellationToken cancellationToken = default);
Task<CatalogList<QuestionVideoCatalogItem>> GetQuestionVideosAsync(
VideoPlaybackActor actor,
QuestionVideoQuery query,
CancellationToken cancellationToken = default);
}
public sealed class VideoPlaybackException(string message, string code) : Exception(message)
{
public string Code { get; } = code;
}

View File

@@ -346,6 +346,7 @@ public interface IDirectContentService
Task<ContentManagementResult<OperationContentItem>> UpsertOperationContentAsync(DirectContentActor actor, string kind, OperationContentCommand command, CancellationToken cancellationToken = default);
Task<SimpleImportResult> PreviewImportAsync(DirectContentActor actor, SimpleImportCommand command, CancellationToken cancellationToken = default);
Task<SimpleImportResult> ExecuteImportAsync(DirectContentActor actor, SimpleImportCommand command, CancellationToken cancellationToken = default);
Task<ContentImportJobDetail> GetImportJobAsync(DirectContentActor actor, Guid jobId, CancellationToken cancellationToken = default);
Task<CatalogList<ContentImportIssueModel>> GetImportIssuesAsync(DirectContentActor actor, Guid jobId, CancellationToken cancellationToken = default);
Task<ImportPostCheckResult> RunImportPostCheckAsync(DirectContentActor actor, Guid jobId, CancellationToken cancellationToken = default);
Task<ImportPostCheckResult> GetImportPostCheckAsync(DirectContentActor actor, Guid jobId, CancellationToken cancellationToken = default);

View File

@@ -6,6 +6,12 @@ public sealed record ProfileActor(Guid TenantId, Guid UserId);
public sealed record ProfileQuery(int? RecentLimit = null, int? Limit = null);
public sealed record ProfileScoreEventQuery(
int? Limit = null,
string? SourceType = null,
DateTimeOffset? From = null,
DateTimeOffset? To = null);
public sealed record UpdateProfileCommand(
string? Name,
string? AvatarPreset,
@@ -144,6 +150,26 @@ public sealed record SubmitFeedbackCommand(
JsonElement Attachments,
JsonElement Metadata);
public sealed record CheckInResult(
DateOnly CheckInDate,
bool AlreadyCheckedIn,
int Points,
int BalanceAfter,
Guid? ClaimId,
Guid? ScoreEventId);
public sealed record ProfileScoreEventItem(
Guid Id,
string EventType,
int Points,
int BalanceAfter,
string? SourceType,
Guid? SourceId,
JsonElement Metadata,
DateTimeOffset CreatedAt);
public sealed record ProfileScoreEventList(IReadOnlyCollection<ProfileScoreEventItem> Items);
public interface IProfileService
{
Task<StudentProfileItem> GetMeAsync(ProfileActor actor, ProfileQuery query, CancellationToken cancellationToken = default);
@@ -154,4 +180,6 @@ public interface IProfileService
Task<BadgeList> GetBadgesAsync(ProfileActor actor, BadgeQuery query, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<FeedbackItem>> GetFeedbacksAsync(ProfileActor actor, FeedbackQuery query, CancellationToken cancellationToken = default);
Task<FeedbackItem> SubmitFeedbackAsync(ProfileActor actor, SubmitFeedbackCommand command, CancellationToken cancellationToken = default);
Task<CheckInResult> CheckInAsync(ProfileActor actor, CancellationToken cancellationToken = default);
Task<ProfileScoreEventList> GetScoreEventsAsync(ProfileActor actor, ProfileScoreEventQuery query, CancellationToken cancellationToken = default);
}