Skip to content

Commit fc6f0df

Browse files
authored
Add API support for adding and querying missing objects (#195)
resolves #192
1 parent 0efdb8c commit fc6f0df

15 files changed

Lines changed: 136 additions & 44 deletions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using OpenLoco.Dat.Data;
2+
3+
namespace OpenLoco.Definitions.DTO
4+
{
5+
public record DtoMissingObjectEntry(
6+
string DatName,
7+
uint32_t DatChecksum,
8+
ObjectType ObjectType);
9+
}

Definitions/DTO/DtoObjectEntry.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public record DtoObjectEntry(
88
UniqueObjectId Id,
99
string InternalName,
1010
string DisplayName,
11-
uint? DatChecksum,
11+
uint32_t? DatChecksum,
1212
string? Description,
1313
ObjectSource ObjectSource,
1414
ObjectType ObjectType,

Definitions/ObjectAvailability.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace OpenLoco.Definitions
33
public enum ObjectAvailability
44
{
55
Unavailable,
6-
Available
6+
Available,
7+
Missing
78
}
89
}

Definitions/Web/RoutesV2.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ public static class RoutesV2
1212
public const string Tags = "/tags";
1313
public const string Licences = "/licences";
1414

15+
// extra Objects routes
1516
public const string File = "/file";
1617
public const string Images = "/images";
18+
public const string Missing = "/missing";
1719

1820
public const string ResourceRoute = "/{id}";
1921

ObjectService/RouteHandlers/BaseDataTableRouteHandler.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.AspNetCore.Mvc;
12
using Microsoft.EntityFrameworkCore;
23
using OpenLoco.Definitions;
34
using OpenLoco.Definitions.Database;
@@ -21,7 +22,7 @@ public abstract class BaseDataTableRouteHandler<THandler, TDto, TRow> : ITableRo
2122

2223
public static Delegate DeleteDelegate => DeleteAsync;
2324

24-
public static async Task<IResult> CreateAsync(TDto request, LocoDbContext db)
25+
public static async Task<IResult> CreateAsync(TDto request, [FromServices] LocoDbContext db)
2526
=> await BaseDataTableRouteHandlerImpl.CreateAsync(
2627
THandler.GetTable(db),
2728
THandler.ToDtoFunc,
@@ -31,10 +32,10 @@ public static async Task<IResult> CreateAsync(TDto request, LocoDbContext db)
3132
THandler.GetBaseRoute(),
3233
db);
3334

34-
public static async Task<IResult> ReadAsync(UniqueObjectId id, LocoDbContext db)
35+
public static async Task<IResult> ReadAsync(UniqueObjectId id, [FromServices] LocoDbContext db)
3536
=> await BaseDataTableRouteHandlerImpl.ReadAsync(THandler.GetTable(db), THandler.ToDtoFunc, id, db);
3637

37-
public static async Task<IResult> UpdateAsync(UniqueObjectId id, TDto request, LocoDbContext db)
38+
public static async Task<IResult> UpdateAsync(UniqueObjectId id, TDto request, [FromServices] LocoDbContext db)
3839
=> await BaseDataTableRouteHandlerImpl.UpdateAsync(
3940
THandler.GetTable(db),
4041
THandler.ToDtoFunc,
@@ -46,16 +47,16 @@ public static async Task<IResult> UpdateAsync(UniqueObjectId id, TDto request, L
4647
db,
4748
THandler.UpdateFunc);
4849

49-
public static async Task<IResult> DeleteAsync(UniqueObjectId id, LocoDbContext db)
50+
public static async Task<IResult> DeleteAsync(UniqueObjectId id, [FromServices] LocoDbContext db)
5051
=> await BaseDataTableRouteHandlerImpl.DeleteAsync(THandler.GetTable(db), THandler.ToDtoFunc, id, db);
5152

52-
public static async Task<IResult> ListAsync(HttpContext context, LocoDbContext db)
53+
public static async Task<IResult> ListAsync(HttpContext context, [FromServices] LocoDbContext db)
5354
=> await BaseDataTableRouteHandlerImpl.ListAsync(context, THandler.GetTable(db), THandler.ToDtoFunc);
5455
}
5556

5657
public static class BaseDataTableRouteHandlerImpl
5758
{
58-
public static async Task<IResult> CreateAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, Func<TDto, TRow> rowConverter, TDto request, Func<(bool Success, IResult? ErrorMessage)> tryValidateFunc, string baseRoute, LocoDbContext db)
59+
public static async Task<IResult> CreateAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, Func<TDto, TRow> rowConverter, TDto request, Func<(bool Success, IResult? ErrorMessage)> tryValidateFunc, string baseRoute, [FromServices] LocoDbContext db)
5960
where TDto : class, IHasId
6061
where TRow : class, IHasId
6162
{
@@ -71,14 +72,14 @@ public static async Task<IResult> CreateAsync<TDto, TRow>(DbSet<TRow> table, Fun
7172
return Results.Created($"{baseRoute}/{row.Id}", dtoConverter(row));
7273
}
7374

74-
public static async Task<IResult> ReadAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, UniqueObjectId id, LocoDbContext db)
75+
public static async Task<IResult> ReadAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, UniqueObjectId id, [FromServices] LocoDbContext db)
7576
where TDto : class, IHasId
7677
where TRow : class, IHasId
7778
=> await table.FindAsync(id) is TRow row
7879
? Results.Ok(dtoConverter(row))
7980
: Results.NotFound();
8081

