From 57ac4750ad0d9a16f1e12fbcc846a326a1925b94 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Thu, 7 Mar 2024 20:07:17 +0100 Subject: [PATCH] [backend/core] Allow editing of locally originated polls (ISH-136) This also improves the behavior of handling remotely originating poll edits. --- .../Controllers/Mastodon/StatusController.cs | 21 +++++--- .../Core/Queues/BackgroundTaskQueue.cs | 2 +- .../Core/Services/NoteService.cs | 52 ++++++++++++++++--- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs b/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs index f4843514..aef34161 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/StatusController.cs @@ -98,7 +98,7 @@ public class StatusController( var success = await noteSvc.LikeNoteAsync(note, user); if (success) note.LikeCount++; // we do not want to call save changes after this point - + return await GetNote(id); } @@ -118,7 +118,7 @@ public class StatusController( var success = await noteSvc.UnlikeNoteAsync(note, user); if (success) note.LikeCount--; // we do not want to call save changes after this point - + return await GetNote(id); } @@ -331,20 +331,29 @@ public class StatusController( public async Task EditNote(string id, [FromHybrid] StatusSchemas.EditStatusRequest request) { var user = HttpContext.GetUserOrFail(); - var note = await db.Notes.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == id && p.User == user) ?? + var note = await db.Notes + .Include(p => p.Poll) + .IncludeCommonProperties() + .FirstOrDefaultAsync(p => p.Id == id && p.User == user) ?? throw GracefulException.RecordNotFound(); if (request.Text == null && request.MediaIds is not { Count: > 0 } && request.Poll == null) throw GracefulException.BadRequest("Posts must have text, media or poll"); - if (request.Poll != null) - throw GracefulException.BadRequest("Poll edits haven't been implemented yet, please delete & redraft instead"); + var poll = request.Poll != null + ? new Poll + { + Choices = request.Poll.Options, + Multiple = request.Poll.Multiple, + ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(request.Poll.ExpiresIn) + } + : null; var attachments = request.MediaIds != null ? await db.DriveFiles.Where(p => request.MediaIds.Contains(p.Id)).ToListAsync() : []; - note = await noteSvc.UpdateNoteAsync(note, request.Text, request.Cw, attachments); + note = await noteSvc.UpdateNoteAsync(note, request.Text, request.Cw, attachments, poll); var res = await noteRenderer.RenderAsync(note, user); return Ok(res); diff --git a/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs b/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs index bc62a496..114f27e6 100644 --- a/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs +++ b/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs @@ -144,7 +144,7 @@ public abstract class BackgroundTaskQueue var db = scope.GetRequiredService(); var poll = await db.Polls.FirstOrDefaultAsync(p => p.NoteId == job.NoteId, cancellationToken: token); if (poll == null) return; - if (poll.ExpiresAt > DateTime.UtcNow + TimeSpan.FromMinutes(5)) return; + if (poll.ExpiresAt > DateTime.UtcNow + TimeSpan.FromSeconds(30)) return; var note = await db.Notes.IncludeCommonProperties() .FirstOrDefaultAsync(p => p.Id == poll.NoteId, cancellationToken: token); if (note == null) return; diff --git a/Iceshrimp.Backend/Core/Services/NoteService.cs b/Iceshrimp.Backend/Core/Services/NoteService.cs index 9a4004dd..b4daff9d 100644 --- a/Iceshrimp.Backend/Core/Services/NoteService.cs +++ b/Iceshrimp.Backend/Core/Services/NoteService.cs @@ -208,7 +208,8 @@ public class NoteService( } public async Task UpdateNoteAsync( - Note note, string? text = null, string? cw = null, IReadOnlyCollection? attachments = null + Note note, string? text = null, string? cw = null, IReadOnlyCollection? attachments = null, + Poll? poll = null ) { var noteEdit = new NoteEdit @@ -268,6 +269,38 @@ public class NoteService( note.UpdatedAt = DateTime.UtcNow; + if (poll != null) + { + if (note.Poll != null) + { + if (note.Poll.ExpiresAt != poll.ExpiresAt) + { + note.Poll.ExpiresAt = poll.ExpiresAt; + await EnqueuePollExpiryTask(note.Poll); + } + + if (!note.Poll.Choices.SequenceEqual(poll.Choices) || note.Poll.Multiple != poll.Multiple) + { + await db.PollVotes.Where(p => p.Note == note).ExecuteDeleteAsync(); + note.Poll.Choices = poll.Choices; + note.Poll.Votes = poll.Choices.Select(p => 0).ToList(); + note.Poll.Multiple = poll.Multiple; + db.Update(note.Poll); + } + } + else { + poll.Note = note; + poll.UserId = note.User.Id; + poll.UserHost = note.UserHost; + poll.Votes = poll.Choices.Select(_ => 0).ToList(); + poll.NoteVisibility = note.Visibility; + await db.AddAsync(poll); + await EnqueuePollExpiryTask(poll); + } + + note.HasPoll = true; + } + db.Update(note); await db.AddAsync(noteEdit); await db.SaveChangesAsync(); @@ -658,13 +691,20 @@ public class NoteService( if (dbNote.Poll != null) { - if (!dbNote.Poll.Choices.SequenceEqual(choices.Select(p => p.Name))) + if (dbNote.Poll.ExpiresAt != (question.EndTime ?? question.Closed)) + { + dbNote.Poll.ExpiresAt = question.EndTime ?? question.Closed; + if (dbNote.Poll.ExpiresAt != null) + await EnqueuePollExpiryTask(dbNote.Poll); + } + + if (!dbNote.Poll.Choices.SequenceEqual(choices.Select(p => p.Name)) || + dbNote.Poll.Multiple != (question.AnyOf != null)) { await db.PollVotes.Where(p => p.Note == dbNote).ExecuteDeleteAsync(); - dbNote.Poll.Choices = choices.Select(p => p.Name).Cast().ToList(); - dbNote.Poll.Votes = choices.Select(p => (int?)p.Replies?.TotalItems ?? 0).ToList(); - dbNote.Poll.ExpiresAt = question.EndTime ?? question.Closed; - dbNote.Poll.Multiple = question.AnyOf != null; + dbNote.Poll.Choices = choices.Select(p => p.Name).Cast().ToList(); + dbNote.Poll.Votes = choices.Select(p => (int?)p.Replies?.TotalItems ?? 0).ToList(); + dbNote.Poll.Multiple = question.AnyOf != null; db.Update(dbNote.Poll); } else