♻️ Centralized data models (wip)

This commit is contained in:
2025-09-27 14:09:28 +08:00
parent 51b6f7309e
commit e70d8371f8
206 changed files with 1352 additions and 2128 deletions

View File

@@ -1,67 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using NodaTime;
namespace DysonNetwork.Sphere.Poll;
public class Poll : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public List<PollQuestion> Questions { get; set; } = new();
[MaxLength(1024)] public string? Title { get; set; }
[MaxLength(4096)] public string? Description { get; set; }
public Instant? EndedAt { get; set; }
public bool IsAnonymous { get; set; }
public Guid PublisherId { get; set; }
[JsonIgnore] public Publisher.Publisher? Publisher { get; set; }
}
public enum PollQuestionType
{
SingleChoice,
MultipleChoice,
YesNo,
Rating,
FreeText
}
public class PollQuestion : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public PollQuestionType Type { get; set; }
[Column(TypeName = "jsonb")] public List<PollOption>? Options { get; set; }
[MaxLength(1024)] public string Title { get; set; } = null!;
[MaxLength(4096)] public string? Description { get; set; }
public int Order { get; set; } = 0;
public bool IsRequired { get; set; }
public Guid PollId { get; set; }
[JsonIgnore] public Poll Poll { get; set; } = null!;
}
public class PollOption
{
public Guid Id { get; set; } = Guid.NewGuid();
[Required][MaxLength(1024)] public string Label { get; set; } = null!;
[MaxLength(4096)] public string? Description { get; set; }
public int Order { get; set; } = 0;
}
public class PollAnswer : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[Column(TypeName = "jsonb")] public Dictionary<string, JsonElement> Answer { get; set; } = null!;
public Guid AccountId { get; set; }
public Guid PollId { get; set; }
[JsonIgnore] public Poll? Poll { get; set; }
[NotMapped] public AccountReference? Account { get; set; }
}

View File

@@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using Microsoft.AspNetCore.Authorization;
@@ -46,7 +47,7 @@ public class PollController(
[HttpPost("{id:guid}/answer")]
[Authorize]
public async Task<ActionResult<PollAnswer>> AnswerPoll(Guid id, [FromBody] PollAnswerRequest request)
public async Task<ActionResult<SnPollAnswer>> AnswerPoll(Guid id, [FromBody] PollAnswerRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
@@ -78,7 +79,7 @@ public class PollController(
}
[HttpGet("{id:guid}/feedback")]
public async Task<ActionResult<List<PollAnswer>>> GetPollFeedback(
public async Task<ActionResult<List<SnPollAnswer>>> GetPollFeedback(
Guid id,
[FromQuery] int offset = 0,
[FromQuery] int take = 20
@@ -91,7 +92,7 @@ public class PollController(
.FirstOrDefaultAsync(p => p.Id == id);
if (poll is null) return NotFound("Poll not found");
if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Publisher.PublisherMemberRole.Viewer))
if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Shared.Models.PublisherMemberRole.Viewer))
return StatusCode(403, "You need to be a viewer to view this poll's feedback.");
var answerQuery = db.PollAnswers
@@ -117,7 +118,7 @@ public class PollController(
{
var protoValue = answeredAccounts.FirstOrDefault(a => a.Id == answer.AccountId.ToString());
if (protoValue is not null)
answer.Account = AccountReference.FromProtoValue(protoValue);
answer.Account = SnAccount.FromProtoValue(protoValue);
}
}
@@ -177,14 +178,14 @@ public class PollController(
public Guid Id { get; set; } = Guid.NewGuid();
public PollQuestionType Type { get; set; }
public List<PollOption>? Options { get; set; }
public List<SnPollOption>? Options { get; set; }
[MaxLength(1024)] public string Title { get; set; } = null!;
[MaxLength(4096)] public string? Description { get; set; }
public int Order { get; set; } = 0;
public bool IsRequired { get; set; }
public PollQuestion ToQuestion() => new()
public SnPollQuestion ToQuestion() => new()
{
Id = Id,
Type = Type,
@@ -207,7 +208,7 @@ public class PollController(
var publisher = await pub.GetPublisherByName(pubName);
if (publisher is null) return BadRequest("Publisher was not found.");
if (!await pub.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor))
if (!await pub.IsMemberWithRole(publisher.Id, accountId, Shared.Models.PublisherMemberRole.Editor))
return StatusCode(403, "You need at least be an editor to create polls as this publisher.");
var poll = new Poll
@@ -253,7 +254,7 @@ public class PollController(
if (poll == null) return NotFound("Poll not found");
// Check if user is an editor of the publisher that owns the poll
if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Shared.Models.PublisherMemberRole.Editor))
return StatusCode(403, "You need to be at least an editor to update this poll.");
// Update properties if they are provided in the request
@@ -317,7 +318,7 @@ public class PollController(
if (poll == null) return NotFound("Poll not found");
// Check if user is an editor of the publisher that owns the poll
if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
if (!await pub.IsMemberWithRole(poll.PublisherId, accountId, Shared.Models.PublisherMemberRole.Editor))
return StatusCode(403, "You need to be at least an editor to delete this poll.");
// Delete all answers for this poll

View File

@@ -1,13 +1,14 @@
using DysonNetwork.Shared.Models;
using DysonNetwork.Sphere.WebReader;
namespace DysonNetwork.Sphere.Poll;
public class PollWithStats : Poll
{
public PollAnswer? UserAnswer { get; set; }
public SnPollAnswer? UserAnswer { get; set; }
public Dictionary<Guid, Dictionary<string, int>> Stats { get; set; } = new(); // question id -> (option id -> count)
public static PollWithStats FromPoll(Poll poll, PollAnswer? userAnswer = null)
public static PollWithStats FromPoll(Poll poll, SnPollAnswer? userAnswer = null)
{
return new PollWithStats
{

View File

@@ -1,5 +1,6 @@
using System.Text.Json;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@@ -42,10 +43,10 @@ public class PollService(AppDatabase db, ICacheService cache)
private const string PollAnswerCachePrefix = "poll:answer:";
public async Task<PollAnswer?> GetPollAnswer(Guid pollId, Guid accountId)
public async Task<SnPollAnswer?> GetPollAnswer(Guid pollId, Guid accountId)
{
var cacheKey = $"{PollAnswerCachePrefix}{pollId}:{accountId}";
var cachedAnswer = await cache.GetAsync<PollAnswer?>(cacheKey);
var cachedAnswer = await cache.GetAsync<SnPollAnswer?>(cacheKey);
if (cachedAnswer is not null)
return cachedAnswer;
@@ -103,7 +104,7 @@ public class PollService(AppDatabase db, ICacheService cache)
}
}
public async Task<PollAnswer> AnswerPoll(Guid pollId, Guid accountId, Dictionary<string, JsonElement> answer)
public async Task<SnPollAnswer> AnswerPoll(Guid pollId, Guid accountId, Dictionary<string, JsonElement> answer)
{
// Validation
var poll = await db.Polls
@@ -124,7 +125,7 @@ public class PollService(AppDatabase db, ICacheService cache)
await UnAnswerPoll(pollId, accountId);
// Save the new answer
var answerRecord = new PollAnswer
var answerRecord = new SnPollAnswer
{
PollId = pollId,
AccountId = accountId,