Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [2.0.4](https://github.com/sondresjolyst/garge-api/compare/v2.0.3...v2.0.4) (2026-04-23)


### Bug Fixes

* **electricity:** surface NordPool warnings and refresh today+tomorrow on daily run ([8fd04f1](https://github.com/sondresjolyst/garge-api/commit/8fd04f1ac351f89972267e93017a86c378bd1435))
* **electricity:** surface NordPool warnings and refresh today+tomorrow on daily run ([#114](https://github.com/sondresjolyst/garge-api/issues/114)) ([2420724](https://github.com/sondresjolyst/garge-api/commit/24207249c7ae7aa0813043673e5f3685eb9da805))

## [2.0.3](https://github.com/sondresjolyst/garge-api/compare/v2.0.2...v2.0.3) (2026-04-21)


Expand Down
74 changes: 74 additions & 0 deletions Controllers/SensorController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,5 +1066,79 @@ public async Task<IActionResult> GetLatestSensorData(int sensorId)
var dto = _mapper.Map<SensorDataDto>(latestData);
return Ok(dto);
}

[HttpPost("{sensorId}/photo")]
[SwaggerOperation(Summary = "Upload or replace a photo for a sensor.")]
[SwaggerResponse(200, "Photo saved.")]
[SwaggerResponse(400, "Invalid request.")]
[SwaggerResponse(403, "No access to sensor.")]
public async Task<IActionResult> UploadSensorPhoto(int sensorId, [FromBody] UploadSensorPhotoDto dto)
{
if (!await UserCanAccessSensorAsync(sensorId))
return Forbid();

if (string.IsNullOrWhiteSpace(dto.Data) || string.IsNullOrWhiteSpace(dto.ContentType))
return BadRequest(new { message = "Data and ContentType are required." });

var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)!;

var existing = await _context.SensorPhotos.FirstOrDefaultAsync(sp => sp.SensorId == sensorId);
if (existing != null)
{
existing.Data = dto.Data;
existing.ContentType = dto.ContentType;
existing.UserId = userId;
existing.CreatedAt = DateTime.UtcNow;
}
else
{
_context.SensorPhotos.Add(new Models.Sensor.SensorPhoto
{
SensorId = sensorId,
UserId = userId,
Data = dto.Data,
ContentType = dto.ContentType
});
}

await _context.SaveChangesAsync();
return Ok(new { message = "Photo saved." });
}

[HttpGet("{sensorId}/photo")]
[SwaggerOperation(Summary = "Get the photo for a sensor.")]
[SwaggerResponse(200, "Photo data.")]
[SwaggerResponse(403, "No access to sensor.")]
[SwaggerResponse(404, "No photo found.")]
public async Task<IActionResult> GetSensorPhoto(int sensorId)
{
if (!await UserCanAccessSensorAsync(sensorId))
return Forbid();

var photo = await _context.SensorPhotos.FirstOrDefaultAsync(sp => sp.SensorId == sensorId);
if (photo == null)
return NotFound(new { message = "No photo found." });

return Ok(new { data = photo.Data, contentType = photo.ContentType });
}

[HttpDelete("{sensorId}/photo")]
[SwaggerOperation(Summary = "Delete the photo for a sensor.")]
[SwaggerResponse(200, "Photo deleted.")]
[SwaggerResponse(403, "No access to sensor.")]
[SwaggerResponse(404, "No photo found.")]
public async Task<IActionResult> DeleteSensorPhoto(int sensorId)
{
if (!await UserCanAccessSensorAsync(sensorId))
return Forbid();

var photo = await _context.SensorPhotos.FirstOrDefaultAsync(sp => sp.SensorId == sensorId);
if (photo == null)
return NotFound(new { message = "No photo found." });

_context.SensorPhotos.Remove(photo);
await _context.SaveChangesAsync();
return Ok(new { message = "Photo deleted." });
}
}
}
14 changes: 14 additions & 0 deletions Dtos/Sensor/UploadSensorPhotoDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;

namespace garge_api.Dtos.Sensor
{
public class UploadSensorPhotoDto
{
[Required]
public required string Data { get; set; }

[Required]
[MaxLength(50)]
public required string ContentType { get; set; }
}
}
Loading