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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy to .env and replace both values with local development secrets.
# These values are required; no credentials or JWT signing key defaults are committed.
MSSQL_SA_PASSWORD=
Jwt__SigningKey=
13 changes: 13 additions & 0 deletions backend/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.18",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
11 changes: 11 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
**/bin/
**/obj/
**/TestResults/
tests/
.git/
.github/
.env
.env.*
!*.env.example
appsettings.Development.json
appsettings.*.local.json
10 changes: 10 additions & 0 deletions backend/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
30 changes: 30 additions & 0 deletions backend/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="AutoMapper" Version="16.2.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageVersion Include="MediatR" Version="13.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.18" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.18" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="9.0.18" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.18" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.18" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.18" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.18" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.18" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.18" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.6" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.13.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
46 changes: 46 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0.316 AS base
ARG APP_UID=10001
ARG APP_GID=10001
RUN groupadd --system --gid ${APP_GID} inventoryflow \
&& useradd --uid ${APP_UID} --gid inventoryflow --create-home --home-dir /home/inventoryflow --shell /usr/sbin/nologin inventoryflow \
&& mkdir -p /src /app /home/inventoryflow/.nuget/packages \
&& chown -R inventoryflow:inventoryflow /src /app /home/inventoryflow
ENV DOTNET_CLI_HOME=/home/inventoryflow \
NUGET_PACKAGES=/home/inventoryflow/.nuget/packages
WORKDIR /src
USER inventoryflow

FROM base AS restore
COPY --chown=inventoryflow:inventoryflow Directory.Build.props Directory.Packages.props InventoryFlow.sln ./
COPY --chown=inventoryflow:inventoryflow src/InventoryFlow.Api/InventoryFlow.Api.csproj src/InventoryFlow.Api/
COPY --chown=inventoryflow:inventoryflow src/InventoryFlow.Application/InventoryFlow.Application.csproj src/InventoryFlow.Application/
COPY --chown=inventoryflow:inventoryflow src/InventoryFlow.Domain/InventoryFlow.Domain.csproj src/InventoryFlow.Domain/
COPY --chown=inventoryflow:inventoryflow src/InventoryFlow.Infrastructure/InventoryFlow.Infrastructure.csproj src/InventoryFlow.Infrastructure/
COPY --chown=inventoryflow:inventoryflow src/InventoryFlow.Shared/InventoryFlow.Shared.csproj src/InventoryFlow.Shared/
RUN dotnet restore src/InventoryFlow.Api/InventoryFlow.Api.csproj

FROM restore AS build
COPY --chown=inventoryflow:inventoryflow src ./src
RUN dotnet build src/InventoryFlow.Api/InventoryFlow.Api.csproj --configuration Release --no-restore

FROM build AS publish
RUN dotnet publish src/InventoryFlow.Api/InventoryFlow.Api.csproj --configuration Release --no-build --output /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:9.0.316 AS final
ARG APP_UID=10001
ARG APP_GID=10001
RUN groupadd --system --gid ${APP_GID} inventoryflow \
&& useradd --uid ${APP_UID} --gid inventoryflow --create-home --home-dir /home/inventoryflow --shell /usr/sbin/nologin inventoryflow \
&& mkdir -p /app \
&& chown inventoryflow:inventoryflow /app
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
COPY --from=publish --chown=inventoryflow:inventoryflow /app/publish .
USER inventoryflow
ENTRYPOINT ["dotnet", "InventoryFlow.Api.dll"]

