Skip to content

Commit 941ec30

Browse files
committed
introduce review/audit mechanism for edits, creates, and deletes
1 parent 022dfb7 commit 941ec30

File tree

9 files changed

+353
-237
lines changed

9 files changed

+353
-237
lines changed

rubberduckvba.Server/Api/Admin/AdminController.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
using Microsoft.AspNetCore.Cors;
33
using Microsoft.AspNetCore.Mvc;
44
using Microsoft.Extensions.Options;
5+
using rubberduckvba.Server.Model.Entity;
56
using rubberduckvba.Server.Services;
67

78
namespace rubberduckvba.Server.Api.Admin;
89

910
[ApiController]
1011
[EnableCors(CorsPolicies.AllowAll)]
11-
public class AdminController(ConfigurationOptions options, HangfireLauncherService hangfire, CacheService cache) : ControllerBase
12+
public class AdminController(ConfigurationOptions options, HangfireLauncherService hangfire, CacheService cache, IAuditService audits) : ControllerBase
1213
{
1314
/// <summary>
1415
/// Enqueues a job that updates xmldoc content from the latest release/pre-release tags.
@@ -42,6 +43,16 @@ public IActionResult ClearCache()
4243
return Ok();
4344
}
4445

46+
[Authorize("github")]
47+
[HttpGet("admin/audits")]
48+
public async Task<IActionResult> GetPendingAudits()
49+
{
50+
var edits = await audits.GetPendingItems<FeatureEditEntity>();
51+
var ops = await audits.GetPendingItems<FeatureOpEntity>();
52+
53+
return Ok(new { edits = edits.ToArray(), other = ops.ToArray() });
54+
}
55+
4556
#if DEBUG
4657
[AllowAnonymous]
4758
[HttpGet("admin/config/current")]

rubberduckvba.Server/Api/Features/FeaturesController.cs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using rubberduckvba.Server.Model.Entity;
77
using rubberduckvba.Server.Services;
88
using rubberduckvba.Server.Services.rubberduckdb;
9+
using System.Security.Principal;
910

1011
namespace rubberduckvba.Server.Api.Features;
1112

@@ -21,17 +22,19 @@ public class FeaturesController : RubberduckApiController
2122
{
2223
private readonly CacheService cache;
2324
private readonly IRubberduckDbService db;
25+
private readonly IAuditService admin;
2426
private readonly FeatureServices features;
2527
private readonly IRepository<TagAssetEntity> assetsRepository;
2628
private readonly IRepository<TagEntity> tagsRepository;
2729
private readonly IMarkdownFormattingService markdownService;
2830

29-
public FeaturesController(CacheService cache, IRubberduckDbService db, FeatureServices features, IMarkdownFormattingService markdownService,
31+
public FeaturesController(CacheService cache, IRubberduckDbService db, IAuditService admin, FeatureServices features, IMarkdownFormattingService markdownService,
3032
IRepository<TagAssetEntity> assetsRepository, IRepository<TagEntity> tagsRepository, ILogger<FeaturesController> logger)
3133
: base(logger)
3234
{
3335
this.cache = cache;
3436
this.db = db;
37+
this.admin = admin;
3538
this.features = features;
3639
this.assetsRepository = assetsRepository;
3740
this.tagsRepository = tagsRepository;
@@ -175,11 +178,15 @@ public async Task<ActionResult<FeatureEditViewModel>> Create([FromBody] FeatureE
175178
}
176179

177180
var feature = model.ToFeature();
178-
179-
var result = await db.SaveFeature(feature);
180-
var features = await GetFeatureOptions(model.RepositoryId);
181-
182-
return Ok(new FeatureEditViewModel(result, features, RepositoryOptions));
181+
if (User.Identity is IIdentity identity)
182+
{
183+
await admin.CreateFeature(feature, identity);
184+
return Ok(feature);
185+
}
186+
else
187+
{
188+
return Unauthorized("User identity is not available.");
189+
}
183190
}
184191

185192
[HttpPost("features/update")]
@@ -198,11 +205,15 @@ public async Task<ActionResult<FeatureEditViewModel>> Update([FromBody] FeatureE
198205
}
199206

200207
var feature = model.ToFeature();
201-
202-
var result = await db.SaveFeature(feature);
203-
var features = await GetFeatureOptions(model.RepositoryId);
204-
205-
return new FeatureEditViewModel(result, features, RepositoryOptions);
208+
if (User.Identity is IIdentity identity)
209+
{
210+
await admin.UpdateFeature(feature, identity);
211+
return Ok(feature);
212+
}
213+
else
214+
{
215+
return Unauthorized("User identity is not available.");
216+
}
206217
}
207218

208219
[HttpPost("features/delete")]
@@ -213,13 +224,20 @@ public async Task Delete([FromBody] IFeature model)
213224
{
214225
throw new ArgumentException("Model is invalid for this endpoint.");
215226
}
216-
var existingId = await db.GetFeatureId(RepositoryId.Rubberduck, model.Name);
217-
if (existingId is null)
227+
var existing = await db.ResolveFeature(RepositoryId.Rubberduck, model.Name);
228+
if (existing is null)
218229
{
219230
throw new ArgumentException("Model is invalid for this endpoint.");
220231
}
221232

222-
await db.DeleteFeature(existingId.Value);
233+
if (User.Identity is IIdentity identity)
234+
{
235+
await admin.DeleteFeature(existing, identity);
236+
}
237+
else
238+
{
239+
throw new UnauthorizedAccessException("User identity is not available.");
240+
}
223241
}
224242

225243
[HttpPost("markdown/format")]

rubberduckvba.Server/Model/Entity/FeatureEntity.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,46 @@ public record class FeatureEntity : Entity
1919
}
2020

2121
public record class BlogLink(string Name, string Url, string Author, string Published) { }
22+
23+
public record class AuditEntity
24+
{
25+
public int Id { get; init; }
26+
public DateTime DateInserted { get; init; }
27+
public DateTime? DateModified { get; init; }
28+
public string Author { get; init; } = string.Empty;
29+
}
30+
31+
public record class FeatureEditEntity : AuditEntity
32+
{
33+
public int FeatureId { get; init; }
34+
public string FieldName { get; init; } = string.Empty;
35+
public string? ValueBefore { get; init; }
36+
public string ValueAfter { get; init; } = string.Empty;
37+
public string? ApprovedBy { get; init; }
38+
public DateTime? ApprovedAt { get; init; }
39+
public string? RejectedBy { get; init; }
40+
public DateTime? RejectedAt { get; init; }
41+
}
42+
43+
public enum FeatureOperation
44+
{
45+
Create = 1,
46+
Delete = 2,
47+
}
48+
49+
public record class FeatureOpEntity : AuditEntity
50+
{
51+
public FeatureOperation FeatureAction { get; init; }
52+
53+
public int? ParentId { get; init; }
54+
public string FeatureName { get; init; } = default!;
55+
public int RepositoryId { get; init; }
56+
public string Title { get; init; } = default!;
57+
public string ShortDescription { get; init; } = default!;
58+
public string Description { get; init; } = default!;
59+
public bool IsHidden { get; init; }
60+
public bool IsNew { get; init; }
61+
public bool HasImage { get; init; }
62+
63+
public string Links { get; init; } = string.Empty;
64+
}

rubberduckvba.Server/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ private static void ConfigureServices(IServiceCollection services)
228228

229229
services.AddSession(ConfigureSession);
230230
services.AddSingleton<IMemoryCache>(provider => new MemoryCache(new MemoryCacheOptions(), provider.GetRequiredService<ILoggerFactory>()));
231+
services.AddSingleton<IAuditService, AuditService>();
231232
}
232233

233234
private static void ConfigureLogging(IServiceCollection services)

0 commit comments

Comments
 (0)