[backend/api] Add idempotency key support to NoteController.CreateNote (ISH-294)
This commit is contained in:
parent
2519f382c5
commit
d109f00d55
3 changed files with 45 additions and 8 deletions
|
@ -363,7 +363,7 @@ public class StatusController(
|
|||
{
|
||||
if (!hit.StartsWith('_')) break;
|
||||
await Task.Delay(100);
|
||||
hit = await cache.GetAsync<string>($"idempotency:{user.Id}:{idempotencyKey}") ??
|
||||
hit = await cache.GetAsync<string>(key) ??
|
||||
throw new Exception("Idempotency key status disappeared in for loop");
|
||||
if (i >= 10)
|
||||
throw GracefulException.RequestTimeout("Failed to resolve idempotency key note within 1000 ms");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Net.Mime;
|
||||
using AsyncKeyedLock;
|
||||
using Iceshrimp.Backend.Controllers.Attributes;
|
||||
using Iceshrimp.Backend.Controllers.Renderers;
|
||||
using Iceshrimp.Shared.Schemas;
|
||||
|
@ -24,9 +25,16 @@ public class NoteController(
|
|||
DatabaseContext db,
|
||||
NoteService noteSvc,
|
||||
NoteRenderer noteRenderer,
|
||||
UserRenderer userRenderer
|
||||
UserRenderer userRenderer,
|
||||
CacheService cache
|
||||
) : ControllerBase
|
||||
{
|
||||
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
|
||||
{
|
||||
o.PoolSize = 100;
|
||||
o.PoolInitialFill = 5;
|
||||
});
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authenticate]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NoteResponse))]
|
||||
|
@ -209,6 +217,31 @@ public class NoteController(
|
|||
{
|
||||
var user = HttpContext.GetUserOrFail();
|
||||
|
||||
if (request.IdempotencyKey != null)
|
||||
{
|
||||
var key = $"idempotency:{user.Id}:{request.IdempotencyKey}";
|
||||
string hit;
|
||||
using (await KeyedLocker.LockAsync(key))
|
||||
{
|
||||
hit = await cache.FetchAsync(key, TimeSpan.FromHours(24), () => $"_:{HttpContext.TraceIdentifier}");
|
||||
}
|
||||
|
||||
if (hit != $"_:{HttpContext.TraceIdentifier}")
|
||||
{
|
||||
for (var i = 0; i <= 10; i++)
|
||||
{
|
||||
if (!hit.StartsWith('_')) break;
|
||||
await Task.Delay(100);
|
||||
hit = await cache.GetAsync<string>(key) ??
|
||||
throw new Exception("Idempotency key status disappeared in for loop");
|
||||
if (i >= 10)
|
||||
throw GracefulException.RequestTimeout("Failed to resolve idempotency key note within 1000 ms");
|
||||
}
|
||||
|
||||
return await GetNote(hit);
|
||||
}
|
||||
}
|
||||
|
||||
var reply = request.ReplyId != null
|
||||
? await db.Notes.Where(p => p.Id == request.ReplyId)
|
||||
.IncludeCommonProperties()
|
||||
|
@ -232,6 +265,9 @@ public class NoteController(
|
|||
var note = await noteSvc.CreateNoteAsync(user, (Note.NoteVisibility)request.Visibility, request.Text,
|
||||
request.Cw, reply, renote, attachments);
|
||||
|
||||
if (request.IdempotencyKey != null)
|
||||
await cache.SetAsync($"idempotency:{user.Id}:{request.IdempotencyKey}", note.Id, TimeSpan.FromHours(24));
|
||||
|
||||
return Ok(await noteRenderer.RenderOne(note, user));
|
||||
}
|
||||
}
|
|
@ -2,10 +2,11 @@ namespace Iceshrimp.Shared.Schemas;
|
|||
|
||||
public class NoteCreateRequest
|
||||
{
|
||||
public required string Text { get; set; }
|
||||
public string? Cw { get; set; }
|
||||
public string? ReplyId { get; set; }
|
||||
public string? RenoteId { get; set; }
|
||||
public List<string>? MediaIds { get; set; }
|
||||
public required NoteVisibility Visibility { get; set; }
|
||||
public required string Text { get; set; }
|
||||
public string? Cw { get; set; }
|
||||
public string? ReplyId { get; set; }
|
||||
public string? RenoteId { get; set; }
|
||||
public List<string>? MediaIds { get; set; }
|
||||
public required NoteVisibility Visibility { get; set; }
|
||||
public string? IdempotencyKey { get; set; }
|
||||
}
|
Loading…
Add table
Reference in a new issue