5.1 FramesController.UploadImage (multipart)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -129,7 +129,7 @@ Each type lives in `src/FrameProcessor/Domain/`. Tests in `tests/FrameProcessor.
|
||||
|
||||
## Phase 5 — First end-to-end happy path (no MQTT yet)
|
||||
|
||||
### [ ] 5.1 `FramesController.UploadImage` (multipart)
|
||||
### [x] 5.1 `FramesController.UploadImage` (multipart)
|
||||
- Route: `POST /api/frames/{name}/image`.
|
||||
- Resolve frame by name → 404 if unknown.
|
||||
- Read multipart file part `image` (return 400 if missing).
|
||||
|
||||
55
src/FrameProcessor/Controllers/FramesController.cs
Normal file
55
src/FrameProcessor/Controllers/FramesController.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using FrameProcessor.Configuration;
|
||||
using FrameProcessor.Domain;
|
||||
using FrameProcessor.ImagePipeline;
|
||||
using FrameProcessor.Storage;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FrameProcessor.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/frames")]
|
||||
public sealed class FramesController : ControllerBase
|
||||
{
|
||||
private readonly FramesRegistry _frames;
|
||||
private readonly IImagePipeline _pipeline;
|
||||
private readonly ImageStore _store;
|
||||
|
||||
public FramesController(FramesRegistry frames, IImagePipeline pipeline, ImageStore store)
|
||||
{
|
||||
_frames = frames;
|
||||
_pipeline = pipeline;
|
||||
_store = store;
|
||||
}
|
||||
|
||||
[HttpPost("{name}/image")]
|
||||
public async Task<IActionResult> UploadImage(string name, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!FrameName.TryParse(name, out var frameName) || !_frames.TryGetByName(frameName, out var frame))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var file = Request.Form.Files.GetFile("image");
|
||||
if (file is null || file.Length == 0)
|
||||
{
|
||||
return BadRequest(new { error = "Missing 'image' file part." });
|
||||
}
|
||||
|
||||
byte[] pngBytes;
|
||||
await using (var stream = file.OpenReadStream())
|
||||
{
|
||||
pngBytes = _pipeline.Process(stream, frame);
|
||||
}
|
||||
|
||||
await _store.WriteAsync(frame.Mac, pngBytes, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
frame = frame.Name.Value,
|
||||
mac = frame.Mac.ToString(),
|
||||
url = $"/i/{frame.Mac}.png",
|
||||
processedAt = DateTimeOffset.UtcNow,
|
||||
mqttPublished = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using FrameProcessor.Configuration;
|
||||
using FrameProcessor.ImagePipeline;
|
||||
using FrameProcessor.Storage;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -34,6 +36,8 @@ builder.Services.AddOptions<FramesOptions>()
|
||||
.Bind(builder.Configuration);
|
||||
builder.Services.AddSingleton<FramesOptionsValidator>();
|
||||
builder.Services.AddSingleton<FramesRegistry>();
|
||||
builder.Services.AddSingleton<IImagePipeline, FrameProcessor.ImagePipeline.ImagePipeline>();
|
||||
builder.Services.AddSingleton<ImageStore>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user