81-
public static async Task<IResult> UpdateAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, Func<TDto, TRow> rowConverter, TDto request, Func<(bool Success, IResult? ErrorMessage)> tryValidateFunc, string baseRoute, UniqueObjectId id, LocoDbContext db, Action<TDto, TRow> updateFunc)
82+
public static async Task<IResult> UpdateAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, Func<TDto, TRow> rowConverter, TDto request, Func<(bool Success, IResult? ErrorMessage)> tryValidateFunc, string baseRoute, UniqueObjectId id, [FromServices] LocoDbContext db, Action<TDto, TRow> updateFunc)
8283
where TDto : class, IHasId
8384
where TRow : class, IHasId
8485
{
@@ -92,7 +93,7 @@ public static async Task<IResult> UpdateAsync<TDto, TRow>(DbSet<TRow> table, Fun
9293
return Results.Accepted($"{baseRoute}/{row.Id}", dtoConverter(row));
9394
}
9495

95-
public static async Task<IResult> DeleteAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, UniqueObjectId id, LocoDbContext db)
96+
public static async Task<IResult> DeleteAsync<TDto, TRow>(DbSet<TRow> table, Func<TRow, TDto> dtoConverter, UniqueObjectId id, [FromServices] LocoDbContext db)
9697
where TDto : class, IHasId
9798
where TRow : class, IHasId
9899
{

ObjectService/RouteHandlers/ITableRouteConfig.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.AspNetCore.Mvc;
12
using Microsoft.EntityFrameworkCore;
23
using OpenLoco.Definitions;
34
using OpenLoco.Definitions.Database;
@@ -14,6 +15,6 @@ public interface ITableRouteConfig<TDto, TRow>
1415
static abstract TDto ToDtoFunc(TRow request);
1516
static abstract void UpdateFunc(TDto request, TRow row);
1617

17-
static abstract bool TryValidateCreate(TDto request, LocoDbContext db, out IResult? result);
18+
static abstract bool TryValidateCreate(TDto request, [FromServices] LocoDbContext db, out IResult? result);
1819
}
1920
}

ObjectService/RouteHandlers/TableHandlers/AuthorRouteHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.AspNetCore.Mvc;
12
using Microsoft.EntityFrameworkCore;
23
using OpenLoco.Definitions.Database;
34
using OpenLoco.Definitions.DTO;
@@ -28,7 +29,7 @@ public static void UpdateFunc(DtoAuthorEntry request, TblAuthor row)
2829
public static TblAuthor ToRowFunc(DtoAuthorEntry request)
2930
=> request.ToTable();
3031

