Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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