FROM build AS migrator
COPY --chown=inventoryflow:inventoryflow .config .config
RUN dotnet tool restore
ENTRYPOINT ["dotnet", "ef", "database", "update", "--project", "src/InventoryFlow.Infrastructure", "--startup-project", "src/InventoryFlow.Api"]
129 changes: 129 additions & 0 deletions backend/InventoryFlow.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.Domain", "src\InventoryFlow.Domain\InventoryFlow.Domain.csproj", "{D6637A17-757E-4C0E-82A8-4080C183B775}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.Application", "src\InventoryFlow.Application\InventoryFlow.Application.csproj", "{3436993A-54EE-4E1C-A601-EDD2BEE33712}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.Infrastructure", "src\InventoryFlow.Infrastructure\InventoryFlow.Infrastructure.csproj", "{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.Shared", "src\InventoryFlow.Shared\InventoryFlow.Shared.csproj", "{526EF02F-24EE-49E0-AC41-0F7174C5B961}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.Api", "src\InventoryFlow.Api\InventoryFlow.Api.csproj", "{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.UnitTests", "tests\InventoryFlow.UnitTests\InventoryFlow.UnitTests.csproj", "{A1976E8B-EC99-4F3F-A5C1-7B196893A477}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryFlow.IntegrationTests", "tests\InventoryFlow.IntegrationTests\InventoryFlow.IntegrationTests.csproj", "{5C4062B2-3592-421D-B956-DACAB8F01B42}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D6637A17-757E-4C0E-82A8-4080C183B775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Debug|x64.ActiveCfg = Debug|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Debug|x64.Build.0 = Debug|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Debug|x86.ActiveCfg = Debug|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Debug|x86.Build.0 = Debug|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Release|Any CPU.Build.0 = Release|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Release|x64.ActiveCfg = Release|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Release|x64.Build.0 = Release|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Release|x86.ActiveCfg = Release|Any CPU
{D6637A17-757E-4C0E-82A8-4080C183B775}.Release|x86.Build.0 = Release|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Debug|x64.ActiveCfg = Debug|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Debug|x64.Build.0 = Debug|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Debug|x86.ActiveCfg = Debug|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Debug|x86.Build.0 = Debug|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Release|Any CPU.Build.0 = Release|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Release|x64.ActiveCfg = Release|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Release|x64.Build.0 = Release|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Release|x86.ActiveCfg = Release|Any CPU
{3436993A-54EE-4E1C-A601-EDD2BEE33712}.Release|x86.Build.0 = Release|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Debug|x64.ActiveCfg = Debug|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Debug|x64.Build.0 = Debug|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Debug|x86.ActiveCfg = Debug|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Debug|x86.Build.0 = Debug|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Release|Any CPU.Build.0 = Release|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Release|x64.ActiveCfg = Release|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Release|x64.Build.0 = Release|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Release|x86.ActiveCfg = Release|Any CPU
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE}.Release|x86.Build.0 = Release|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Debug|Any CPU.Build.0 = Debug|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Debug|x64.ActiveCfg = Debug|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Debug|x64.Build.0 = Debug|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Debug|x86.ActiveCfg = Debug|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Debug|x86.Build.0 = Debug|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Release|Any CPU.ActiveCfg = Release|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Release|Any CPU.Build.0 = Release|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Release|x64.ActiveCfg = Release|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Release|x64.Build.0 = Release|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Release|x86.ActiveCfg = Release|Any CPU
{526EF02F-24EE-49E0-AC41-0F7174C5B961}.Release|x86.Build.0 = Release|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Debug|x64.ActiveCfg = Debug|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Debug|x64.Build.0 = Debug|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Debug|x86.ActiveCfg = Debug|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Debug|x86.Build.0 = Debug|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Release|Any CPU.Build.0 = Release|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Release|x64.ActiveCfg = Release|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Release|x64.Build.0 = Release|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Release|x86.ActiveCfg = Release|Any CPU
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245}.Release|x86.Build.0 = Release|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Debug|x64.ActiveCfg = Debug|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Debug|x64.Build.0 = Debug|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Debug|x86.ActiveCfg = Debug|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Debug|x86.Build.0 = Debug|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Release|Any CPU.Build.0 = Release|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Release|x64.ActiveCfg = Release|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Release|x64.Build.0 = Release|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Release|x86.ActiveCfg = Release|Any CPU
{A1976E8B-EC99-4F3F-A5C1-7B196893A477}.Release|x86.Build.0 = Release|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Debug|x64.ActiveCfg = Debug|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Debug|x64.Build.0 = Debug|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Debug|x86.ActiveCfg = Debug|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Debug|x86.Build.0 = Debug|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Release|Any CPU.Build.0 = Release|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Release|x64.ActiveCfg = Release|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Release|x64.Build.0 = Release|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Release|x86.ActiveCfg = Release|Any CPU
{5C4062B2-3592-421D-B956-DACAB8F01B42}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D6637A17-757E-4C0E-82A8-4080C183B775} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{3436993A-54EE-4E1C-A601-EDD2BEE33712} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{20CB6ECB-430E-4CD9-B392-C196CFD6D7AE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{526EF02F-24EE-49E0-AC41-0F7174C5B961} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{D0C3B3C5-8FA6-4B19-8BE9-CB2E3FBD6245} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{A1976E8B-EC99-4F3F-A5C1-7B196893A477} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{5C4062B2-3592-421D-B956-DACAB8F01B42} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
15 changes: 15 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Inventory Flow API

Set the database connection string and JWT signing secret outside tracked configuration before running the API:

```bash
cd backend/src/InventoryFlow.Api
dotnet user-secrets set "Jwt:SigningKey" "replace-with-at-least-32-random-bytes"
export ConnectionStrings__InventoryFlowDatabase='Server=localhost,1433;Database=InventoryFlow;User Id=sa;Password=<strong-password>;TrustServerCertificate=True'
```

Production deployments must supply `Jwt__SigningKey` from a secret manager and set exact `Cors__AllowedOrigins__0` values. The browser refresh cookie is `HttpOnly`, `SameSite=Strict`, and requires same-site SPA/API deployment. Apply migrations with `dotnet ef database update --project src/InventoryFlow.Infrastructure --startup-project src/InventoryFlow.Api` from this directory.

## Workspace tenancy

Self-registration creates a personal workspace and immutable Owner membership. Authentication responses include that server-resolved workspace; workspace IDs are never accepted from the refresh cookie. Future multi-workspace switching and invitations are intentionally not yet implemented.
Loading
Loading