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
2 changes: 1 addition & 1 deletion docker/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:latest
COPY shippingrecorder.api-1.8.0.0 /opt/shippingrecorder.api
COPY shippingrecorder.api-1.9.0.0 /opt/shippingrecorder.api
WORKDIR /opt/shippingrecorder.api/bin
ENTRYPOINT [ "./ShippingRecorder.Api" ]
2 changes: 1 addition & 1 deletion docker/ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:latest AS runtime
COPY shippingrecorder.mvc-1.8.0.0 /opt/shippingrecorder.mvc
COPY shippingrecorder.mvc-1.9.0.0 /opt/shippingrecorder.mvc
WORKDIR /opt/shippingrecorder.mvc/bin
ENTRYPOINT [ "./ShippingRecorder.Mvc" ]
10 changes: 5 additions & 5 deletions src/ShippingRecorder.Api/Controllers/SightingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ public async Task<ActionResult<List<Sighting>>> GetSightingsByVesselAsync(int ve
}

[HttpGet]
[Route("recent/imo/{imo}")]
public async Task<ActionResult<Sighting>> GetMostRecentVesselSightingAsync(string imo)
[Route("recent/identifier/{identifier}")]
public async Task<ActionResult<Sighting>> GetMostRecentVesselSightingAsync(string identifier)
{
LogMessage(Severity.Debug, $"Retrieving most recent sighting for vessel {imo}");
LogMessage(Severity.Debug, $"Retrieving most recent sighting for vessel {identifier}");

Sighting sighting = await Factory.Sightings.GetMostRecentAsync(x => x.Vessel.IMO == imo);
Sighting sighting = await Factory.Sightings.GetMostRecentAsync(x => x.Vessel.Identifier == identifier);

if (sighting == null)
{
LogMessage(Severity.Debug, $"Sighting for vessel {imo} not found");
LogMessage(Severity.Debug, $"Sighting for vessel {identifier} not found");
return NoContent();
}

Expand Down
20 changes: 11 additions & 9 deletions src/ShippingRecorder.Api/Controllers/VesselsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ public async Task<ActionResult<Vessel>> GetVesselByIdAsync(int id)
}

