feat(backend): add create flag endpoint and job
This commit is contained in:
		
							parent
							
								
									ff2ba1fb1b
								
							
						
					
					
						commit
						14e6e35cb7
					
				
					 4 changed files with 87 additions and 5 deletions
				
			
		|  | @ -1,4 +1,7 @@ | ||||||
|  | using Coravel.Queuing.Interfaces; | ||||||
| using Foxnouns.Backend.Database; | using Foxnouns.Backend.Database; | ||||||
|  | using Foxnouns.Backend.Database.Models; | ||||||
|  | using Foxnouns.Backend.Jobs; | ||||||
| using Foxnouns.Backend.Middleware; | using Foxnouns.Backend.Middleware; | ||||||
| using Foxnouns.Backend.Services; | using Foxnouns.Backend.Services; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
|  | @ -7,7 +10,11 @@ using Microsoft.EntityFrameworkCore; | ||||||
| namespace Foxnouns.Backend.Controllers; | namespace Foxnouns.Backend.Controllers; | ||||||
| 
 | 
 | ||||||
| [Route("/api/v2/users/@me/flags")] | [Route("/api/v2/users/@me/flags")] | ||||||
| public class FlagsController(DatabaseContext db, UserRendererService userRenderer) : ApiControllerBase | public class FlagsController( | ||||||
|  |     DatabaseContext db, | ||||||
|  |     UserRendererService userRenderer, | ||||||
|  |     ISnowflakeGenerator snowflakeGenerator, | ||||||
|  |     IQueue queue) : ApiControllerBase | ||||||
| { | { | ||||||
|     [HttpGet] |     [HttpGet] | ||||||
|     [Authorize("identify")] |     [Authorize("identify")] | ||||||
|  | @ -16,10 +23,29 @@ public class FlagsController(DatabaseContext db, UserRendererService userRendere | ||||||
|     { |     { | ||||||
|         var flags = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).ToListAsync(ct); |         var flags = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).ToListAsync(ct); | ||||||
| 
 | 
 | ||||||
|         return Ok(flags.Select(f => new PrideFlagResponse( |         return Ok(flags.Select(ToResponse)); | ||||||
|             f.Id, userRenderer.ImageUrlFor(f), f.Name, f.Description))); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     [HttpPost] | ||||||
|  |     [Authorize("user.update")] | ||||||
|  |     [ProducesResponseType<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)] | ||||||
|  |     public IActionResult CreateFlag([FromBody] CreateFlagRequest req) | ||||||
|  |     { | ||||||
|  |         var id = snowflakeGenerator.GenerateSnowflake(); | ||||||
|  | 
 | ||||||
|  |         queue.QueueInvocableWithPayload<CreateFlagInvocable, CreateFlagPayload>( | ||||||
|  |             new CreateFlagPayload(id, CurrentUser!.Id, req.Name, req.Image, req.Description)); | ||||||
|  | 
 | ||||||
|  |         return Accepted(new CreateFlagResponse(id, req.Name, req.Description)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public record CreateFlagRequest(string Name, string Image, string? Description); | ||||||
|  | 
 | ||||||
|  |     public record CreateFlagResponse(Snowflake Id, string Name, string? Description); | ||||||
|  | 
 | ||||||
|  |     private PrideFlagResponse ToResponse(PrideFlag flag) => | ||||||
|  |         new(flag.Id, userRenderer.ImageUrlFor(flag), flag.Name, flag.Description); | ||||||
|  | 
 | ||||||
|     private record PrideFlagResponse( |     private record PrideFlagResponse( | ||||||
|         Snowflake Id, |         Snowflake Id, | ||||||
|         string ImageUrl, |         string ImageUrl, | ||||||
|  |  | ||||||
|  | @ -99,7 +99,8 @@ public static class WebApplicationExtensions | ||||||
|                 .AddHostedService<PeriodicTasksService>() |                 .AddHostedService<PeriodicTasksService>() | ||||||
|                 // Transient jobs |                 // Transient jobs | ||||||
|                 .AddTransient<MemberAvatarUpdateInvocable>() |                 .AddTransient<MemberAvatarUpdateInvocable>() | ||||||
|                 .AddTransient<UserAvatarUpdateInvocable>(); |                 .AddTransient<UserAvatarUpdateInvocable>() | ||||||
|  |                 .AddTransient<CreateFlagInvocable>(); | ||||||
| 
 | 
 | ||||||
|             if (!config.Logging.EnableMetrics) |             if (!config.Logging.EnableMetrics) | ||||||
|                 services.AddHostedService<BackgroundMetricsCollectionService>(); |                 services.AddHostedService<BackgroundMetricsCollectionService>(); | ||||||
|  |  | ||||||
							
								
								
									
										53
									
								
								Foxnouns.Backend/Jobs/CreateFlagInvocable.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								Foxnouns.Backend/Jobs/CreateFlagInvocable.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | using System.Security.Cryptography; | ||||||
|  | using Coravel.Invocable; | ||||||
|  | using Foxnouns.Backend.Database; | ||||||
|  | using Foxnouns.Backend.Database.Models; | ||||||
|  | using Foxnouns.Backend.Extensions; | ||||||
|  | using Foxnouns.Backend.Services; | ||||||
|  | 
 | ||||||
|  | namespace Foxnouns.Backend.Jobs; | ||||||
|  | 
 | ||||||
|  | public class CreateFlagInvocable(DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger) | ||||||
|  |     : IInvocable, IInvocableWithPayload<CreateFlagPayload> | ||||||
|  | { | ||||||
|  |     private readonly ILogger _logger = logger.ForContext<CreateFlagInvocable>(); | ||||||
|  |     public required CreateFlagPayload Payload { get; set; } | ||||||
|  | 
 | ||||||
|  |     public async Task Invoke() | ||||||
|  |     { | ||||||
|  |         _logger.Information("Creating flag {FlagId} for user {UserId} with image data length {DataLength}", Payload.Id, | ||||||
|  |             Payload.UserId, Payload.ImageData.Length); | ||||||
|  | 
 | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             var image = await Payload.ImageData.ConvertBase64UriToImage(size: 256, crop: false); | ||||||
|  |             image.Seek(0, SeekOrigin.Begin); | ||||||
|  |             var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower(); | ||||||
|  |             image.Seek(0, SeekOrigin.Begin); | ||||||
|  | 
 | ||||||
|  |             await objectStorageService.PutObjectAsync(Path(hash), image, "image/webp"); | ||||||
|  | 
 | ||||||
|  |             var flag = new PrideFlag | ||||||
|  |             { | ||||||
|  |                 Id = Payload.Id, | ||||||
|  |                 UserId = Payload.UserId, | ||||||
|  |                 Hash = hash, | ||||||
|  |                 Name = Payload.Name, | ||||||
|  |                 Description = Payload.Description | ||||||
|  |             }; | ||||||
|  |             db.Add(flag); | ||||||
|  | 
 | ||||||
|  |             await db.SaveChangesAsync(); | ||||||
|  | 
 | ||||||
|  |             _logger.Information("Uploaded flag {FlagId} with hash {Hash}", flag.Id, flag.Hash); | ||||||
|  |         } | ||||||
|  |         catch (ArgumentException ae) | ||||||
|  |         { | ||||||
|  |             _logger.Warning("Invalid data URI for flag {FlagId}: {Reason}", Payload.Id, ae.Message); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         throw new NotImplementedException(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static string Path(string hash) => $"flags/{hash}.webp"; | ||||||
|  | } | ||||||
|  | @ -3,3 +3,5 @@ using Foxnouns.Backend.Database; | ||||||
| namespace Foxnouns.Backend.Jobs; | namespace Foxnouns.Backend.Jobs; | ||||||
| 
 | 
 | ||||||
| public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar); | public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar); | ||||||
|  | 
 | ||||||
|  | public record CreateFlagPayload(Snowflake Id, Snowflake UserId, string Name, string ImageData, string? Description); | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue