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
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
**/bin/
**/obj/
.vs/
.idea/
.git/
.gitignore
.env
*.user
*.suo
*.nupkg
README.md
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copy this file to .env before running Docker Compose.
# Use a strong local-only password that satisfies SQL Server complexity requirements.
MSSQL_SA_PASSWORD=ChangeThis_LocalOnly_2026!
25 changes: 15 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,30 @@ permissions:
jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '6.0.x'
dotnet-version: '10.0.x'

- name: Restore application
- name: Restore
run: dotnet restore CargoAPI.sln

- name: Restore tests
run: dotnet restore CargoAPI.Tests/CargoAPI.Tests.csproj

- name: Build application
- name: Build
run: dotnet build CargoAPI.sln --configuration Release --no-restore

- name: Run unit tests
run: dotnet test CargoAPI.Tests/CargoAPI.Tests.csproj --configuration Release --no-restore --verbosity normal
- name: Test
run: dotnet test CargoAPI.Tests/CargoAPI.Tests.csproj --configuration Release --no-build --verbosity normal

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build the test project before using --no-build

On a clean GitHub Actions runner, this test step fails because CargoAPI.sln does not include CargoAPI.Tests, so the preceding restore and build steps neither restore nor compile the test project. The documented --no-build behavior also implies --no-restore, leaving no test assembly or assets to execute; add the test project to the solution, build it explicitly, or remove --no-build.

Useful? React with 👍 / 👎.


- name: Validate Docker Compose configuration
env:
MSSQL_SA_PASSWORD: CI_Only_Password_2026!
run: docker compose config --quiet

- name: Build API container image
run: docker build --tag cargoapi:ci .
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ obj/
## Rider
.idea/

## Local environment
.env

## OS
Thumbs.db
.DS_Store
4 changes: 2 additions & 2 deletions CargoAPI.API/CargoAPI.API.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.15" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.36">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.11">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
42 changes: 34 additions & 8 deletions CargoAPI.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("ConnectionStrings:DefaultConnection is required.");

builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
options.UseSqlServer(connectionString));

builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
builder.Services.AddScoped<ICarrierConfigurationRepository, CarrierConfigurationRepository>();
Expand All @@ -18,38 +21,61 @@
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<ICarrierReportService, CarrierReportService>();

// Hangfire configuration using same SQL Server connection
builder.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(builder.Configuration.GetConnectionString("DefaultConnection"));
config.UseSqlServerStorage(connectionString);
});
builder.Services.AddHangfireServer();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHealthChecks();
Comment on lines 31 to +33

var app = builder.Build();

if (builder.Configuration.GetValue<bool>("Database:ApplyMigrations"))
{
using var scope = app.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await dbContext.Database.MigrateAsync();
}

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseHangfireDashboard("/hangfire");
}

// Global exception handling - must be before other middleware
app.UseMiddleware<GlobalExceptionMiddleware>();

// Hangfire Dashboard
app.UseHangfireDashboard("/hangfire");

// Register recurring job - runs every hour
RecurringJob.AddOrUpdate<ICarrierReportService>(
"carrier-reports",
service => service.GenerateReportsAsync(),
Cron.Hourly,
new RecurringJobOptions { TimeZone = TimeZoneInfo.Local });

app.MapGet("/health/live", () => Results.Ok(new
{
status = "ok",
service = "CargoAPI"
}));

app.MapGet("/health/ready", async (AppDbContext dbContext, CancellationToken cancellationToken) =>
{
if (await dbContext.Database.CanConnectAsync(cancellationToken))
{
return Results.Ok(new
{
status = "ready",
database = "reachable"
});
}

return Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
});

app.MapControllers();

app.Run();
3 changes: 3 additions & 0 deletions CargoAPI.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=CargoDb;Trusted_Connection=True;TrustServerCertificate=True;"
},
"Database": {
"ApplyMigrations": false
},
"Logging": {
"LogLevel": {
"Default": "Information",
Expand Down
4 changes: 2 additions & 2 deletions CargoAPI.Business/CargoAPI.Business.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\CargoAPI.DataAccess\CargoAPI.DataAccess.csproj" />
</ItemGroup>

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
10 changes: 5 additions & 5 deletions CargoAPI.DataAccess/CargoAPI.DataAccess.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\CargoAPI.Entities\CargoAPI.Entities.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.36">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.11">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions CargoAPI.Entities/CargoAPI.Entities.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion CargoAPI.Tests/CargoAPI.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
Comment on lines +1 to +2

COPY CargoAPI.sln ./
COPY CargoAPI.API/CargoAPI.API.csproj CargoAPI.API/
COPY CargoAPI.Business/CargoAPI.Business.csproj CargoAPI.Business/
COPY CargoAPI.DataAccess/CargoAPI.DataAccess.csproj CargoAPI.DataAccess/
COPY CargoAPI.Entities/CargoAPI.Entities.csproj CargoAPI.Entities/
COPY CargoAPI.Tests/CargoAPI.Tests.csproj CargoAPI.Tests/
RUN dotnet restore CargoAPI.API/CargoAPI.API.csproj
Comment on lines +4 to +10

COPY . .
RUN dotnet publish CargoAPI.API/CargoAPI.API.csproj \
--configuration Release \
--no-restore \
--output /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .

ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080

ENTRYPOINT ["dotnet", "CargoAPI.API.dll"]
Loading
Loading