[HttpGet]
[Route("imo/{imo}")]
public async Task<ActionResult<Vessel>> GetVesselByIMOAsync(string imo)
[Route("identifier/{identifier}")]
public async Task<ActionResult<Vessel>> GetVesselByIdentifierAsync(string identifier)
{
LogMessage(Severity.Debug, $"Retrieving vessel with IMO {imo}");
LogMessage(Severity.Debug, $"Retrieving vessel with identifier {identifier}");

Vessel vessel = await Factory.Vessels.GetAsync(m => m.IMO == imo);
Vessel vessel = await Factory.Vessels.GetAsync(m => m.Identifier == identifier);

if (vessel == null)
{
LogMessage(Severity.Debug, $"Vessel with IMO {imo} not found");
LogMessage(Severity.Debug, $"Vessel with identifier {identifier} not found");
return NotFound();
}

Expand All @@ -75,13 +75,14 @@ public async Task<ActionResult<Vessel>> AddVesselAsync([FromBody] Vessel templat
{
LogMessage(Severity.Debug,
$"Adding vessel: " +
$"IMO = {template.IMO}, " +
$"Identifier = {template.Identifier}, " +
$"Is IMO = {template.IsIMO}, " +
$"Built = {template.Built}, " +
$"Draught = {template.Draught}, " +
$"Length = {template.Length}, " +
$"Beam = {template.Beam}");

Vessel vessel = await Factory.Vessels.AddAsync(template.IMO, template.Built, template.Draught, template.Length, template.Beam);
Vessel vessel = await Factory.Vessels.AddAsync(template.Identifier, template.IsIMO, template.Built, template.Draught, template.Length, template.Beam);
LogMessage(Severity.Debug, $"Vessel added: {vessel}");
return vessel;
}
Expand All @@ -93,13 +94,14 @@ public async Task<ActionResult<Vessel>> UpdateVesselAsync([FromBody] Vessel temp
LogMessage(Severity.Debug,
$"Adding vessel: " +
$"ID = {template.Id}, " +
$"IMO = {template.IMO}, " +
$"Identifier = {template.Identifier}, " +
$"Is IMO = {template.IsIMO}, " +
$"Built = {template.Built}, " +
$"Draught = {template.Draught}, " +
$"Length = {template.Length}, " +
$"Beam = {template.Beam}");

Vessel vessel = await Factory.Vessels.UpdateAsync(template.Id, template.IMO, template.Built, template.Draught, template.Length, template.Beam);
Vessel vessel = await Factory.Vessels.UpdateAsync(template.Id, template.Identifier, template.IsIMO, template.Built, template.Draught, template.Length, template.Beam);
LogMessage(Severity.Debug, $"Vessel updated: {vessel}");
return vessel;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ShippingRecorder.Api/ShippingRecorder.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<FileVersion>1.8.0.0</FileVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
<FileVersion>1.9.0.0</FileVersion>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<NoWarn>NU1900</NoWarn>
Expand Down
71 changes: 46 additions & 25 deletions src/ShippingRecorder.BusinessLogic/Database/VesselManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,39 +46,49 @@ public virtual IAsyncEnumerable<Vessel> ListAsync(Expression<Func<Vessel, bool>>
.ThenInclude(h => h.Flag)
.Include(x => x.RegistrationHistory)
.ThenInclude(h => h.Operator)
.OrderBy(x => x.IMO)
.OrderBy(x => x.Identifier)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.AsAsyncEnumerable();

/// <summary>
/// Add a vessel
/// </summary>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <param name="isIMO"></param>
/// <param name="built"></param>
/// <param name="draught"></param>
/// <param name="length"></param>
/// <param name="beam"></param>
/// <returns></returns>
public async Task<Vessel> AddAsync(string imo, int? built, decimal? draught, int? length, int? beam)
public async Task<Vessel> AddAsync(string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam)
{
imo = imo.CleanCode();
_factory.Logger.LogMessage(Severity.Debug, $"Adding vessel: IMO = {imo}, Built = {built}, Draught = {draught}, Length = {length}, Beam = {beam}");
_factory.Logger.LogMessage(Severity.Debug, $"Adding vessel: Identifier = {identifier}, Built = {built}, Draught = {draught}, Length = {length}, Beam = {beam}");

// Validate the parameters
imo.ValidateNumericAndThrow<InvalidIMOException>(7, 7);
if (isIMO)
{
identifier = identifier.CleanCode();
identifier.ValidateNumericAndThrow<InvalidVesselIdentifierException>(7, 7);
}
else
{
identifier = identifier.Clean().ToUpper();
}

built.ValidateIntegerAndThrow<InvalidYearBuiltException>(Vessel.EarliestYearBuilt, DateTime.Today.Year, true);
draught.ValidateDecimalAndThrow<InvalidDraughtException>(Vessel.MinimumDraught, decimal.MaxValue, true);
length.ValidateIntegerAndThrow<InvalidLengthException>(Vessel.MinimumLength, int.MaxValue, true);
beam.ValidateIntegerAndThrow<InvalidBeamException>(Vessel.MinimumBeam, int.MaxValue, true);

// Check the vessel doesn't already exist
await CheckVesselIsNotADuplicate(imo, 0);
await CheckVesselIsNotADuplicate(identifier, 0);

// Add the vessel and save changes
var vessel = new Vessel
{
IMO = imo,
Identifier = identifier,
IsIMO = isIMO,
Built = built,
Draught = draught,
Length = length,
Expand All @@ -98,19 +108,20 @@ public async Task<Vessel> AddAsync(string imo, int? built, decimal? draught, int
/// <summary>
/// Add a vessel, if it doesn't already exist
/// </summary>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <param name="isIMO"></param>
/// <param name="built"></param>
/// <param name="draught"></param>
/// <param name="length"></param>
/// <param name="beam"></param>
/// <returns></returns>
public async Task<Vessel> AddIfNotExistsAsync(string imo, int? built, decimal? draught, int? length, int? beam)
public async Task<Vessel> AddIfNotExistsAsync(string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam)
{
imo = imo.CleanCode();
var vessel = await GetAsync(x => x.IMO == imo);
identifier = isIMO ? identifier.CleanCode() : identifier.Clean().ToUpper();
var vessel = await GetAsync(x => x.Identifier == identifier);
if (vessel == null)
{
vessel = await AddAsync(imo, built, draught, length, beam);
vessel = await AddAsync(identifier, isIMO, built, draught, length, beam);
}
return vessel;
}
Expand All @@ -119,20 +130,29 @@ public async Task<Vessel> AddIfNotExistsAsync(string imo, int? built, decimal? d
/// Update a vessel
/// </summary>
/// <param name="id"></param>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <param name="isIMO"></param>
/// <param name="built"></param>
/// <param name="draught"></param>
/// <param name="length"></param>
/// <param name="beam"></param>
/// <returns></returns>
/// <exception cref="VesselNotFoundException"></exception>
public async Task<Vessel> UpdateAsync(long id, string imo, int? built, decimal? draught, int? length, int? beam)
public async Task<Vessel> UpdateAsync(long id, string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam)
{
imo = imo.CleanCode();
_factory.Logger.LogMessage(Severity.Debug, $"Updating vessel: ID = {id}, IMO = {imo}, Built = {built}, Draught = {draught}, Length = {length}, Beam = {beam}");
identifier = identifier.CleanCode();
_factory.Logger.LogMessage(Severity.Debug, $"Updating vessel: ID = {id}, Identifier = {identifier}, Built = {built}, Draught = {draught}, Length = {length}, Beam = {beam}");

// Validate the IMO
imo.ValidateNumericAndThrow<InvalidIMOException>(7, 7);
// Validate the identifier
if (isIMO)
{
identifier = identifier.CleanCode();
identifier.ValidateNumericAndThrow<InvalidVesselIdentifierException>(7, 7);
}
else
{
identifier = identifier.Clean().ToUpper();
}

// Retrieve the vessel
var vessel = await Context.Vessels.FirstOrDefaultAsync(x => x.Id == id);
Expand All @@ -143,10 +163,11 @@ public async Task<Vessel> UpdateAsync(long id, string imo, int? built, decimal?
}

// Check the vessel doesn't already exist
await CheckVesselIsNotADuplicate(imo, id);
await CheckVesselIsNotADuplicate(identifier, id);

// Update the vessel properties and save changes
vessel.IMO = imo;
vessel.Identifier = identifier;
vessel.IsIMO = isIMO;
vessel.Built = built;
vessel.Draught = draught;
vessel.Length = length;
Expand Down Expand Up @@ -187,15 +208,15 @@ public async Task DeleteAsync(long id)
/// <summary>
/// Raise an exception if an attempt is made to add/update a duplicate vessel
/// </summary>
/// <param imo="imo"></param>
/// <param name="identifier"></param>
/// <param name="id"></param>
/// <exception cref="VesselExistsException"></exception>
private async Task CheckVesselIsNotADuplicate(string imo, long id)
private async Task CheckVesselIsNotADuplicate(string identifier, long id)
{
var vessel = await Context.Vessels.FirstOrDefaultAsync(x => x.IMO == imo);
var vessel = await Context.Vessels.FirstOrDefaultAsync(x => x.Identifier == identifier);
if ((vessel != null) && (vessel.Id != id))
{
var message = $"Vessel {imo} already exists";
var message = $"Vessel {identifier} already exists";
throw new VesselExistsException(message);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>ShippingRecorder.BusinessLogic</PackageId>
<PackageVersion>1.8.0.0</PackageVersion>
<PackageVersion>1.9.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2025</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -15,7 +15,7 @@
<PackageProjectUrl>https://github.com/davewalker5/ShippingRecorder</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
<NoWarn>NU1900</NoWarn>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/ShippingRecorder.BusinessLogic/Sql/MyVoyages.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SELECT s.Date,
l.Name AS "Location",
v.IMO,
v.Identifier,
rh.Name AS "Vessel",
v.Id AS "VesselId"
FROM SIGHTING s
Expand Down
8 changes: 4 additions & 4 deletions src/ShippingRecorder.Client/ApiClient/SightingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ public async Task<List<Sighting>> ListAsync(int pageNumber, int pageSize)
=> await ListSightingsAsync(null, pageNumber, pageSize);

/// <summary>
/// Get the most recent sighting of an aircraft
/// Get the most recent sighting of a vessel
/// </summary>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <returns></returns>
public async Task<Sighting> GetMostRecentVesselSightingAsync(string imo)
=> (await ListSightingsAsync($"vessel/{imo}", 1, 1))?.FirstOrDefault();
public async Task<Sighting> GetMostRecentVesselSightingAsync(string identifier)
=> (await ListSightingsAsync($"vessel/{identifier}", 1, 1))?.FirstOrDefault();

/// <summary>
/// Return a list of sightings for the specified vessel
Expand Down
24 changes: 14 additions & 10 deletions src/ShippingRecorder.Client/ApiClient/VesselClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ public async Task<Vessel> GetAsync(long id)
}

/// <summary>
/// Return the vessel with the specified IMO
/// Return the vessel with the specified identifier
/// </summary>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <returns></returns>
public async Task<Vessel> GetAsync(string imo)
public async Task<Vessel> GetAsync(string identifier)
{
var baseRoute = Settings.ApiRoutes.First(r => r.Name == RouteKey).Route;
var route = $"{baseRoute}/imo/{imo}";
var route = $"{baseRoute}/identifier/{identifier}";
var json = await SendDirectAsync(route, null, HttpMethod.Get);
var vessel = Deserialize<Vessel>(json);
return vessel;
Expand All @@ -57,17 +57,19 @@ public async Task<Vessel> GetAsync(string imo)
/// <summary>
/// Add a new vessel to the database
/// </summary>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <param name="isIMO"></param>
/// <param name="built"></param>
/// <param name="draught"></param>
/// <param name="length"></param>
/// <param name="beam"></param>
/// <returns></returns>
public async Task<Vessel> AddAsync(string imo, int? built, decimal? draught, int? length, int? beam)
public async Task<Vessel> AddAsync(string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam)
{
dynamic template = new
{
IMO = imo,
Identifier = identifier,
IsIMO = isIMO,
Built = built,
Draught = draught,
Length = length,
Expand All @@ -85,18 +87,20 @@ public async Task<Vessel> AddAsync(string imo, int? built, decimal? draught, int
/// Update an existing vessel
/// </summary>
/// <param name="id"></param>
/// <param name="imo"></param>
/// <param name="identifier"></param>
/// <param name="isIMO"></param>
/// <param name="built"></param>
/// <param name="draught"></param>
/// <param name="length"></param>
/// <param name="beam"></param>
/// <returns></returns>
public async Task<Vessel> UpdateAsync(long id, string imo, int? built, decimal? draught, int? length, int? beam)
public async Task<Vessel> UpdateAsync(long id, string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam)
{
dynamic template = new
{
Id = id,
IMO = imo,
Identifier = identifier,
IsIMO = isIMO,
Built = built,
Draught = draught,
Length = length,
Expand Down
2 changes: 1 addition & 1 deletion src/ShippingRecorder.Client/Interfaces/ISightingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface ISightingClient : IImporter, IExporter
Task<Sighting> AddAsync(long locationId, long? voyageId, long vesselId, DateTime date, bool isMyVoyage);
Task DeleteAsync(long id);
Task<List<Sighting>> ListAsync(int pageNumber, int pageSize);
Task<Sighting> GetMostRecentVesselSightingAsync(string imo);
Task<Sighting> GetMostRecentVesselSightingAsync(string identifier);
Task<List<Sighting>> ListSightingsByVesselAsync(long vesselId, int pageNumber, int pageSize);
Task<List<Sighting>> ListSightingsByLocationAsync(long locationId, int pageNumber, int pageSize);
Task<List<Sighting>> ListSightingsByDateAsync(DateTime start, DateTime end, int pageNumber, int pageSize);
Expand Down
6 changes: 3 additions & 3 deletions src/ShippingRecorder.Client/Interfaces/IVesselClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ namespace ShippingRecorder.Client.Interfaces
public interface IVesselClient : IImporter, IExporter
{
Task<Vessel> GetAsync(long id);
Task<Vessel> GetAsync(string imo);
Task<Vessel> AddAsync(string imo, int? built, decimal? draught, int? length, int? beam);
Task<Vessel> GetAsync(string identifier);
Task<Vessel> AddAsync(string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam);
Task DeleteAsync(long id);
Task<List<Vessel>> ListAsync(int pageNumber, int pageSize);
Task<Vessel> UpdateAsync(long id, string imo, int? built, decimal? draught, int? length, int? beam);
Task<Vessel> UpdateAsync(long id, string identifier, bool isIMO, int? built, decimal? draught, int? length, int? beam);
}
}
Loading