✨ Stickers & packs api
This commit is contained in:
parent
790dcafeb0
commit
eab775e224
@ -60,6 +60,9 @@ public class AppDatabase(
|
|||||||
public DbSet<Chat.MessageStatus> ChatStatuses { get; set; }
|
public DbSet<Chat.MessageStatus> ChatStatuses { get; set; }
|
||||||
public DbSet<Chat.MessageReaction> ChatReactions { get; set; }
|
public DbSet<Chat.MessageReaction> ChatReactions { get; set; }
|
||||||
|
|
||||||
|
public DbSet<Sticker.Sticker> Stickers { get; set; }
|
||||||
|
public DbSet<Sticker.StickerPack> StickerPacks { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
|
||||||
@ -81,18 +84,22 @@ public class AppDatabase(
|
|||||||
context.Set<PermissionGroup>().Add(new PermissionGroup
|
context.Set<PermissionGroup>().Add(new PermissionGroup
|
||||||
{
|
{
|
||||||
Key = "default",
|
Key = "default",
|
||||||
Nodes =
|
Nodes = new List<string>
|
||||||
{
|
{
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "posts.create", true),
|
"posts.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "posts.react", true),
|
"posts.react",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true),
|
"publishers.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "files.create", true),
|
"files.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "chat.create", true),
|
"chat.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "chat.messages.create", true),
|
"chat.messages.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "chat.realtime.create", true),
|
"chat.realtime.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "accounts.statuses.create", true),
|
"accounts.statuses.create",
|
||||||
PermissionService.NewPermissionNode("group:default", "global", "accounts.statuses.update", true)
|
"accounts.statuses.update",
|
||||||
}
|
"stickers.pack.create",
|
||||||
|
"stickers.create"
|
||||||
|
}.Select(permission =>
|
||||||
|
PermissionService.NewPermissionNode("group:default", "global", permission, true))
|
||||||
|
.ToList()
|
||||||
});
|
});
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
await context.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
2693
DysonNetwork.Sphere/Migrations/20250510124730_AddStickerAndPacks.Designer.cs
generated
Normal file
2693
DysonNetwork.Sphere/Migrations/20250510124730_AddStickerAndPacks.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddStickerAndPacks : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "sticker_packs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||||
|
prefix = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
|
publisher_id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_sticker_packs", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_sticker_packs_publishers_publisher_id",
|
||||||
|
column: x => x.publisher_id,
|
||||||
|
principalTable: "publishers",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "stickers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
slug = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
|
image_id = table.Column<string>(type: "character varying(128)", nullable: false),
|
||||||
|
pack_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_stickers", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_stickers_files_image_id",
|
||||||
|
column: x => x.image_id,
|
||||||
|
principalTable: "files",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_stickers_sticker_packs_pack_id",
|
||||||
|
column: x => x.pack_id,
|
||||||
|
principalTable: "sticker_packs",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_sticker_packs_publisher_id",
|
||||||
|
table: "sticker_packs",
|
||||||
|
column: "publisher_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_stickers_image_id",
|
||||||
|
table: "stickers",
|
||||||
|
column: "image_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_stickers_pack_id",
|
||||||
|
table: "stickers",
|
||||||
|
column: "pack_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "stickers");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "sticker_packs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1763,6 +1763,102 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("realm_members", (string)null);
|
b.ToTable("realm_members", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("ImageId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("image_id");
|
||||||
|
|
||||||
|
b.Property<Guid>("PackId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("pack_id");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("slug");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_stickers");
|
||||||
|
|
||||||
|
b.HasIndex("ImageId")
|
||||||
|
.HasDatabaseName("ix_stickers_image_id");
|
||||||
|
|
||||||
|
b.HasIndex("PackId")
|
||||||
|
.HasDatabaseName("ix_stickers_pack_id");
|
||||||
|
|
||||||
|
b.ToTable("stickers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<string>("Prefix")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("prefix");
|
||||||
|
|
||||||
|
b.Property<long>("PublisherId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("publisher_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_sticker_packs");
|
||||||
|
|
||||||
|
b.HasIndex("PublisherId")
|
||||||
|
.HasDatabaseName("ix_sticker_packs_publisher_id");
|
||||||
|
|
||||||
|
b.ToTable("sticker_packs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
@ -2421,6 +2517,39 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Realm");
|
b.Navigation("Realm");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.Sticker", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Image")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ImageId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_stickers_files_image_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PackId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_stickers_sticker_packs_pack_id");
|
||||||
|
|
||||||
|
b.Navigation("Image");
|
||||||
|
|
||||||
|
b.Navigation("Pack");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PublisherId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_sticker_packs_publishers_publisher_id");
|
||||||
|
|
||||||
|
b.Navigation("Publisher");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
|
@ -15,6 +15,7 @@ using DysonNetwork.Sphere.Localization;
|
|||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
|
using DysonNetwork.Sphere.Sticker;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Localization;
|
using Microsoft.AspNetCore.Localization;
|
||||||
@ -162,6 +163,7 @@ builder.Services.AddScoped<PostService>();
|
|||||||
builder.Services.AddScoped<RealmService>();
|
builder.Services.AddScoped<RealmService>();
|
||||||
builder.Services.AddScoped<ChatRoomService>();
|
builder.Services.AddScoped<ChatRoomService>();
|
||||||
builder.Services.AddScoped<ChatService>();
|
builder.Services.AddScoped<ChatService>();
|
||||||
|
builder.Services.AddScoped<StickerService>();
|
||||||
|
|
||||||
// Timed task
|
// Timed task
|
||||||
|
|
||||||
|
28
DysonNetwork.Sphere/Sticker/Sticker.cs
Normal file
28
DysonNetwork.Sphere/Sticker/Sticker.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Sticker;
|
||||||
|
|
||||||
|
public class Sticker : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||||
|
|
||||||
|
public string ImageId { get; set; } = null!;
|
||||||
|
public CloudFile Image { get; set; } = null!;
|
||||||
|
|
||||||
|
public Guid PackId { get; set; }
|
||||||
|
public StickerPack Pack { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StickerPack : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string Name { get; set; } = null!;
|
||||||
|
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
||||||
|
[MaxLength(128)] public string Prefix { get; set; } = null!;
|
||||||
|
|
||||||
|
public long PublisherId { get; set; }
|
||||||
|
public Post.Publisher Publisher { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
305
DysonNetwork.Sphere/Sticker/StickerController.cs
Normal file
305
DysonNetwork.Sphere/Sticker/StickerController.cs
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using DysonNetwork.Sphere.Permission;
|
||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Sticker;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/stickers")]
|
||||||
|
public class StickerController(AppDatabase db, StickerService st) : ControllerBase
|
||||||
|
{
|
||||||
|
private async Task<IActionResult> _CheckStickerPackPermissions(Guid packId, Account.Account currentUser, PublisherMemberRole requiredRole)
|
||||||
|
{
|
||||||
|
var pack = await db.StickerPacks
|
||||||
|
.Include(p => p.Publisher)
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == packId);
|
||||||
|
|
||||||
|
if (pack is null)
|
||||||
|
return NotFound("Sticker pack not found");
|
||||||
|
|
||||||
|
var member = await db.PublisherMembers
|
||||||
|
.FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId);
|
||||||
|
if (member is null)
|
||||||
|
return StatusCode(403, "You are not a member of this publisher");
|
||||||
|
if (member.Role < requiredRole)
|
||||||
|
return StatusCode(403, $"You need to be at least a {requiredRole} to perform this action");
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<List<StickerPack>>> ListStickerPacks([FromQuery] int offset = 0,
|
||||||
|
[FromQuery] int take = 20)
|
||||||
|
{
|
||||||
|
var totalCount = await db.StickerPacks.CountAsync();
|
||||||
|
var packs = await db.StickerPacks
|
||||||
|
.OrderByDescending(e => e.CreatedAt)
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(take)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
Response.Headers["X-Total"] = totalCount.ToString();
|
||||||
|
return Ok(packs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}")]
|
||||||
|
public async Task<ActionResult<StickerPack>> GetStickerPack(Guid id)
|
||||||
|
{
|
||||||
|
var pack = await db.StickerPacks
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
|
||||||
|
if (pack is null) return NotFound();
|
||||||
|
return Ok(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StickerPackRequest
|
||||||
|
{
|
||||||
|
[MaxLength(1024)] public string? Name { get; set; }
|
||||||
|
[MaxLength(4096)] public string? Description { get; set; }
|
||||||
|
[MaxLength(128)] public string? Prefix { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[RequiredPermission("global", "sticker.packs.create")]
|
||||||
|
public async Task<ActionResult<StickerPack>> CreateStickerPack([FromBody] StickerPackRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(request.Name))
|
||||||
|
return BadRequest("Name is required");
|
||||||
|
if (string.IsNullOrEmpty(request.Prefix))
|
||||||
|
return BadRequest("Prefix is required");
|
||||||
|
|
||||||
|
var publisherName = Request.Headers["X-Pub"].ToString();
|
||||||
|
if (string.IsNullOrEmpty(publisherName))
|
||||||
|
return BadRequest("Publisher name is required in X-Pub header");
|
||||||
|
|
||||||
|
var publisher =
|
||||||
|
await db.Publishers.FirstOrDefaultAsync(p => p.Name == publisherName && p.AccountId == currentUser.Id);
|
||||||
|
if (publisher == null)
|
||||||
|
return BadRequest("Publisher not found");
|
||||||
|
|
||||||
|
var pack = new StickerPack
|
||||||
|
{
|
||||||
|
Name = request.Name!,
|
||||||
|
Description = request.Description ?? string.Empty,
|
||||||
|
Prefix = request.Prefix!,
|
||||||
|
PublisherId = publisher.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
db.StickerPacks.Add(pack);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return Ok(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id:guid}")]
|
||||||
|
public async Task<ActionResult<StickerPack>> UpdateStickerPack(Guid id, [FromBody] StickerPackRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var pack = await db.StickerPacks
|
||||||
|
.Include(p => p.Publisher)
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
|
||||||
|
if (pack is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var member = await db.PublisherMembers
|
||||||
|
.FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId);
|
||||||
|
|
||||||
|
if (member is null)
|
||||||
|
return StatusCode(403, "You are not a member of this publisher");
|
||||||
|
|
||||||
|
if (member.Role < PublisherMemberRole.Editor)
|
||||||
|
return StatusCode(403, "You need to be at least an editor to update sticker packs");
|
||||||
|
|
||||||
|
if (request.Name is not null)
|
||||||
|
pack.Name = request.Name;
|
||||||
|
if (request.Description is not null)
|
||||||
|
pack.Description = request.Description;
|
||||||
|
if (request.Prefix is not null)
|
||||||
|
pack.Prefix = request.Prefix;
|
||||||
|
|
||||||
|
db.StickerPacks.Update(pack);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return Ok(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id:guid}")]
|
||||||
|
public async Task<IActionResult> DeleteStickerPack(Guid id)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var pack = await db.StickerPacks
|
||||||
|
.Include(p => p.Publisher)
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
if (pack is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var member = await db.PublisherMembers
|
||||||
|
.FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.PublisherId == pack.PublisherId);
|
||||||
|
if (member is null)
|
||||||
|
return StatusCode(403, "You are not a member of this publisher");
|
||||||
|
if (member.Role < PublisherMemberRole.Editor)
|
||||||
|
return StatusCode(403, "You need to be an editor to delete sticker packs");
|
||||||
|
|
||||||
|
await st.DeleteStickerPackAsync(pack);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("{packId:guid}/stickers")]
|
||||||
|
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
|
||||||
|
.Where(s => s.Pack.Id == packId)
|
||||||
|
.Include(e => e.Pack)
|
||||||
|
.Include(e => e.Image)
|
||||||
|
.OrderByDescending(e => e.CreatedAt)
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(take)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
Response.Headers["X-Total"] = totalCount.ToString();
|
||||||
|
return Ok(stickers);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("lookup/{identifier}")]
|
||||||
|
public async Task<ActionResult<Sticker>> GetStickerByIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
IQueryable<Sticker> query = db.Stickers
|
||||||
|
.Include(e => e.Pack)
|
||||||
|
.Include(e => e.Image);
|
||||||
|
query = Guid.TryParse(identifier, out var guid)
|
||||||
|
? query.Where(e => e.Id == guid)
|
||||||
|
: query.Where(e => e.Pack.Prefix + e.Slug == identifier);
|
||||||
|
var sticker = await query.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (sticker is null) return NotFound();
|
||||||
|
return Ok(sticker);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{packId:guid}/stickers/{id:guid}")]
|
||||||
|
public async Task<ActionResult<Sticker>> GetSticker(Guid packId, Guid id)
|
||||||
|
{
|
||||||
|
var sticker = await db.Stickers
|
||||||
|
.Where(s => s.Pack.Id == packId && s.Id == id)
|
||||||
|
.Include(e => e.Pack)
|
||||||
|
.Include(e => e.Image)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (sticker is null) return NotFound();
|
||||||
|
|
||||||
|
return Ok(sticker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StickerRequest
|
||||||
|
{
|
||||||
|
[MaxLength(128)] public string? Slug { get; set; } = null!;
|
||||||
|
public string? ImageId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{packId:guid}/stickers/{id:guid}")]
|
||||||
|
public async Task<IActionResult> UpdateSticker(Guid packId, Guid id, [FromBody] StickerRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor);
|
||||||
|
if (permissionCheck is not OkResult)
|
||||||
|
return permissionCheck;
|
||||||
|
|
||||||
|
var sticker = await db.Stickers
|
||||||
|
.Include(s => s.Pack)
|
||||||
|
.ThenInclude(p => p.Publisher)
|
||||||
|
.FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId);
|
||||||
|
|
||||||
|
if (sticker is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
if (request.Slug is not null)
|
||||||
|
sticker.Slug = request.Slug;
|
||||||
|
|
||||||
|
CloudFile? image = null;
|
||||||
|
if (request.ImageId is not null)
|
||||||
|
{
|
||||||
|
image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId);
|
||||||
|
if (image is null)
|
||||||
|
return BadRequest("Image not found");
|
||||||
|
sticker.ImageId = request.ImageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
sticker = await st.UpdateStickerAsync(sticker, image);
|
||||||
|
return Ok(sticker);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{packId:guid}/stickers/{id:guid}")]
|
||||||
|
public async Task<IActionResult> DeleteSticker(Guid packId, Guid id)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor);
|
||||||
|
if (permissionCheck is not OkResult)
|
||||||
|
return permissionCheck;
|
||||||
|
|
||||||
|
var sticker = await db.Stickers
|
||||||
|
.Include(s => s.Pack)
|
||||||
|
.ThenInclude(p => p.Publisher)
|
||||||
|
.FirstOrDefaultAsync(e => e.Id == id && e.Pack.Id == packId);
|
||||||
|
|
||||||
|
if (sticker is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
await st.DeleteStickerAsync(sticker);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{packId:guid}/stickers")]
|
||||||
|
[RequiredPermission("global", "stickers.create")]
|
||||||
|
public async Task<IActionResult> CreateSticker(Guid packId, [FromBody] StickerRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Slug))
|
||||||
|
return BadRequest("Slug is required.");
|
||||||
|
if (request.ImageId is null)
|
||||||
|
return BadRequest("Image is required.");
|
||||||
|
|
||||||
|
var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, PublisherMemberRole.Editor);
|
||||||
|
if (permissionCheck is not OkResult)
|
||||||
|
return permissionCheck;
|
||||||
|
|
||||||
|
var pack = await db.StickerPacks
|
||||||
|
.Include(p => p.Publisher)
|
||||||
|
.FirstOrDefaultAsync(e => e.Id == packId);
|
||||||
|
|
||||||
|
if (pack is null)
|
||||||
|
return BadRequest("Sticker pack was not found.");
|
||||||
|
|
||||||
|
var image = await db.Files.FirstOrDefaultAsync(e => e.Id == request.ImageId);
|
||||||
|
if (image is null)
|
||||||
|
return BadRequest("Image was not found.");
|
||||||
|
|
||||||
|
var sticker = new Sticker
|
||||||
|
{
|
||||||
|
Slug = request.Slug,
|
||||||
|
ImageId = image.Id,
|
||||||
|
Image = image,
|
||||||
|
Pack = pack
|
||||||
|
};
|
||||||
|
|
||||||
|
sticker = await st.CreateStickerAsync(sticker);
|
||||||
|
return Ok(sticker);
|
||||||
|
}
|
||||||
|
}
|
55
DysonNetwork.Sphere/Sticker/StickerService.cs
Normal file
55
DysonNetwork.Sphere/Sticker/StickerService.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Sticker;
|
||||||
|
|
||||||
|
public class StickerService(AppDatabase db, FileService fs)
|
||||||
|
{
|
||||||
|
public async Task<Sticker> CreateStickerAsync(Sticker sticker)
|
||||||
|
{
|
||||||
|
db.Stickers.Add(sticker);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await fs.MarkUsageAsync(sticker.Image, 1);
|
||||||
|
|
||||||
|
return sticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Sticker> UpdateStickerAsync(Sticker sticker, CloudFile? newImage)
|
||||||
|
{
|
||||||
|
if (newImage != null)
|
||||||
|
{
|
||||||
|
await fs.MarkUsageAsync(sticker.Image, -1);
|
||||||
|
sticker.Image = newImage;
|
||||||
|
await fs.MarkUsageAsync(sticker.Image, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Stickers.Update(sticker);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return sticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteStickerAsync(Sticker sticker)
|
||||||
|
{
|
||||||
|
db.Stickers.Remove(sticker);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
await fs.MarkUsageAsync(sticker.Image, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteStickerPackAsync(StickerPack pack)
|
||||||
|
{
|
||||||
|
var stickers = await db.Stickers
|
||||||
|
.Include(s => s.Image)
|
||||||
|
.Where(s => s.PackId == pack.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var images = stickers.Select(s => s.Image).ToList();
|
||||||
|
|
||||||
|
db.Stickers.RemoveRange(stickers);
|
||||||
|
db.StickerPacks.Remove(pack);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await fs.MarkUsageRangeAsync(images, -1);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user