🐛 Fixes for sticker & sticker packs

This commit is contained in:
LittleSheep 2025-05-11 22:13:13 +08:00
parent eab775e224
commit 3d5d4db3e3
8 changed files with 93 additions and 34 deletions

View File

@ -17,13 +17,13 @@ public class AccountService(
{ {
public async Task PurgeAccountCache(Account account) public async Task PurgeAccountCache(Account account)
{ {
cache.Remove($"dyn_user_friends_{account.Id}"); cache.Remove($"UserFriends_{account.Id}");
var sessions = await db.AuthSessions.Where(e => e.Account.Id == account.Id).Select(e => e.Id) var sessions = await db.AuthSessions.Where(e => e.Account.Id == account.Id).Select(e => e.Id)
.ToListAsync(); .ToListAsync();
foreach (var session in sessions) foreach (var session in sessions)
{ {
cache.Remove($"dyn_auth_{session}"); cache.Remove($"Auth_{session}");
} }
} }

View File

@ -51,8 +51,8 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await ApplyRelationshipPermissions(relationship); await ApplyRelationshipPermissions(relationship);
cache.Remove($"dyn_user_friends_{relationship.AccountId}"); cache.Remove($"UserFriends_{relationship.AccountId}");
cache.Remove($"dyn_user_friends_{relationship.RelatedId}"); cache.Remove($"UserFriends_{relationship.RelatedId}");
return relationship; return relationship;
} }
@ -105,8 +105,8 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
ApplyRelationshipPermissions(relationshipBackward) ApplyRelationshipPermissions(relationshipBackward)
); );
cache.Remove($"dyn_user_friends_{relationship.AccountId}"); cache.Remove($"UserFriends_{relationship.AccountId}");
cache.Remove($"dyn_user_friends_{relationship.RelatedId}"); cache.Remove($"UserFriends_{relationship.RelatedId}");
return relationshipBackward; return relationshipBackward;
} }
@ -120,20 +120,20 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
db.Update(relationship); db.Update(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await ApplyRelationshipPermissions(relationship); await ApplyRelationshipPermissions(relationship);
cache.Remove($"dyn_user_friends_{related.Id}"); cache.Remove($"UserFriends_{related.Id}");
return relationship; return relationship;
} }
public async Task<List<long>> ListAccountFriends(Account account) public async Task<List<long>> ListAccountFriends(Account account)
{ {
if (!cache.TryGetValue($"dyn_user_friends_{account.Id}", out List<long>? friends)) if (!cache.TryGetValue($"UserFriends_{account.Id}", out List<long>? friends))
{ {
friends = await db.AccountRelationships friends = await db.AccountRelationships
.Where(r => r.RelatedId == account.Id) .Where(r => r.RelatedId == account.Id)
.Where(r => r.Status == RelationshipStatus.Friends) .Where(r => r.Status == RelationshipStatus.Friends)
.Select(r => r.AccountId) .Select(r => r.AccountId)
.ToListAsync(); .ToListAsync();
cache.Set($"dyn_user_friends_{account.Id}", friends, TimeSpan.FromHours(1)); cache.Set($"UserFriends_{account.Id}", friends, TimeSpan.FromHours(1));
} }
return friends ?? []; return friends ?? [];

View File

@ -10,7 +10,7 @@ public class UserInfoMiddleware(RequestDelegate next, IMemoryCache cache)
var sessionIdClaim = context.User.FindFirst("session_id")?.Value; var sessionIdClaim = context.User.FindFirst("session_id")?.Value;
if (sessionIdClaim is not null && Guid.TryParse(sessionIdClaim, out var sessionId)) if (sessionIdClaim is not null && Guid.TryParse(sessionIdClaim, out var sessionId))
{ {
if (!cache.TryGetValue($"dyn_auth_{sessionId}", out Session? session)) if (!cache.TryGetValue($"Auth_{sessionId}", out Session? session))
{ {
session = await db.AuthSessions session = await db.AuthSessions
.Include(e => e.Challenge) .Include(e => e.Challenge)
@ -21,7 +21,7 @@ public class UserInfoMiddleware(RequestDelegate next, IMemoryCache cache)
if (session is not null) if (session is not null)
{ {
cache.Set($"dyn_auth_{sessionId}", session, TimeSpan.FromHours(1)); cache.Set($"Auth_{sessionId}", session, TimeSpan.FromHours(1));
} }
} }

View File

@ -230,6 +230,8 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (post is null) return NotFound(); if (post is null) return NotFound();
var isSelfReact = post.Publisher.AccountId is not null && post.Publisher.AccountId == currentUser.Id;
var isExistingReaction = await db.PostReactions var isExistingReaction = await db.PostReactions
.AnyAsync(r => r.PostId == post.Id && .AnyAsync(r => r.PostId == post.Id &&
r.Symbol == request.Symbol && r.Symbol == request.Symbol &&
@ -241,7 +243,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
PostId = post.Id, PostId = post.Id,
AccountId = currentUser.Id AccountId = currentUser.Id
}; };
var isRemoving = await ps.ModifyPostVotes(post, reaction, isExistingReaction); var isRemoving = await ps.ModifyPostVotes(post, reaction, isExistingReaction, isSelfReact);
if (isRemoving) return NoContent(); if (isRemoving) return NoContent();
return Ok(reaction); return Ok(reaction);

View File

@ -174,7 +174,7 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
/// <param name="post">Post that modifying</param> /// <param name="post">Post that modifying</param>
/// <param name="reaction">The new / target reaction adding / removing</param> /// <param name="reaction">The new / target reaction adding / removing</param>
/// <param name="isRemoving">Indicate this operation is adding / removing</param> /// <param name="isRemoving">Indicate this operation is adding / removing</param>
public async Task<bool> ModifyPostVotes(Post post, PostReaction reaction, bool isRemoving) public async Task<bool> ModifyPostVotes(Post post, PostReaction reaction, bool isRemoving, bool isSelfReact)
{ {
var isExistingReaction = await db.Set<PostReaction>() var isExistingReaction = await db.Set<PostReaction>()
.AnyAsync(r => r.PostId == post.Id && r.AccountId == reaction.AccountId); .AnyAsync(r => r.PostId == post.Id && r.AccountId == reaction.AccountId);
@ -193,6 +193,12 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
return isRemoving; return isRemoving;
} }
if (isSelfReact)
{
await db.SaveChangesAsync();
return isRemoving;
}
switch (reaction.Attitude) switch (reaction.Attitude)
{ {
case PostReactionAttitude.Positive: case PostReactionAttitude.Positive:

View File

@ -26,6 +26,14 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
return Ok(publisher); return Ok(publisher);
} }
[HttpGet("{name}/stats")]
public async Task<ActionResult<PublisherService.PublisherStats>> GetPublisherStats(string name)
{
var stats = await ps.GetPublisherStats(name);
if (stats is null) return NotFound();
return Ok(stats);
}
[HttpGet] [HttpGet]
[Authorize] [Authorize]
public async Task<ActionResult<List<Publisher>>> ListManagedPublishers() public async Task<ActionResult<List<Publisher>>> ListManagedPublishers()

View File

@ -1,9 +1,11 @@
using DysonNetwork.Sphere.Storage; using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using NodaTime; using NodaTime;
namespace DysonNetwork.Sphere.Post; namespace DysonNetwork.Sphere.Post;
public class PublisherService(AppDatabase db, FileService fs) public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache)
{ {
public async Task<Publisher> CreateIndividualPublisher( public async Task<Publisher> CreateIndividualPublisher(
Account.Account account, Account.Account account,
@ -44,4 +46,50 @@ public class PublisherService(AppDatabase db, FileService fs)
} }
// TODO Able to create organizational publisher when the realm system is completed // TODO Able to create organizational publisher when the realm system is completed
public class PublisherStats
{
public int PostsCreated { get; set; }
public int StickerPacksCreated { get; set; }
public int StickersCreated { get; set; }
public int UpvoteReceived { get; set; }
public int DownvoteReceived { get; set; }
}
private const string PublisherStatsCacheKey = "PublisherStats_{0}";
public async Task<PublisherStats?> GetPublisherStats(string name)
{
var cacheKey = string.Format(PublisherStatsCacheKey, name);
if (cache.TryGetValue(cacheKey, out PublisherStats? stats))
return stats;
var publisher = await db.Publishers.FirstOrDefaultAsync(e => e.Name == name);
if (publisher is null) return null;
var postsCount = await db.Posts.Where(e => e.Publisher.Id == publisher.Id).CountAsync();
var postsUpvotes = await db.PostReactions
.Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Positive)
.CountAsync();
var postsDownvotes = await db.PostReactions
.Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Negative)
.CountAsync();
var stickerPacksId = await db.StickerPacks.Where(e => e.Publisher.Id == publisher.Id).Select(e => e.Id).ToListAsync();
var stickerPacksCount = stickerPacksId.Count;
var stickersCount = await db.Stickers.Where(e => stickerPacksId.Contains(e.PackId)).CountAsync();
stats = new PublisherStats
{
PostsCreated = postsCount,
StickerPacksCreated = stickerPacksCount,
StickersCreated = stickersCount,
UpvoteReceived = postsUpvotes,
DownvoteReceived = postsDownvotes
};
cache.Set(cacheKey, stats, TimeSpan.FromMinutes(5));
return stats;
}
} }

View File

@ -63,7 +63,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
} }
[HttpPost] [HttpPost]
[RequiredPermission("global", "sticker.packs.create")] [RequiredPermission("global", "stickers.packs.create")]
public async Task<ActionResult<StickerPack>> CreateStickerPack([FromBody] StickerPackRequest request) public async Task<ActionResult<StickerPack>> CreateStickerPack([FromBody] StickerPackRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
@ -104,16 +104,13 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
var pack = await db.StickerPacks var pack = await db.StickerPacks
.Include(p => p.Publisher) .Include(p => p.Publisher)
.FirstOrDefaultAsync(p => p.Id == id); .FirstOrDefaultAsync(p => p.Id == id);
if (pack is null) if (pack is null)
return NotFound(); return NotFound();
var member = await db.PublisherMembers var member = await db.PublisherMembers
.FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId); .FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId);
if (member is null) if (member is null)
return StatusCode(403, "You are not a member of this publisher"); return StatusCode(403, "You are not a member of this publisher");
if (member.Role < PublisherMemberRole.Editor) if (member.Role < PublisherMemberRole.Editor)
return StatusCode(403, "You need to be at least an editor to update sticker packs"); return StatusCode(403, "You need to be at least an editor to update sticker packs");
@ -152,25 +149,16 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
return NoContent(); return NoContent();
} }
[HttpGet("{packId:guid}/content")]
[HttpGet("{packId:guid}/stickers")] public async Task<ActionResult<List<Sticker>>> ListStickers(Guid packId)
public async Task<ActionResult<List<Sticker>>> ListStickers(Guid packId, [FromQuery] int offset = 0,
[FromQuery] int take = 20)
{ {
var totalCount = await db.Stickers
.Where(s => s.Pack.Id == packId)
.CountAsync();
var stickers = await db.Stickers var stickers = await db.Stickers
.Where(s => s.Pack.Id == packId) .Where(s => s.Pack.Id == packId)
.Include(e => e.Pack) .Include(e => e.Pack)
.Include(e => e.Image) .Include(e => e.Image)
.OrderByDescending(e => e.CreatedAt) .OrderByDescending(e => e.CreatedAt)
.Skip(offset)
.Take(take)
.ToListAsync(); .ToListAsync();
Response.Headers["X-Total"] = totalCount.ToString();
return Ok(stickers); return Ok(stickers);
} }
@ -189,7 +177,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
return Ok(sticker); return Ok(sticker);
} }
[HttpGet("{packId:guid}/stickers/{id:guid}")] [HttpGet("{packId:guid}/content/{id:guid}")]
public async Task<ActionResult<Sticker>> GetSticker(Guid packId, Guid id) public async Task<ActionResult<Sticker>> GetSticker(Guid packId, Guid id)
{ {
var sticker = await db.Stickers var sticker = await db.Stickers
@ -208,7 +196,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
public string? ImageId { get; set; } public string? ImageId { get; set; }
} }
[HttpPatch("{packId:guid}/stickers/{id:guid}")] [HttpPatch("{packId:guid}/content/{id:guid}")]
public async Task<IActionResult> UpdateSticker(Guid packId, Guid id, [FromBody] StickerRequest request) public async Task<IActionResult> UpdateSticker(Guid packId, Guid id, [FromBody] StickerRequest request)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
@ -219,6 +207,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
return permissionCheck; return permissionCheck;
var sticker = await db.Stickers var sticker = await db.Stickers
.Include(s => s.Image)
.Include(s => s.Pack) .Include(s => s.Pack)
.ThenInclude(p => p.Publisher) .ThenInclude(p => p.Publisher)
.FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId); .FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId);
@ -242,7 +231,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
return Ok(sticker); return Ok(sticker);
} }
[HttpDelete("{packId:guid}/stickers/{id:guid}")] [HttpDelete("{packId:guid}/content/{id:guid}")]
public async Task<IActionResult> DeleteSticker(Guid packId, Guid id) public async Task<IActionResult> DeleteSticker(Guid packId, Guid id)
{ {
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
@ -253,6 +242,7 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
return permissionCheck; return permissionCheck;
var sticker = await db.Stickers var sticker = await db.Stickers
.Include(s => s.Image)
.Include(s => s.Pack) .Include(s => s.Pack)
.ThenInclude(p => p.Publisher) .ThenInclude(p => p.Publisher)
.FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId); .FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId);
@ -264,7 +254,9 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
return NoContent(); return NoContent();
} }
[HttpPost("{packId:guid}/stickers")] public const int MaxStickersPerPack = 24;
[HttpPost("{packId:guid}/content")]
[RequiredPermission("global", "stickers.create")] [RequiredPermission("global", "stickers.create")]
public async Task<IActionResult> CreateSticker(Guid packId, [FromBody] StickerRequest request) public async Task<IActionResult> CreateSticker(Guid packId, [FromBody] StickerRequest request)
{ {
@ -283,10 +275,13 @@ public class StickerController(AppDatabase db, StickerService st) : ControllerBa
var pack = await db.StickerPacks var pack = await db.StickerPacks
.Include(p => p.Publisher) .Include(p => p.Publisher)
.FirstOrDefaultAsync(e => e.Id == packId); .FirstOrDefaultAsync(e => e.Id == packId);
if (pack is null) if (pack is null)
return BadRequest("Sticker pack was not found."); return BadRequest("Sticker pack was not found.");
var stickersCount = await db.Stickers.CountAsync(s => s.PackId == packId);
if (stickersCount >= MaxStickersPerPack)
return BadRequest($"Sticker pack has reached maximum capacity of {MaxStickersPerPack} stickers.");
var image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId); var image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId);
if (image is null) if (image is null)
return BadRequest("Image was not found."); return BadRequest("Image was not found.");