From bb5d70eddb738bc3622b5f2bd6cf39767b093210 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 27 Dec 2025 14:24:35 +0800 Subject: [PATCH] :sparkles: Sharable rewind point --- .../Rewind/AccountRewindController.cs | 40 ++++++++++++++ .../Rewind/AccountRewindService.cs | 52 +++++++++++++++++++ DysonNetwork.Shared/Models/RewindPoint.cs | 2 + 3 files changed, 94 insertions(+) diff --git a/DysonNetwork.Pass/Rewind/AccountRewindController.cs b/DysonNetwork.Pass/Rewind/AccountRewindController.cs index 2fadf6f..d448c32 100644 --- a/DysonNetwork.Pass/Rewind/AccountRewindController.cs +++ b/DysonNetwork.Pass/Rewind/AccountRewindController.cs @@ -8,6 +8,14 @@ namespace DysonNetwork.Pass.Rewind; [Route("/api/rewind")] public class AccountRewindController(AccountRewindService rewindSrv) : ControllerBase { + [HttpGet("{code}")] + public async Task> GetRewindPoint([FromRoute] string code) + { + var point = await rewindSrv.GetPublicRewindPoint(code); + if (point is null) return NotFound(); + return Ok(point); + } + [HttpGet("me")] [Authorize] public async Task> GetCurrentRewindPoint() @@ -16,4 +24,36 @@ public class AccountRewindController(AccountRewindService rewindSrv) : Controlle var point = await rewindSrv.GetOrCreateRewindPoint(currentUser.Id); return Ok(point); } + + [HttpPost("me/{year:int}/public")] + [Authorize] + public async Task> SetRewindPointPublic([FromRoute] int year) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + try + { + var point = await rewindSrv.SetRewindPointPublic(currentUser.Id, year); + return Ok(point); + } + catch (InvalidOperationException error) + { + return BadRequest(error.Message); + } + } + + [HttpPost("me/{year:int}/private")] + [Authorize] + public async Task> SetRewindPointPrivate([FromRoute] int year) + { + if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized(); + try + { + var point = await rewindSrv.SetRewindPointPrivate(currentUser.Id, year); + return Ok(point); + } + catch (InvalidOperationException error) + { + return BadRequest(error.Message); + } + } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Rewind/AccountRewindService.cs b/DysonNetwork.Pass/Rewind/AccountRewindService.cs index 1da962d..54b671d 100644 --- a/DysonNetwork.Pass/Rewind/AccountRewindService.cs +++ b/DysonNetwork.Pass/Rewind/AccountRewindService.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using Grpc.Net.Client; @@ -75,4 +76,55 @@ public class AccountRewindService( return await CreateRewindPoint(accountId); } + + public async Task GetPublicRewindPoint(string code) + { + var point = await db.RewindPoints + .Where(p => p.SharableCode == code) + .OrderBy(p => p.CreatedAt) + .FirstOrDefaultAsync(); + return point; + } + + public async Task SetRewindPointPublic(Guid accountId, int year) + { + var point = await db.RewindPoints + .Where(p => p.AccountId == accountId && p.Year == year) + .OrderBy(p => p.CreatedAt) + .FirstOrDefaultAsync(); + if (point is null) throw new InvalidOperationException("No rewind point was found."); + point.SharableCode = _GenerateRandomString(16); + db.RewindPoints.Update(point); + await db.SaveChangesAsync(); + + return point; + } + + public async Task SetRewindPointPrivate(Guid accountId, int year) + { + var point = await db.RewindPoints + .Where(p => p.AccountId == accountId && p.Year == year) + .OrderBy(p => p.CreatedAt) + .FirstOrDefaultAsync(); + if (point is null) throw new InvalidOperationException("No rewind point was found."); + point.SharableCode = null; + db.RewindPoints.Update(point); + await db.SaveChangesAsync(); + + return point; + } + + private static string _GenerateRandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var result = new char[length]; + using var rng = RandomNumberGenerator.Create(); + for (var i = 0; i < length; i++) + { + var bytes = new byte[1]; + rng.GetBytes(bytes); + result[i] = chars[bytes[0] % chars.Length]; + } + return new string(result); + } } diff --git a/DysonNetwork.Shared/Models/RewindPoint.cs b/DysonNetwork.Shared/Models/RewindPoint.cs index 2446fcb..cfa93b5 100644 --- a/DysonNetwork.Shared/Models/RewindPoint.cs +++ b/DysonNetwork.Shared/Models/RewindPoint.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace DysonNetwork.Shared.Models; @@ -13,6 +14,7 @@ public class SnRewindPoint : ModelBase /// this field provide the clues for the client to parsing the data correctly. /// public int SchemaVersion { get; set; } = 1; + [MaxLength(4096)] public string? SharableCode { get; set; } [Column(TypeName = "jsonb")] public Dictionary Data { get; set; } = new();