Skip to content

Commit 7ccd907

Browse files
committed
more progress
1 parent d880fe7 commit 7ccd907

34 files changed

Lines changed: 404 additions & 131 deletions

‎RepositoryServer/Config/FirmwareAdvisoryConfig.cs‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎RepositoryServer/Config/FirmwareConfig.cs‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,4 @@ public sealed class FirmwareConfig
1717
/// How long a release may remain in <c>editing</c> status before the cleanup job aborts it.
1818
/// </summary>
1919
public TimeSpan EditingReleaseTtl { get; init; } = TimeSpan.FromDays(7);
20-
21-
/// <summary>
22-
/// Security / compatibility advisories exposed on the manifest endpoint.
23-
/// Served from configuration, not from the database.
24-
/// </summary>
25-
public List<FirmwareAdvisoryConfig> Advisories { get; init; } = new();
2620
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.EntityFrameworkCore;
5+
using OpenShock.RepositoryServer.Enums;
6+
using OpenShock.RepositoryServer.Models.Firmware;
7+
using OpenShock.RepositoryServer.Problems;
8+
using OpenShock.RepositoryServer.RepoServerDb;
9+
10+
namespace OpenShock.RepositoryServer.Controllers.V2.Firmware.Admin;
11+
12+
[ApiVersion("2.0")]
13+
[ApiController]
14+
[Route("/v{version:apiVersion}/firmware/admin/advisories")]
15+
[Authorize(AuthenticationSchemes = AuthSchemas.AdminToken)]
16+
public class AdvisoriesController : OpenShockControllerBase
17+
{
18+
private readonly RepoServerContext _db;
19+
20+
public AdvisoriesController(RepoServerContext db)
21+
{
22+
_db = db;
23+
}
24+
25+
[HttpPost]
26+
public async Task<IActionResult> CreateAdvisory([FromBody] UpsertFirmwareAdvisoryRequest request, CancellationToken ct)
27+
{
28+
if (!TryParseSeverity(request.Severity, out var severity))
29+
{
30+
return Problem(FirmwareError.FirmwareInvalidAdvisorySeverity);
31+
}
32+
33+
var advisory = new FirmwareAdvisory
34+
{
35+
Id = Guid.NewGuid(),
36+
Severity = severity,
37+
Title = request.Title,
38+
Content = request.Content,
39+
AffectedVersions = request.AffectedVersions,
40+
Url = request.Url
41+
};
42+
_db.FirmwareAdvisories.Add(advisory);
43+
await _db.SaveChangesAsync(ct);
44+
45+
return Created((string?)null, ToDto(advisory));
46+
}
47+
48+
[HttpGet]
49+
public async Task<IActionResult> ListAdvisories(CancellationToken ct)
50+
{
51+
var rows = await _db.FirmwareAdvisories
52+
.OrderBy(a => a.Severity)
53+
.ThenBy(a => a.Title)
54+
.ToListAsync(ct);
55+
56+
return Ok(rows.Select(ToDto));
57+
}
58+
59+
[HttpPut("{id:guid}")]
60+
public async Task<IActionResult> UpdateAdvisory(
61+
[FromRoute] Guid id,
62+
[FromBody] UpsertFirmwareAdvisoryRequest request,
63+
CancellationToken ct)
64+
{
65+
if (!TryParseSeverity(request.Severity, out var severity))
66+
{
67+
return Problem(FirmwareError.FirmwareInvalidAdvisorySeverity);
68+
}
69+
70+
var advisory = await _db.FirmwareAdvisories.FirstOrDefaultAsync(a => a.Id == id, ct);
71+
if (advisory is null)
72+
{
73+
return Problem(FirmwareError.FirmwareAdvisoryNotFound);
74+
}
75+
76+
advisory.Severity = severity;
77+
advisory.Title = request.Title;
78+
advisory.Content = request.Content;
79+
advisory.AffectedVersions = request.AffectedVersions;
80+
advisory.Url = request.Url;
81+
await _db.SaveChangesAsync(ct);
82+
83+
return Ok(ToDto(advisory));
84+
}
85+
86+
[HttpDelete("{id:guid}")]
87+
public async Task<IActionResult> DeleteAdvisory([FromRoute] Guid id, CancellationToken ct)
88+
{
89+
var deleted = await _db.FirmwareAdvisories.Where(a => a.Id == id).ExecuteDeleteAsync(ct);
90+
if (deleted <= 0)
91+
{
92+
return Problem(FirmwareError.FirmwareAdvisoryNotFound);
93+
}
94+
95+
return NoContent();
96+
}
97+
98+
private static bool TryParseSeverity(string value, out AdvisorySeverity severity) =>
99+
Enum.TryParse(value, true, out severity);
100+
101+
private static FirmwareAdvisoryAdminDto ToDto(FirmwareAdvisory a) => new()
102+
{
103+
Id = a.Id,
104+
Severity = a.Severity.ToString().ToLowerInvariant(),
105+
Title = a.Title,
106+
Content = a.Content,
107+
AffectedVersions = a.AffectedVersions,
108+
Url = a.Url
109+
};
110+
}

