Compare commits
No commits in common. "6a4aa8064a97da54be4791fab404e361821fca71" and "e20a7d34659e4389267dc12efeb0a6dd482e2266" have entirely different histories.
6a4aa8064a
...
e20a7d3465
3 changed files with 3 additions and 122 deletions
|
@ -1,11 +1,9 @@
|
||||||
using Coravel.Queuing.Interfaces;
|
using Coravel.Queuing.Interfaces;
|
||||||
using Foxnouns.Backend.Database;
|
using Foxnouns.Backend.Database;
|
||||||
using Foxnouns.Backend.Database.Models;
|
using Foxnouns.Backend.Database.Models;
|
||||||
using Foxnouns.Backend.Extensions;
|
|
||||||
using Foxnouns.Backend.Jobs;
|
using Foxnouns.Backend.Jobs;
|
||||||
using Foxnouns.Backend.Middleware;
|
using Foxnouns.Backend.Middleware;
|
||||||
using Foxnouns.Backend.Services;
|
using Foxnouns.Backend.Services;
|
||||||
using Foxnouns.Backend.Utils;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
@ -13,15 +11,11 @@ namespace Foxnouns.Backend.Controllers;
|
||||||
|
|
||||||
[Route("/api/v2/users/@me/flags")]
|
[Route("/api/v2/users/@me/flags")]
|
||||||
public class FlagsController(
|
public class FlagsController(
|
||||||
ILogger logger,
|
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
UserRendererService userRenderer,
|
UserRendererService userRenderer,
|
||||||
ObjectStorageService objectStorageService,
|
|
||||||
ISnowflakeGenerator snowflakeGenerator,
|
ISnowflakeGenerator snowflakeGenerator,
|
||||||
IQueue queue) : ApiControllerBase
|
IQueue queue) : ApiControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger = logger.ForContext<FlagsController>();
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize("identify")]
|
[Authorize("identify")]
|
||||||
[ProducesResponseType<IEnumerable<PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)]
|
[ProducesResponseType<IEnumerable<PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)]
|
||||||
|
@ -37,8 +31,6 @@ public class FlagsController(
|
||||||
[ProducesResponseType<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)]
|
[ProducesResponseType<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)]
|
||||||
public IActionResult CreateFlag([FromBody] CreateFlagRequest req)
|
public IActionResult CreateFlag([FromBody] CreateFlagRequest req)
|
||||||
{
|
{
|
||||||
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, req.Image));
|
|
||||||
|
|
||||||
var id = snowflakeGenerator.GenerateSnowflake();
|
var id = snowflakeGenerator.GenerateSnowflake();
|
||||||
|
|
||||||
queue.QueueInvocableWithPayload<CreateFlagInvocable, CreateFlagPayload>(
|
queue.QueueInvocableWithPayload<CreateFlagInvocable, CreateFlagPayload>(
|
||||||
|
@ -49,67 +41,7 @@ public class FlagsController(
|
||||||
|
|
||||||
public record CreateFlagRequest(string Name, string Image, string? Description);
|
public record CreateFlagRequest(string Name, string Image, string? Description);
|
||||||
|
|
||||||
private record CreateFlagResponse(Snowflake Id, string Name, string? Description);
|
public record CreateFlagResponse(Snowflake Id, string Name, string? Description);
|
||||||
|
|
||||||
[HttpPatch("{id}")]
|
|
||||||
[Authorize("user.update")]
|
|
||||||
public async Task<IActionResult> UpdateFlagAsync(Snowflake id, [FromBody] UpdateFlagRequest req)
|
|
||||||
{
|
|
||||||
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, null));
|
|
||||||
|
|
||||||
var flag = await db.PrideFlags.FirstOrDefaultAsync(f => f.Id == id && f.UserId == CurrentUser!.Id);
|
|
||||||
if (flag == null) throw new ApiError.NotFound("Unknown flag ID, or it's not your flag.");
|
|
||||||
|
|
||||||
if (req.Name != null) flag.Name = req.Name;
|
|
||||||
|
|
||||||
if (req.HasProperty(nameof(req.Description)))
|
|
||||||
flag.Description = req.Description;
|
|
||||||
|
|
||||||
db.Update(flag);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return Ok(ToResponse(flag));
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UpdateFlagRequest : PatchRequest
|
|
||||||
{
|
|
||||||
public string? Name { get; init; }
|
|
||||||
public string? Description { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
|
||||||
[Authorize("user.update")]
|
|
||||||
public async Task<IActionResult> DeleteFlagAsync(Snowflake id)
|
|
||||||
{
|
|
||||||
await using var tx = await db.Database.BeginTransactionAsync();
|
|
||||||
|
|
||||||
var flag = await db.PrideFlags.FirstOrDefaultAsync(f => f.Id == id && f.UserId == CurrentUser!.Id);
|
|
||||||
if (flag == null) throw new ApiError.NotFound("Unknown flag ID, or it's not your flag.");
|
|
||||||
|
|
||||||
var hash = flag.Hash;
|
|
||||||
|
|
||||||
db.PrideFlags.Remove(flag);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var flagCount = await db.PrideFlags.CountAsync(f => f.Hash == flag.Hash);
|
|
||||||
if (flagCount == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.Information("Deleting flag file {Hash} as it is no longer used by any flags", hash);
|
|
||||||
await objectStorageService.DeleteFlagAsync(hash);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Error(e, "Error deleting flag file {Hash}", hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else _logger.Debug("Flag file {Hash} is used by other flags, not deleting", hash);
|
|
||||||
|
|
||||||
await tx.CommitAsync();
|
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrideFlagResponse ToResponse(PrideFlag flag) =>
|
private PrideFlagResponse ToResponse(PrideFlag flag) =>
|
||||||
new(flag.Id, userRenderer.ImageUrlFor(flag), flag.Name, flag.Description);
|
new(flag.Id, userRenderer.ImageUrlFor(flag), flag.Name, flag.Description);
|
||||||
|
@ -119,52 +51,4 @@ public class FlagsController(
|
||||||
string ImageUrl,
|
string ImageUrl,
|
||||||
string Name,
|
string Name,
|
||||||
string? Description);
|
string? Description);
|
||||||
|
|
||||||
private static List<(string, ValidationError?)> ValidateFlag(string? name, string? description, string? imageData)
|
|
||||||
{
|
|
||||||
var errors = new List<(string, ValidationError?)>();
|
|
||||||
|
|
||||||
if (name != null)
|
|
||||||
{
|
|
||||||
switch (name.Length)
|
|
||||||
{
|
|
||||||
case < 1:
|
|
||||||
errors.Add(("name", ValidationError.LengthError("Name is too short", 1, 100, name.Length)));
|
|
||||||
break;
|
|
||||||
case > 100:
|
|
||||||
errors.Add(("name", ValidationError.LengthError("Name is too long", 1, 100, name.Length)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description != null)
|
|
||||||
{
|
|
||||||
switch (description.Length)
|
|
||||||
{
|
|
||||||
case < 1:
|
|
||||||
errors.Add(("description",
|
|
||||||
ValidationError.LengthError("Description is too short", 1, 100, description.Length)));
|
|
||||||
break;
|
|
||||||
case > 500:
|
|
||||||
errors.Add(("description",
|
|
||||||
ValidationError.LengthError("Description is too long", 1, 100, description.Length)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageData != null)
|
|
||||||
{
|
|
||||||
switch (imageData.Length)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
errors.Add(("image", ValidationError.GenericValidationError("Image cannot be empty", null)));
|
|
||||||
break;
|
|
||||||
case > 1_500_000:
|
|
||||||
errors.Add(("image", ValidationError.GenericValidationError("Image is too large", null)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -24,10 +24,6 @@ public static class AvatarObjectExtensions
|
||||||
CancellationToken ct = default) =>
|
CancellationToken ct = default) =>
|
||||||
await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct);
|
await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct);
|
||||||
|
|
||||||
public static async Task DeleteFlagAsync(this ObjectStorageService objectStorageService, string hash,
|
|
||||||
CancellationToken ct = default) =>
|
|
||||||
await objectStorageService.RemoveObjectAsync(CreateFlagInvocable.Path(hash), ct);
|
|
||||||
|
|
||||||
public static async Task<(string Hash, Stream Image)> ConvertBase64UriToImage(this string uri, int size, bool crop)
|
public static async Task<(string Hash, Stream Image)> ConvertBase64UriToImage(this string uri, int size, bool crop)
|
||||||
{
|
{
|
||||||
if (!uri.StartsWith("data:image/"))
|
if (!uri.StartsWith("data:image/"))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
using Coravel.Invocable;
|
using Coravel.Invocable;
|
||||||
using Foxnouns.Backend.Database;
|
using Foxnouns.Backend.Database;
|
||||||
using Foxnouns.Backend.Database.Models;
|
using Foxnouns.Backend.Database.Models;
|
||||||
|
@ -44,5 +45,5 @@ public class CreateFlagInvocable(DatabaseContext db, ObjectStorageService object
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Path(string hash) => $"flags/{hash}.webp";
|
private static string Path(string hash) => $"flags/{hash}.webp";
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue