Anonymous poll

This commit is contained in:
2025-09-07 23:22:34 +08:00
parent eaa3a9c297
commit fa01b7027a
6 changed files with 2204 additions and 4 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddPollAnonymousOrNot : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "is_anonymous",
table: "polls",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "is_anonymous",
table: "polls");
}
}
}

View File

@@ -395,6 +395,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("ended_at"); .HasColumnName("ended_at");
b.Property<bool>("IsAnonymous")
.HasColumnType("boolean")
.HasColumnName("is_anonymous");
b.Property<Guid>("PublisherId") b.Property<Guid>("PublisherId")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasColumnName("publisher_id"); .HasColumnName("publisher_id");

View File

@@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Pass.Account;
using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Data;
using NodaTime; using NodaTime;
@@ -16,6 +17,7 @@ public class Poll : ModelBase
[MaxLength(4096)] public string? Description { get; set; } [MaxLength(4096)] public string? Description { get; set; }
public Instant? EndedAt { get; set; } public Instant? EndedAt { get; set; }
public bool IsAnonymous { get; set; }
public Guid PublisherId { get; set; } public Guid PublisherId { get; set; }
[JsonIgnore] public Publisher.Publisher? Publisher { get; set; } [JsonIgnore] public Publisher.Publisher? Publisher { get; set; }
@@ -62,4 +64,5 @@ public class PollAnswer : ModelBase
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
public Guid PollId { get; set; } public Guid PollId { get; set; }
[JsonIgnore] public Poll? Poll { get; set; } [JsonIgnore] public Poll? Poll { get; set; }
[NotMapped] public Account? Account { get; set; }
} }

View File

@@ -1,6 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Publisher;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -11,7 +12,12 @@ namespace DysonNetwork.Sphere.Poll;
[ApiController] [ApiController]
[Route("/api/polls")] [Route("/api/polls")]
public class PollController(AppDatabase db, PollService polls, Publisher.PublisherService pub) : ControllerBase public class PollController(
AppDatabase db,
PollService polls,
Publisher.PublisherService pub,
AccountClientHelper accountsHelper
) : ControllerBase
{ {
[HttpGet("{id:guid}")] [HttpGet("{id:guid}")]
public async Task<ActionResult<PollWithStats>> GetPoll(Guid id) public async Task<ActionResult<PollWithStats>> GetPoll(Guid id)
@@ -101,6 +107,20 @@ public class PollController(AppDatabase db, PollService polls, Publisher.Publish
.Take(take) .Take(take)
.ToListAsync(); .ToListAsync();
if (!poll.IsAnonymous)
{
var answeredAccountsId = answers.Select(x => x.AccountId).Distinct().ToList();
var answeredAccounts = await accountsHelper.GetAccountBatch(answeredAccountsId);
// Populate Account field for each answer
foreach (var answer in answers)
{
var protoValue = answeredAccounts.FirstOrDefault(a => a.Id == answer.AccountId.ToString());
if (protoValue is not null)
answer.Account = Pass.Account.Account.FromProtoValue(protoValue);
}
}
return Ok(answers); return Ok(answers);
} }
@@ -148,6 +168,7 @@ public class PollController(AppDatabase db, PollService polls, Publisher.Publish
public string? Title { get; set; } public string? Title { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public Instant? EndedAt { get; set; } public Instant? EndedAt { get; set; }
public bool? IsAnonymous { get; set; }
public List<PollRequestQuestion>? Questions { get; set; } public List<PollRequestQuestion>? Questions { get; set; }
} }
@@ -194,6 +215,7 @@ public class PollController(AppDatabase db, PollService polls, Publisher.Publish
Title = request.Title, Title = request.Title,
Description = request.Description, Description = request.Description,
EndedAt = request.EndedAt, EndedAt = request.EndedAt,
IsAnonymous = request.IsAnonymous ?? false,
PublisherId = publisher.Id, PublisherId = publisher.Id,
Questions = request.Questions.Select(q => q.ToQuestion()).ToList() Questions = request.Questions.Select(q => q.ToQuestion()).ToList()
}; };
@@ -238,6 +260,7 @@ public class PollController(AppDatabase db, PollService polls, Publisher.Publish
if (request.Title != null) poll.Title = request.Title; if (request.Title != null) poll.Title = request.Title;
if (request.Description != null) poll.Description = request.Description; if (request.Description != null) poll.Description = request.Description;
if (request.EndedAt.HasValue) poll.EndedAt = request.EndedAt; if (request.EndedAt.HasValue) poll.EndedAt = request.EndedAt;
if (request.IsAnonymous.HasValue) poll.IsAnonymous = request.IsAnonymous.Value;
db.Update(poll); db.Update(poll);
await db.SaveChangesAsync(); await db.SaveChangesAsync();

View File

@@ -75,7 +75,7 @@ public class PollService(AppDatabase db, ICacheService cache)
var questionId = question.Id.ToString(); var questionId = question.Id.ToString();
if (question.IsRequired && !answer.ContainsKey(questionId)) if (question.IsRequired && !answer.ContainsKey(questionId))
throw new Exception($"Missing required field: {question.Title}"); throw new Exception($"Missing required field: {question.Title}");
else if (!answer.ContainsKey(questionId)) if (!answer.ContainsKey(questionId))
continue; continue;
switch (question.Type) switch (question.Type)
{ {
@@ -269,7 +269,7 @@ public class PollService(AppDatabase db, ICacheService cache)
await cache.SetWithGroupsAsync( await cache.SetWithGroupsAsync(
cacheKey, cacheKey,
stats, stats,
new[] { PollCacheGroupPrefix + question.PollId }, [PollCacheGroupPrefix + question.PollId],
TimeSpan.FromHours(1)); TimeSpan.FromHours(1));
return stats; return stats;