‎RepositoryServer/Controllers/V2/Firmware/Admin/BoardsAdminController.cs‎

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ public BoardsAdminController(RepoServerContext db)
2222
_db = db;
2323
}
2424

25-
[HttpPut("{boardId}")]
26-
public async Task<IActionResult> UpsertBoard(
27-
[FromRoute] string boardId,
25+
/// <summary>
26+
/// Create a new board. <c>Name</c> is the unique identifier visible to clients.
27+
/// </summary>
28+
[HttpPost]
29+
public async Task<IActionResult> CreateBoard(
2830
[FromBody] CreateFirmwareBoardRequest request,
2931
CancellationToken ct)
3032
{
@@ -33,46 +35,58 @@ public async Task<IActionResult> UpsertBoard(
3335
return Problem(FirmwareError.FirmwareChipNotFound);
3436
}
3537

36-
var requiredArtifactTypes = Array.Empty<FirmwareArtifactType>();
37-
if (request.RequiredArtifactTypes is { Count: > 0 })
38+
if (!TryParseRequiredArtifactTypes(request.RequiredArtifactTypes, out var requiredArtifactTypes))
3839
{
39-
var parsed = new List<FirmwareArtifactType>();
40-
foreach (var typeStr in request.RequiredArtifactTypes)
41-
{
42-
if (!Enum.TryParse<FirmwareArtifactType>(typeStr, true, out var artifactType))
43-
{
44-
return Problem(FirmwareError.FirmwareInvalidArtifactType);
45-
}
46-
parsed.Add(artifactType);
47-
}
48-
requiredArtifactTypes = parsed.Distinct().ToArray();
40+
return Problem(FirmwareError.FirmwareInvalidArtifactType);
4941
}
5042

51-
var existing = await _db.FirmwareBoards.FirstOrDefaultAsync(b => b.Id == boardId, ct);
52-
if (existing is not null)
43+
var board = new FirmwareBoard
5344
{
54-
existing.Name = request.Name;
55-
existing.ChipId = request.ChipId;
56-
existing.RequiredArtifactTypes = requiredArtifactTypes;
45+
Id = Guid.NewGuid(),
46+
Name = request.Name,
47+
ChipId = request.ChipId,
48+
Discontinued = false,
49+
RequiredArtifactTypes = requiredArtifactTypes
50+
};
51+
52+
_db.FirmwareBoards.Add(board);
53+
await _db.SaveChangesAsync(ct);
54+
55+
return Created((string?)null, new { id = board.Id });
56+
}
57+
58+
[HttpPut("{boardId:guid}")]
59+
public async Task<IActionResult> UpdateBoard(
60+
[FromRoute] Guid boardId,
61+
[FromBody] CreateFirmwareBoardRequest request,
62+
CancellationToken ct)
63+
{
64+
if (!await _db.FirmwareChips.AnyAsync(c => c.Id == request.ChipId, ct))
65+
{
66+
return Problem(FirmwareError.FirmwareChipNotFound);
5767
}
58-
else
68+
69+
if (!TryParseRequiredArtifactTypes(request.RequiredArtifactTypes, out var requiredArtifactTypes))
5970
{
60-
_db.FirmwareBoards.Add(new FirmwareBoard
61-
{
62-
Id = boardId,
63-
Name = request.Name,
64-
ChipId = request.ChipId,
65-
Discontinued = false,
66-
RequiredArtifactTypes = requiredArtifactTypes
67-
});
71+
return Problem(FirmwareError.FirmwareInvalidArtifactType);
72+
}
73+
74+
var board = await _db.FirmwareBoards.FirstOrDefaultAsync(b => b.Id == boardId, ct);
75+
if (board is null)
76+
{
77+
return Problem(FirmwareError.FirmwareBoardNotFound);
6878
}
6979

80+
board.Name = request.Name;
81+
board.ChipId = request.ChipId;
82+
board.RequiredArtifactTypes = requiredArtifactTypes;
83+
7084
await _db.SaveChangesAsync(ct);
71-
return Created();
85+
return Ok();
7286
}
7387

74-
[HttpPatch("{boardId}/discontinue")]
75-
public async Task<IActionResult> DiscontinueBoard([FromRoute] string boardId, CancellationToken ct)
88+
[HttpPatch("{boardId:guid}/discontinue")]
89+
public async Task<IActionResult> DiscontinueBoard([FromRoute] Guid boardId, CancellationToken ct)
7690
{
7791
var board = await _db.FirmwareBoards.FirstOrDefaultAsync(b => b.Id == boardId, ct);
7892
if (board is null)
@@ -85,8 +99,8 @@ public async Task<IActionResult> DiscontinueBoard([FromRoute] string boardId, Ca
8599
return Ok();
86100
}
87101

88-
[HttpDelete("{boardId}")]
89-
public async Task<IActionResult> DeleteBoard([FromRoute] string boardId, CancellationToken ct)
102+
[HttpDelete("{boardId:guid}")]
103+
public async Task<IActionResult> DeleteBoard([FromRoute] Guid boardId, CancellationToken ct)
90104
{
91105
if (await _db.FirmwareArtifacts.AnyAsync(a => a.BoardId == boardId, ct))
92106
{
@@ -102,9 +116,9 @@ public async Task<IActionResult> DeleteBoard([FromRoute] string boardId, Cancell
102116
return NoContent();
103117
}
104118

105-
[HttpPut("{boardId}/usb-devices/{usbDeviceId:guid}")]
119+
[HttpPut("{boardId:guid}/usb-devices/{usbDeviceId:guid}")]
106120
public async Task<IActionResult> AttachUsbDevice(
107-
[FromRoute] string boardId,
121+
[FromRoute] Guid boardId,
108122
[FromRoute] Guid usbDeviceId,
109123
CancellationToken ct)
110124
{
@@ -133,9 +147,9 @@ public async Task<IActionResult> AttachUsbDevice(
133147
return NoContent();
134148
}
135149

136-
[HttpDelete("{boardId}/usb-devices/{usbDeviceId:guid}")]
150+
[HttpDelete("{boardId:guid}/usb-devices/{usbDeviceId:guid}")]
137151
public async Task<IActionResult> DetachUsbDevice(
138-
[FromRoute] string boardId,
152+
[FromRoute] Guid boardId,
139153
[FromRoute] Guid usbDeviceId,
140154
CancellationToken ct)
141155
{
@@ -145,4 +159,27 @@ await _db.FirmwareBoardUsbDevices
145159

146160
return NoContent();
147161
}
162+
163+
private static bool TryParseRequiredArtifactTypes(List<string>? raw, out FirmwareArtifactType[] parsed)
164+
{
165+
if (raw is null || raw.Count == 0)
166+
{
167+
parsed = Array.Empty<FirmwareArtifactType>();
168+
return true;
169+
}
170+
171+
var list = new List<FirmwareArtifactType>(raw.Count);
172+
foreach (var typeStr in raw)
173+
{
174+
if (!Enum.TryParse<FirmwareArtifactType>(typeStr, true, out var t))
175+
{
176+
parsed = Array.Empty<FirmwareArtifactType>();
177+
return false;
178+
}
179+
list.Add(t);
180+
}
181+
182+
parsed = list.Distinct().ToArray();
183+
return true;
184+
}
148185
}

‎RepositoryServer/Controllers/V2/Firmware/Admin/ChipsAdminController.cs‎

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Asp.Versioning;
2-
using FlexLabs.EntityFrameworkCore.Upsert;
32
using Microsoft.AspNetCore.Authorization;
43
using Microsoft.AspNetCore.Mvc;
54
using Microsoft.EntityFrameworkCore;
@@ -23,9 +22,8 @@ public ChipsAdminController(RepoServerContext db)
2322
_db = db;
2423
}
2524

26-
[HttpPut("{chipId}")]
27-
public async Task<IActionResult> UpsertChip(
28-
[FromRoute] string chipId,
25+
[HttpPost]
26+
public async Task<IActionResult> CreateChip(
2927
[FromBody] CreateFirmwareChipRequest request,
3028
CancellationToken ct)
3129
{
@@ -41,19 +39,48 @@ public async Task<IActionResult> UpsertChip(
4139

4240
var chip = new FirmwareChip
4341
{
44-
Id = chipId,
42+
Id = Guid.NewGuid(),
4543
Name = request.Name,
4644
Architecture = architecture
4745
};
4846

49-
var executed = await _db.FirmwareChips.Upsert(chip).On(c => c.Id).RunAsync(ct);
50-
if (executed <= 0) throw new Exception("Failed to upsert firmware chip");
47+
_db.FirmwareChips.Add(chip);
48+
await _db.SaveChangesAsync(ct);
5149

52-
return Created();
50+
return Created((string?)null, new { id = chip.Id });
5351
}
5452

55-
[HttpDelete("{chipId}")]
56-
public async Task<IActionResult> DeleteChip([FromRoute] string chipId, CancellationToken ct)
53+
[HttpPut("{chipId:guid}")]
54+
public async Task<IActionResult> UpdateChip(
55+
[FromRoute] Guid chipId,
56+
[FromBody] CreateFirmwareChipRequest request,
57+
CancellationToken ct)
58+
{
59+
FirmwareChipArchitecture? architecture = null;
60+
if (request.Architecture is not null)
61+
{
62+
if (!Enum.TryParse<FirmwareChipArchitecture>(request.Architecture, true, out var parsed))
63+
{
64+
return Problem(FirmwareError.FirmwareInvalidArchitecture);
65+
}
66+
architecture = parsed;
67+
}
68+
69+
var chip = await _db.FirmwareChips.FirstOrDefaultAsync(c => c.Id == chipId, ct);
70+
if (chip is null)
71+
{
72+
return Problem(FirmwareError.FirmwareChipNotFound);
73+
}
74+
75+
chip.Name = request.Name;
76+
chip.Architecture = architecture;
77+
await _db.SaveChangesAsync(ct);
78+
79+
return Ok();
80+
}
81+
82+
[HttpDelete("{chipId:guid}")]
83+
public async Task<IActionResult> DeleteChip([FromRoute] Guid chipId, CancellationToken ct)
5784
{
5885
if (await _db.FirmwareBoards.AnyAsync(b => b.ChipId == chipId, ct))
5986
{
@@ -69,9 +96,9 @@ public async Task<IActionResult> DeleteChip([FromRoute] string chipId, Cancellat
6996
return NoContent();
7097
}
7198

72-
[HttpPut("{chipId}/usb-devices/{usbDeviceId:guid}")]
99+
[HttpPut("{chipId:guid}/usb-devices/{usbDeviceId:guid}")]
73100
public async Task<IActionResult> AttachUsbDevice(
74-
[FromRoute] string chipId,
101+
[FromRoute] Guid chipId,
75102
[FromRoute] Guid usbDeviceId,
76103
CancellationToken ct)
77104
{
@@ -100,9 +127,9 @@ public async Task<IActionResult> AttachUsbDevice(
100127
return NoContent();
101128
}
102129

103-
[HttpDelete("{chipId}/usb-devices/{usbDeviceId:guid}")]
130+
[HttpDelete("{chipId:guid}/usb-devices/{usbDeviceId:guid}")]
104131
public async Task<IActionResult> DetachUsbDevice(
105-
[FromRoute] string chipId,
132+
[FromRoute] Guid chipId,
106133
[FromRoute] Guid usbDeviceId,
107134
CancellationToken ct)
108135
{

0 commit comments

Comments
 (0)