31-
public static bool TryValidateCreate(DtoAuthorEntry request, LocoDbContext db, out IResult? result)
32+
public static bool TryValidateCreate(DtoAuthorEntry request, [FromServices] LocoDbContext db, out IResult? result)
3233
{
3334
if (string.IsNullOrWhiteSpace(request.Name))
3435
{

ObjectService/RouteHandlers/TableHandlers/LicenceRouteHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.AspNetCore.Mvc;
12
using Microsoft.EntityFrameworkCore;
23
using OpenLoco.Definitions.Database;
34
using OpenLoco.Definitions.DTO;
@@ -28,7 +29,7 @@ public static void UpdateFunc(DtoLicenceEntry request, TblLicence row)
2829
public static TblLicence ToRowFunc(DtoLicenceEntry request)
2930
=> request.ToTable();
3031

31-
public static bool TryValidateCreate(DtoLicenceEntry request, LocoDbContext db, out IResult? result)
32+
public static bool TryValidateCreate([FromBody] DtoLicenceEntry request, [FromServices] LocoDbContext db, out IResult? result)
3233
{
3334
if (string.IsNullOrWhiteSpace(request.Name))
3435
{

ObjectService/RouteHandlers/TableHandlers/ObjectPackRouteHandler.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.AspNetCore.Mvc;
12
using Microsoft.EntityFrameworkCore;
23
using OpenLoco.Definitions.Database;
34
using OpenLoco.Definitions.DTO;
@@ -22,18 +23,18 @@ public static void MapRoutes(IEndpointRouteBuilder endpoints)
2223
public static void MapAdditionalRoutes(IEndpointRouteBuilder parentRoute)
2324
{ }
2425

25-
public static async Task<IResult> ListAsync(HttpContext context, LocoDbContext db)
26+
public static async Task<IResult> ListAsync(HttpContext context, [FromServices] LocoDbContext db)
2627
=> Results.Ok(
2728
(await db.ObjectPacks
2829
.Include(l => l.Licence)
2930
.ToListAsync())
3031
.Select(x => x.ToDtoEntry())
3132
.OrderBy(x => x.Name));
3233

33-
public static async Task<IResult> CreateAsync(DtoItemPackDescriptor<DtoObjectEntry> request, LocoDbContext db)
34+
public static async Task<IResult> CreateAsync([FromBody] DtoItemPackDescriptor<DtoObjectEntry> request, [FromServices] LocoDbContext db)
3435
=> await Task.Run(() => Results.Problem(statusCode: StatusCodes.Status501NotImplemented));
3536

36-
public static async Task<IResult> ReadAsync(UniqueObjectId id, LocoDbContext db)
37+
public static async Task<IResult> ReadAsync(UniqueObjectId id, [FromServices] LocoDbContext db)
3738
=> Results.Ok(
3839
(await db.ObjectPacks
3940
.Where(x => x.Id == id)
@@ -43,10 +44,10 @@ public static async Task<IResult> ReadAsync(UniqueObjectId id, LocoDbContext db)
4344
.Select(x => x.ToDtoDescriptor())
4445
.OrderBy(x => x.Name));
4546

46-
public static async Task<IResult> UpdateAsync(UniqueObjectId id, DtoItemPackDescriptor<DtoObjectEntry> request, LocoDbContext db)
47+
public static async Task<IResult> UpdateAsync([FromRoute] UniqueObjectId id, [FromBody] DtoItemPackDescriptor<DtoObjectEntry> request, [FromServices] LocoDbContext db)
4748
=> await Task.Run(() => Results.Problem(statusCode: StatusCodes.Status501NotImplemented));
4849

49-
public static async Task<IResult> DeleteAsync(UniqueObjectId id, LocoDbContext db)
50+
public static async Task<IResult> DeleteAsync([FromRoute] UniqueObjectId id, [FromServices] LocoDbContext db)
5051
=> await Task.Run(() => Results.Problem(statusCode: StatusCodes.Status501NotImplemented));
5152
}
5253
}

ObjectService/RouteHandlers/TableHandlers/ObjectRouteHandler.cs

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using OpenLoco.Dat.Data;
77
using OpenLoco.Dat.FileParsing;
88
using OpenLoco.Dat.Objects;
9+
using OpenLoco.Dat.Types;
910
using OpenLoco.Definitions;
1011
using OpenLoco.Definitions.Database;
1112
using OpenLoco.Definitions.DTO;
@@ -35,12 +36,82 @@ public static void MapAdditionalRoutes(IEndpointRouteBuilder parentRoute)
3536
{
3637
_ = parentRoute.MapPost(string.Empty, CreateDatAsync); // old dat route
3738

39+
_ = parentRoute.MapGet(RoutesV2.Missing, ListMissingObjects);
40+
_ = parentRoute.MapPost(RoutesV2.Missing, AddMissingObject);
41+
3842
var resourceRoute = parentRoute.MapGroup(RoutesV2.ResourceRoute);
3943
_ = resourceRoute.MapGet(RoutesV2.File, GetObjectFileAsync);
4044
_ = resourceRoute.MapGet(RoutesV2.Images, GetObjectImagesAsync);
4145
}
46+
static async Task<IResult> ListMissingObjects([FromServices] LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
47+
{
48+
logger.LogInformation("[ListMissingObjects] List requested for missing objects");
49+
50+
return Results.Ok(
51+
await db.Objects
52+
.Include(x => x.DatObjects)
53+
.Where(x => x.Availability == ObjectAvailability.Missing)
54+
.Select(x => x.ToDtoEntry())
55+
.ToListAsync());
56+
}
57+
58+
static async Task<IResult> AddMissingObject([FromBody] DtoMissingObjectEntry entry, [FromServices] LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
59+
{
60+
var objName = $"{entry.DatName}_{entry.DatChecksum}";
61+
var existing = await db.Objects.FirstOrDefaultAsync(x => x.Name == objName);
62+
if (existing != null)
63+
{
64+
return Results.Conflict($"Object already exists in the database. DatName={entry.DatName} DatChecksum={entry.DatChecksum} UploadedDate={existing!.UploadedDate}");
65+
}
66+
67+
// double check it's missing
68+
if (db.DoesObjectExist(entry.DatName, entry.DatChecksum, out var existingObject) && existingObject != null)
69+
{
70+
return Results.Conflict($"Object already exists in the database. UniqueId={existingObject.Id} DatName={entry.DatName} DatChecksum={entry.DatChecksum} UploadedDate={existingObject!.UploadedDate}");
71+
}
72+
73+
// save to db if true
74+
var tblObject = new TblObject()
75+
{
76+
Name = $"{entry.DatName}_{entry.DatChecksum}",
77+
Description = string.Empty,
78+
ObjectSource = ObjectSource.Custom,
79+
ObjectType = entry.ObjectType,
80+
VehicleType = null,
81+
Availability = ObjectAvailability.Missing,
82+
CreatedDate = null,
83+
ModifiedDate = null,
84+
UploadedDate = DateOnly.Today,
85+
Authors = [],
86+
Tags = [],
87+
ObjectPacks = [],
88+
DatObjects = [],
89+
StringTable = [],
90+
SubObjectId = 0,
91+
Licence = null,
92+
};
93+
94+
95+
_ = await db.Objects.AddAsync(tblObject);
96+
_ = await db.SaveChangesAsync();
97+
98+
// make dat objects
99+
//var xxHash3 = XxHash3.HashToUInt64(datFileBytes);
100+
tblObject.DatObjects.Add(new TblDatObject()
101+
{
102+
ObjectId = tblObject.Id,
103+
DatName = entry.DatName,
104+
DatChecksum = entry.DatChecksum,
105+
xxHash3 = 0,
106+
Object = tblObject,
107+
});
108+
109+
// save again
110+
_ = await db.SaveChangesAsync();
111+
return Results.Created($"Successfully added 'missing' DAT object {tblObject.Name} with checksum {entry.DatChecksum} and unique id {tblObject.Id}", tblObject.Id);
112+
}
42113

43-
//static async Task<IResult> CreateAsync(DtoObjectDescriptor request, LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
114+
//static async Task<IResult> CreateAsync(DtoObjectDescriptor request, [FromServices] LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
44115
//{
45116
// logger.LogInformation("[CreateAsync] Upload requested");
46117

@@ -182,7 +253,7 @@ public static void MapAdditionalRoutes(IEndpointRouteBuilder parentRoute)
182253
// return Results.Created($"Successfully added {tblObject.Name} with unique id {tblObject.Id}", tblObject.Id);
183254
//}
184255

185-
static async Task<IResult> CreateDatAsync(DtoUploadDat request, LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
256+
static async Task<IResult> CreateDatAsync(DtoUploadDat request, [FromServices] LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
186257
{
187258
logger.LogInformation("[CreateAsync] Upload requested");
188259

@@ -330,7 +401,7 @@ static async Task<IResult> CreateDatAsync(DtoUploadDat request, LocoDbContext db
330401
return Results.Created($"Successfully added {tblObject.Name} with unique id {tblObject.Id}", tblObject.Id);
331402
}
332403

333-
static async Task<IResult> ReadAsync([FromRoute] UniqueObjectId id, LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
404+
static async Task<IResult> ReadAsync([FromRoute] UniqueObjectId id, [FromServices] LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
334405
{
335406
logger.LogInformation("[ReadAsync] Read requested for object {ObjectId}", id);
336407

@@ -349,20 +420,20 @@ static async Task<IResult> ReadAsync([FromRoute] UniqueObjectId id, LocoDbContex
349420
return ReturnObject(descriptor, sfm, logger);
350421
}
351422

352-
static async Task<IResult> UpdateAsync([FromRoute] UniqueObjectId id, DtoObjectDescriptor request, LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
423+
static async Task<IResult> UpdateAsync([FromRoute] UniqueObjectId id, DtoObjectDescriptor request, [FromServices] LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
353424
{
354425
logger.LogInformation("[UpdateAsync] Update requested for object {ObjectId}", id);
355426
return await Task.Run(() => Results.Problem(statusCode: StatusCodes.Status501NotImplemented));
356427
}
357428

358-
static async Task<IResult> DeleteAsync([FromRoute] UniqueObjectId id, LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
429+
static async Task<IResult> DeleteAsync([FromRoute] UniqueObjectId id, [FromServices] LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
359430
{
360431
logger.LogInformation("[DeleteAsync] Delete requested for object {ObjectId}", id);
361432
// for now we could soft-delete by marking an object as Unavailable?
362433
return await Task.Run(() => Results.Problem(statusCode: StatusCodes.Status501NotImplemented));
363434
}
364435

365-
static async Task<IResult> ListAsync(HttpContext context, LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
436+
static async Task<IResult> ListAsync(HttpContext context, [FromServices] LocoDbContext db, [FromServices] ILogger<ObjectRouteHandler> logger)
366437
{
367438
logger.LogInformation("[ListAsync] List requested for object");
368439

@@ -439,7 +510,7 @@ await db.Objects
439510
}
440511

441512
// eg: http://localhost:7229/v1/objects/{id}/images
442-
static async Task<IResult> GetObjectImagesAsync([FromRoute] UniqueObjectId id, LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
513+
static async Task<IResult> GetObjectImagesAsync([FromRoute] UniqueObjectId id, [FromServices] LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
443514
{
444515
logger.LogInformation("[GetObjectImages] Get requested for object {ObjectId}", id);
445516

@@ -520,7 +591,7 @@ static async Task<IResult> GetObjectImagesAsync([FromRoute] UniqueObjectId id, L
520591
}
521592

522593
// eg: https://localhost:7230/objects/114
523-
static async Task<IResult> GetObjectFileAsync([FromRoute] UniqueObjectId id, LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
594+
static async Task<IResult> GetObjectFileAsync([FromRoute] UniqueObjectId id, [FromServices] LocoDbContext db, [FromServices] IServiceProvider sp, [FromServices] ILogger<ObjectRouteHandler> logger)
524595
{
525596
logger.LogInformation("[GetObjectFile] Get requested for object {ObjectId}", id);
526597

0 commit comments

Comments
 (0)