Compare commits
1 Commits
main
..
258a5ac31c
| Author | SHA1 | Date | |
|---|---|---|---|
| 258a5ac31c |
@@ -1,99 +0,0 @@
|
|||||||
name: Build & Push MinAttest
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: gitea.theriise.net/steinhelge
|
|
||||||
REGISTRY_HOST: gitea.theriise.net
|
|
||||||
REGISTRY_NAMESPACE: steinhelge
|
|
||||||
IMAGE_NAME: minattest
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
wake-zorin:
|
|
||||||
name: Wake Zorin
|
|
||||||
runs-on: waker # t610-waker-runneren med label "waker"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Wake Zorin via WOL
|
|
||||||
run: /home/steinhelge/wake-zorin.sh
|
|
||||||
|
|
||||||
- name: Vent på at Zorin våkner
|
|
||||||
run: sleep 10
|
|
||||||
|
|
||||||
build-and-push:
|
|
||||||
runs-on: self-hosted
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# Finn ut hvilken tag vi skal bruke
|
|
||||||
- name: Set image tag
|
|
||||||
run: |
|
|
||||||
# Gitea prøver å være kompatibel med GitHub Actions
|
|
||||||
# Noen ganger er GITHUB_REF_NAME bare 'main', andre ganger 'refs/heads/main'
|
|
||||||
REF="${GITHUB_REF_NAME:-$GITHUB_REF}"
|
|
||||||
REF="${REF#refs/heads/}"
|
|
||||||
|
|
||||||
if [ "$REF" = "main" ]; then
|
|
||||||
TAG="latest"
|
|
||||||
elif [ "$REF" = "dev" ]; then
|
|
||||||
TAG="dev"
|
|
||||||
else
|
|
||||||
# fallback: bruk branchnavnet som tag, men uten skråstreker
|
|
||||||
TAG="$(echo "$REF" | tr '/' '-')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Bygger med TAG=$TAG"
|
|
||||||
echo "TAG=$TAG" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Log in to Gitea Container Registry
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login $REGISTRY -u steinhelge --password-stdin
|
|
||||||
|
|
||||||
# ---------- Frontend ----------
|
|
||||||
- name: Build Frontend (${{ env.TAG }})
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
-t $REGISTRY/minattest-frontend:${TAG} \
|
|
||||||
-f frontend/minattest-app/Dockerfile \
|
|
||||||
frontend/minattest-app
|
|
||||||
|
|
||||||
- name: Push Frontend (${{ env.TAG }})
|
|
||||||
run: |
|
|
||||||
docker push $REGISTRY/minattest-frontend:${TAG}
|
|
||||||
|
|
||||||
# ---------- Backend API ----------
|
|
||||||
- name: Build API (${{ env.TAG }})
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
-t $REGISTRY/minattest-api:${TAG} \
|
|
||||||
-f backend/Dockerfile \
|
|
||||||
backend
|
|
||||||
|
|
||||||
- name: Push API (${{ env.TAG }})
|
|
||||||
run: |
|
|
||||||
docker push $REGISTRY/minattest-api:${TAG}
|
|
||||||
|
|
||||||
# ---------- App Host / BFF ----------
|
|
||||||
- name: Build App Host (${{ env.TAG }})
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
-t $REGISTRY/minattest-app-host:${TAG} \
|
|
||||||
-f frontend/minattest-app-host/Dockerfile \
|
|
||||||
frontend/minattest-app-host
|
|
||||||
|
|
||||||
- name: Push App Host (${{ env.TAG }})
|
|
||||||
run: |
|
|
||||||
docker push $REGISTRY/minattest-app-host:${TAG}
|
|
||||||
|
|
||||||
- name: Deploy (docker compose pull + up)
|
|
||||||
run: |
|
|
||||||
cd /srv/minattest # ← Endre denne pathen hvis compose ligger et annet sted
|
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
-346
@@ -1,346 +0,0 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# CodeRush
|
|
||||||
.cr/
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Nx cache
|
|
||||||
.nx/cache/
|
|
||||||
|
|
||||||
# Environment files with secrets
|
|
||||||
**/.env
|
|
||||||
|
|
||||||
# Ignore Entropy.bin file created by docker compose volume mount for sql database
|
|
||||||
Entropy.bin
|
|
||||||
|
|
||||||
tools/
|
|
||||||
.structurizr-cli/
|
|
||||||
plantuml/
|
|
||||||
structurizr-cli*.zip
|
|
||||||
|
|
||||||
# Genererte mellomfiler
|
|
||||||
*.puml
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# ci ping ma. 17. nov. 20:22:27 +0100 2025
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# test ma. 17. nov. 17:50:01 +0100 2025
|
|
||||||
# test ma. 17. nov. 17:51:01 +0100 2025
|
|
||||||
@@ -1,84 +1,2 @@
|
|||||||
# 📄 Produktdokumentasjon – MinAttest
|
# minattest
|
||||||
|
|
||||||
## 1. Hva er MinAttest?
|
|
||||||
MinAttest er en digital plattform for utstedelse, lagring, deling og verifisering av arbeidsattester.
|
|
||||||
Målet er å erstatte papir/PDF-attester med en **standardisert, sikker og brukervennlig løsning** som gir verdi både for arbeidsgivere, ansatte og rekrutterere.
|
|
||||||
|
|
||||||
- For ansatte: trygg tilgang til attester hele livet.
|
|
||||||
- For arbeidsgivere: enkel, sikker og standardisert attesthåndtering.
|
|
||||||
- For rekrutterere: rask verifisering og innsikt i kandidatens arbeidshistorikk.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Brukere i systemet
|
|
||||||
|
|
||||||
### Privatperson (ansatt / tidligere ansatt)
|
|
||||||
- Logger inn med **BankID** (MVP) eller **ID-porten** (senere).
|
|
||||||
- Kan se, laste ned og dele sine attester.
|
|
||||||
- Kan laste opp tidligere attester (merkes som *ikke verifisert*).
|
|
||||||
- Får en samlet oversikt over arbeidserfaring og attester.
|
|
||||||
|
|
||||||
### Bedrift (HR / leder)
|
|
||||||
- Logger inn med **Azure AD / Entra ID** (via B2C).
|
|
||||||
- Kan laste opp og utstede attester på ansatte (knyttet til fødselsnummer).
|
|
||||||
- Har oversikt over attester bedriften har utstedt.
|
|
||||||
|
|
||||||
### Rekrutterer / tredjepart
|
|
||||||
- Får tilgang via en **verifiseringslenke** (delt av kandidaten).
|
|
||||||
- Kan validere at en attest faktisk er utstedt av en bedrift.
|
|
||||||
- Har ikke egen innlogging i første versjon.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Hovedfunksjoner i første versjon (MVP)
|
|
||||||
|
|
||||||
- **Innlogging**
|
|
||||||
- Privatpersoner: BankID.
|
|
||||||
- Bedrifter: Entra ID (federert via B2C).
|
|
||||||
|
|
||||||
- **Attesthåndtering**
|
|
||||||
- Bedrifter kan laste opp attester for ansatte.
|
|
||||||
- Privatpersoner kan se egne attester.
|
|
||||||
- Mulighet for privatperson å laste opp tidligere attester (markeres *ikke verifisert*).
|
|
||||||
|
|
||||||
- **Deling**
|
|
||||||
- Privatperson kan dele en attest via en unik verifiseringslenke.
|
|
||||||
|
|
||||||
- **Grunnleggende sikkerhet**
|
|
||||||
- Fødselsnummer brukes for å koble attest til riktig person (lagres kryptert).
|
|
||||||
- Tilgangskontroll:
|
|
||||||
- Privatperson ser kun egne attester.
|
|
||||||
- Bedrift ser kun egne utstedelser.
|
|
||||||
- All data lagres kryptert og tilgjengeliggjøres kun via autentisert tilgang.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Sikkerhetsmodell
|
|
||||||
|
|
||||||
- **Autentisering:** BankID (privatperson), Entra ID (bedrifter).
|
|
||||||
- **Autorisasjon:** RBAC (rollebasert tilgang) i API.
|
|
||||||
- **Data:**
|
|
||||||
- Fødselsnummer krypteres i databasen.
|
|
||||||
- Attester lagres sikkert (Azure Blob Storage + SAS URL).
|
|
||||||
- **Deling:** Verifiseringslenker utstedes med tidsbegrenset token.
|
|
||||||
- **Logging og sporing:** Hvem har utstedt, åpnet eller delt en attest logges.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Fremtidige features (roadmap)
|
|
||||||
|
|
||||||
### Kortsiktig (V2–V3)
|
|
||||||
- Generere attester fra mal direkte i systemet.
|
|
||||||
- Signering med BankID/eSignering.
|
|
||||||
- Varsling (epost/SMS) når ny attest er tilgjengelig.
|
|
||||||
- Admin-dashboard med statistikk for bedrifter.
|
|
||||||
|
|
||||||
### Langsiktig / visjonære features
|
|
||||||
- **Automatisk CV-generering** basert på attester (kronologisk arbeidshistorikk).
|
|
||||||
- **Integrert AI**:
|
|
||||||
- Generere jobbsøknadstekster basert på brukerens attester + CV.
|
|
||||||
- Foreslå relevante stillinger basert på historikk.
|
|
||||||
- **Jobb-annonsekobling**:
|
|
||||||
- Privatperson kan koble seg direkte på en stillingsannonse og bruke sine attester som grunnlag for søknad.
|
|
||||||
- **Integrasjon med HR-systemer** (Visma, SAP, Tripletex m.fl.) for automatisk generering av attester.
|
|
||||||
- **Internasjonalisering** – kunne brukes utenfor Norge (andre eID-løsninger).
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"isRoot": true,
|
|
||||||
"tools": {
|
|
||||||
"dotnet-ef": {
|
|
||||||
"version": "9.0.11",
|
|
||||||
"commands": [
|
|
||||||
"dotnet-ef"
|
|
||||||
],
|
|
||||||
"rollForward": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# backend/Dockerfile
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
|
||||||
WORKDIR /app
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN dotnet restore MinAttest.Api.sln
|
|
||||||
RUN dotnet publish src/MinAttest.Api/MinAttest.Api.csproj -c Release -o /app/publish /p:UseAppHost=false
|
|
||||||
|
|
||||||
FROM base AS final
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=build /app/publish .
|
|
||||||
ENTRYPOINT ["dotnet", "MinAttest.Api.dll"]
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
|
|
||||||
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}") = "MinAttest.Api", "src\MinAttest.Api\MinAttest.Api.csproj", "{FD304824-536A-4282-8B28-18FD2939F2E9}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.AppHost", "MinAttest.AppHost\MinAttest.AppHost.csproj", "{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.ServiceDefaults", "MinAttest.ServiceDefaults\MinAttest.ServiceDefaults.csproj", "{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.Domain", "src\MinAttest.Domain\MinAttest.Domain.csproj", "{87321FFD-8C90-4268-80EE-5767BE1E3B98}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.Infrastructure", "src\MinAttest.Infrastructure\MinAttest.Infrastructure.csproj", "{90654289-0EC1-4BD9-82BA-C13FF5577705}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.Application", "src\MinAttest.Application\MinAttest.Application.csproj", "{D455F132-39FE-4350-9C36-1B81A5F238C6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.Contracts", "src\MinAttest.Contracts\MinAttest.Contracts.csproj", "{CBB5189D-C63A-4CEC-AF59-391C31E36985}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinAttest.Tests", "tests\MinAttest.Tests\MinAttest.Tests.csproj", "{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}"
|
|
||||||
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
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{8602EA83-F78D-4CD4-92CA-E539FC68E8E1}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{832B2A1B-7957-4ED7-A838-9C8D0AE0FA88}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{FD304824-536A-4282-8B28-18FD2939F2E9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
|
||||||
{87321FFD-8C90-4268-80EE-5767BE1E3B98} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
|
||||||
{90654289-0EC1-4BD9-82BA-C13FF5577705} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
|
||||||
{D455F132-39FE-4350-9C36-1B81A5F238C6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
|
||||||
{CBB5189D-C63A-4CEC-AF59-391C31E36985} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
|
||||||
{DD19FAFD-79C6-48D4-8AFC-5D4DE9C01C5D} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<Sdk Name="Aspire.AppHost.Sdk" Version="9.4.2" />
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsAspireHost>true</IsAspireHost>
|
|
||||||
<UserSecretsId>627e5d7f-da52-404a-8d76-c9ba6c1f65f5</UserSecretsId>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.4.2" />
|
|
||||||
<PackageReference Include="Aspire.Hosting.SqlServer" Version="9.4.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\src\MinAttest.Api\MinAttest.Api.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
var builder = DistributedApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
var sqlPass = builder.AddParameter("sqlPassword", "Your_password123");
|
|
||||||
|
|
||||||
var sql = builder.AddSqlServer("minattest-dev-sql", password: sqlPass, port: 14335)
|
|
||||||
.WithDataVolume()
|
|
||||||
.WithLifetime(ContainerLifetime.Persistent);
|
|
||||||
|
|
||||||
builder.AddProject<Projects.MinAttest_Api>("minattest-api")
|
|
||||||
.WithExternalHttpEndpoints()
|
|
||||||
.WithReference(sql)
|
|
||||||
.WaitFor(sql);
|
|
||||||
|
|
||||||
builder.Build().Run();
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
|
||||||
"profiles": {
|
|
||||||
"https": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"applicationUrl": "https://localhost:17105;http://localhost:15182",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"DOTNET_ENVIRONMENT": "Development",
|
|
||||||
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21157",
|
|
||||||
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22256"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"applicationUrl": "http://localhost:15182",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"DOTNET_ENVIRONMENT": "Development",
|
|
||||||
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19259",
|
|
||||||
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20072"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning",
|
|
||||||
"Aspire.Hosting.Dcp": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.ServiceDiscovery;
|
|
||||||
using OpenTelemetry;
|
|
||||||
using OpenTelemetry.Metrics;
|
|
||||||
using OpenTelemetry.Trace;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
|
|
||||||
// This project should be referenced by each service project in your solution.
|
|
||||||
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
builder.ConfigureOpenTelemetry();
|
|
||||||
|
|
||||||
builder.AddDefaultHealthChecks();
|
|
||||||
|
|
||||||
builder.Services.AddServiceDiscovery();
|
|
||||||
|
|
||||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
|
||||||
{
|
|
||||||
// Turn on resilience by default
|
|
||||||
http.AddStandardResilienceHandler();
|
|
||||||
|
|
||||||
// Turn on service discovery by default
|
|
||||||
http.AddServiceDiscovery();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Uncomment the following to restrict the allowed schemes for service discovery.
|
|
||||||
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
|
|
||||||
// {
|
|
||||||
// options.AllowedSchemes = ["https"];
|
|
||||||
// });
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
builder.Logging.AddOpenTelemetry(logging =>
|
|
||||||
{
|
|
||||||
logging.IncludeFormattedMessage = true;
|
|
||||||
logging.IncludeScopes = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddOpenTelemetry()
|
|
||||||
.WithMetrics(metrics =>
|
|
||||||
{
|
|
||||||
metrics.AddAspNetCoreInstrumentation()
|
|
||||||
.AddHttpClientInstrumentation()
|
|
||||||
.AddRuntimeInstrumentation();
|
|
||||||
})
|
|
||||||
.WithTracing(tracing =>
|
|
||||||
{
|
|
||||||
tracing.AddAspNetCoreInstrumentation()
|
|
||||||
// Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
|
|
||||||
//.AddGrpcClientInstrumentation()
|
|
||||||
.AddHttpClientInstrumentation();
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.AddOpenTelemetryExporters();
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
|
|
||||||
|
|
||||||
if (useOtlpExporter)
|
|
||||||
{
|
|
||||||
builder.Services.AddOpenTelemetry().UseOtlpExporter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
|
|
||||||
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
|
|
||||||
//{
|
|
||||||
// builder.Services.AddOpenTelemetry()
|
|
||||||
// .UseAzureMonitor();
|
|
||||||
//}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
builder.Services.AddHealthChecks()
|
|
||||||
// Add a default liveness check to ensure app is responsive
|
|
||||||
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WebApplication MapDefaultEndpoints(this WebApplication app)
|
|
||||||
{
|
|
||||||
// Adding health checks endpoints to applications in non-development environments has security implications.
|
|
||||||
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
// All health checks must pass for app to be considered ready to accept traffic after starting
|
|
||||||
app.MapHealthChecks("/health");
|
|
||||||
|
|
||||||
// Only health checks tagged with the "live" tag must pass for app to be considered alive
|
|
||||||
app.MapHealthChecks("/alive", new HealthCheckOptions
|
|
||||||
{
|
|
||||||
Predicate = r => r.Tags.Contains("live")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsAspireSharedProject>true</IsAspireSharedProject>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.*" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.*" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<!-- Force use of public NuGet only for everything under ./backend -->
|
|
||||||
<packageSources>
|
|
||||||
<clear />
|
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
|
||||||
</packageSources>
|
|
||||||
<!-- Map all packages to nuget.org so no other source is probed -->
|
|
||||||
<packageSourceMapping>
|
|
||||||
<packageSource key="nuget.org">
|
|
||||||
<package pattern="*" />
|
|
||||||
</packageSource>
|
|
||||||
</packageSourceMapping>
|
|
||||||
<!-- Optional: explicitly disable common local/VS offline feeds -->
|
|
||||||
<disabledPackageSources>
|
|
||||||
<add key="Microsoft Visual Studio Offline Packages" value="true" />
|
|
||||||
</disabledPackageSources>
|
|
||||||
<!-- Avoid prerelease unless explicitly requested (optional) -->
|
|
||||||
<!--
|
|
||||||
<config>
|
|
||||||
<add key="repositoryPath" value=".\\packages" />
|
|
||||||
</config>
|
|
||||||
-->
|
|
||||||
<!-- Keep deterministic restores/reproducibility (optional, can be removed) -->
|
|
||||||
<bindingRedirects>
|
|
||||||
<add key="skip" value="false" />
|
|
||||||
</bindingRedirects>
|
|
||||||
</configuration>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# MinAttest Backend
|
|
||||||
|
|
||||||
Backend for MinAttest som .NET 9 Web API, EF Core (SQL Server) og .NET Aspire for lokal orkestrering (API + databaser i Docker).
|
|
||||||
|
|
||||||
**Hovedpunkter**
|
|
||||||
- API under `/api/v1` (uten autentisering – POC).
|
|
||||||
- EF Core med SQL Server og migrasjoner. Status på attester normaliseres automatisk ved lagring.
|
|
||||||
- .NET Aspire AppHost starter SQL Server‑container og injiserer connection string til API.
|
|
||||||
|
|
||||||
**Struktur**
|
|
||||||
- `MinAttest.sln` – løsning for backend
|
|
||||||
- `src/MinAttest.Api/` – Web API (.http testkall, CORS, routing)
|
|
||||||
- `src/MinAttest.Domain/` – domene‑modeller (POCOs, enums)
|
|
||||||
- `src/MinAttest.Infrastructure/` – EF Core (DbContext, migrasjoner, design‑time factory)
|
|
||||||
- `MinAttest.AppHost/` – .NET Aspire AppHost (Docker orkestrering: SQL Server + API)
|
|
||||||
- `MinAttest.ServiceDefaults/` – felles konfig (telemetri, health, m.m.)
|
|
||||||
|
|
||||||
**Forutsetninger**
|
|
||||||
- .NET SDK 9.x (`dotnet --info`)
|
|
||||||
- Docker Desktop i gang (for AppHost/SQL Server)
|
|
||||||
|
|
||||||
**Kjøre lokalt (anbefalt: Aspire)**
|
|
||||||
- Start alt (API + SQL Server i Docker):
|
|
||||||
- `dotnet run --project backend/MinAttest.AppHost`
|
|
||||||
- AppHost eksponerer API og oppretter databasen. API kjører `Migrate()` ved oppstart.
|
|
||||||
|
|
||||||
**Kjøre kun API (uten Aspire)**
|
|
||||||
- Sørg for en kjørende SQL Server lokalt/ekstern.
|
|
||||||
- Sett connection string i `src/MinAttest.Api/appsettings.Development.json` (felt `ConnectionStrings:Default`).
|
|
||||||
- Start API: `dotnet run --project backend/src/MinAttest.Api/MinAttest.Api.csproj`
|
|
||||||
|
|
||||||
**EF Core (migrasjoner)**
|
|
||||||
- Verktøy er installert lokalt via tool‑manifest i `backend/.config`.
|
|
||||||
- Kjør fra `backend/` mappen:
|
|
||||||
- Legg til migrasjon (prosjekt: Infrastructure, startup: Api):
|
|
||||||
- `dotnet tool run dotnet-ef migrations add <Navn> --project src/MinAttest.Infrastructure/MinAttest.Infrastructure.csproj --startup-project src/MinAttest.Api/MinAttest.Api.csproj --output-dir Data/Migrations`
|
|
||||||
- Oppdater database:
|
|
||||||
- `dotnet tool run dotnet-ef database update --project src/MinAttest.Infrastructure/MinAttest.Infrastructure.csproj --startup-project src/MinAttest.Api/MinAttest.Api.csproj`
|
|
||||||
|
|
||||||
**Viktige endepunkter (POC)**
|
|
||||||
- Health: `GET /api/v1/health` – svarer `200 OK` når API og database er tilgjengelig, ellers `503 Service Unavailable`.
|
|
||||||
- Persons: `POST /api/v1/persons`, `GET /api/v1/persons/{id}`
|
|
||||||
- Employers: `POST /api/v1/employers`, `GET /api/v1/employers`, `GET /api/v1/employers/{id}`
|
|
||||||
- Attests (lagring/lesing):
|
|
||||||
- `GET /api/v1/attests?personId=&employerId=&take=`
|
|
||||||
- `GET /api/v1/attests/{id}`
|
|
||||||
- `GET /api/v1/attests/{id}/download`
|
|
||||||
- `POST /api/v1/attests/uploads` (POC)
|
|
||||||
- `POST /api/v1/attests/uploads/{uploadId}/complete` (skaper attest i DB)
|
|
||||||
- Shares: `POST/GET/DELETE /api/v1/attests/{id}/shares...`
|
|
||||||
- Verify: `GET /api/v1/verify/{code}`
|
|
||||||
|
|
||||||
Se også `src/MinAttest.Api/MinAttest.Api.http` for eksempelkall.
|
|
||||||
|
|
||||||
**Health checks**
|
|
||||||
- I tillegg til API‑endepunktet over, eksponeres også standard health‑endepunkter via ServiceDefaults i utviklingsmiljø:
|
|
||||||
- Readiness: `GET /health` – inkluderer DB‑sjekk (EF Core `AddDbContextCheck`).
|
|
||||||
- Liveness: `GET /alive` – enkel "self"‑sjekk.
|
|
||||||
|
|
||||||
**Navngiving av endpoints (refaktorering)**
|
|
||||||
- Extension‑metodene for routing bruker ikke lenger postfixen "Endpoints".
|
|
||||||
- `MapHealthEndpoints` → `MapHealth`
|
|
||||||
- `MapPersonsEndpoints` → `MapPersons`
|
|
||||||
- `MapEmployerEndpoints` → `MapEmployer`
|
|
||||||
- `MapShareLinksEndpoints` → `MapShareLinks`
|
|
||||||
|
|
||||||
**Domenemodell (kjerne)**
|
|
||||||
- Person (Persons) ↔ Attest (Attests): 1:n (obligatorisk FK `PersonId`)
|
|
||||||
- Employer (Employers) ↔ Attest: 1:n (valgfri FK `EmployerId`)
|
|
||||||
- Uverifisert attest: `EmployerId = null` og `Status = Unverified`
|
|
||||||
- Verifisert: `EmployerId != null` og `Status = Issued` (eller `Revoked` ved tilbaketrekking)
|
|
||||||
- DB‑constraint sikrer konsistente kombinasjoner av `EmployerId`/`Status`.
|
|
||||||
|
|
||||||
**CORS (dev)**
|
|
||||||
- Dev‑origins er konfigurert i `src/MinAttest.Api/appsettings.Development.json` under `Cors:AllowedOrigins`.
|
|
||||||
- Hvis ingen origins er satt, er CORS av (ingen cross‑origin tillatt).
|
|
||||||
|
|
||||||
**Videre**
|
|
||||||
- Auth (BankID/Entra via B2C) legges på senere.
|
|
||||||
- Blob Storage (SAS) for reelle nedlastingslenker.
|
|
||||||
- Prod‑CORS settes eksplisitt når frontend‑domene er klart.
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
using MediatR;
|
|
||||||
using MinAttest.Application.Features.Employers.Commands;
|
|
||||||
using MinAttest.Application.Features.Employers.Queries;
|
|
||||||
using MinAttest.Application.Features.Attests.Commands;
|
|
||||||
using MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
namespace MinAttest.Api.Features.Employer;
|
|
||||||
|
|
||||||
public static class Employers
|
|
||||||
{
|
|
||||||
public static RouteGroupBuilder MapEmployer(this RouteGroupBuilder group)
|
|
||||||
{
|
|
||||||
var employers = group.MapGroup("/employers");
|
|
||||||
|
|
||||||
employers.MapPost("/", UpsertEmployer)
|
|
||||||
.WithName("UpsertEmployer")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapGet("/", ListEmployers)
|
|
||||||
.WithName("ListEmployers")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapGet("/{id:guid}", GetEmployer)
|
|
||||||
.WithName("GetEmployer")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapGet("/{employerId:guid}/users", ListEmployerUsers)
|
|
||||||
.WithName("ListEmployerUsers")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapGet("/{employerId:guid}/users/{userId:guid}", GetEmployerUser)
|
|
||||||
.WithName("GetEmployerUser")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapPost("/{employerId:guid}/users", UpsertEmployerUser)
|
|
||||||
.WithName("UpsertEmployerUser")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapDelete("/{employerId:guid}/users/{userId:guid}", DeleteEmployerUser)
|
|
||||||
.WithName("DeleteEmployerUser")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapPost("/{id:guid}/attests", IssueAttestForEmployer)
|
|
||||||
.WithName("IssueAttestForEmployer")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapGet("/{id:guid}/attests", ListAttestsForEmployer)
|
|
||||||
.WithName("ListAttestsForEmployer")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
employers.MapGet("/{id:guid}/attests/{attestId:guid}/download", DownloadAttestForEmployer)
|
|
||||||
.WithName("DownloadAttestForEmployer")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> IssueAttestForEmployer(Guid id, EmployerAttestUploadRequest req, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var createdId = await mediator.Send(new EmployerIssueAttestCommand(id, req), ct);
|
|
||||||
if (createdId is null) return Results.NotFound();
|
|
||||||
return Results.Created($"/api/v1/employers/{id}/attests/{createdId}", new { attestId = createdId });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> ListAttestsForEmployer(Guid id, int? take, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var items = await mediator.Send(new ListEmployerAttestsQuery(id, take), ct);
|
|
||||||
return Results.Ok(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> DownloadAttestForEmployer(Guid id, Guid attestId, bool? inline, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var content = await mediator.Send(new GetAttestContentQuery(attestId), ct);
|
|
||||||
if (content is null) return Results.NotFound();
|
|
||||||
var contentType = NormalizeContentType(content.Content, content.ContentType);
|
|
||||||
var fileName = NormalizeFileName(content.FileName, attestId, contentType);
|
|
||||||
if (inline == true)
|
|
||||||
{
|
|
||||||
return Results.File(content.Content, contentType);
|
|
||||||
}
|
|
||||||
return Results.File(content.Content, contentType, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeContentType(byte[] data, string? original)
|
|
||||||
{
|
|
||||||
var ct = string.IsNullOrWhiteSpace(original) || string.Equals(original, "application/octet-stream", StringComparison.OrdinalIgnoreCase)
|
|
||||||
? null
|
|
||||||
: original;
|
|
||||||
if (ct is null)
|
|
||||||
{
|
|
||||||
if (data is { Length: >= 4 } && data[0] == 0x25 && data[1] == 0x50 && data[2] == 0x44 && data[3] == 0x46)
|
|
||||||
{
|
|
||||||
return "application/pdf";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ct ?? "application/pdf";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeFileName(string? name, Guid attestId, string contentType)
|
|
||||||
{
|
|
||||||
var n = string.IsNullOrWhiteSpace(name) ? $"attest-{attestId}" : name!;
|
|
||||||
if (string.Equals(contentType, "application/pdf", StringComparison.OrdinalIgnoreCase) && !n.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
n += ".pdf";
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> UpsertEmployer(EmployerUpsertRequest req, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new UpsertEmployerCommand(req.OrgNumber, req.Name), ct);
|
|
||||||
return Results.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> ListEmployers(IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new ListEmployersQuery(), ct);
|
|
||||||
return Results.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Results<Ok<EmployerResponse>, NotFound>> GetEmployer(Guid id, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new GetEmployerQuery(id), ct);
|
|
||||||
if (resp is null) return TypedResults.NotFound();
|
|
||||||
return TypedResults.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> ListEmployerUsers(Guid employerId, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new ListEmployerUsersQuery(employerId), ct);
|
|
||||||
return Results.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Results<Ok<EmployerUserResponse>, NotFound>> GetEmployerUser(Guid employerId, Guid userId, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new GetEmployerUserQuery(employerId, userId), ct);
|
|
||||||
if (resp is null) return TypedResults.NotFound();
|
|
||||||
return TypedResults.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Results<Ok<EmployerUserResponse>, NotFound, BadRequest<string>>> UpsertEmployerUser(Guid employerId, EmployerUserUpsertRequest req, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new UpsertEmployerUserCommand(employerId, req.ExternalObjectId, req.Email, req.Name, req.Role), ct);
|
|
||||||
if (resp is null) return TypedResults.NotFound();
|
|
||||||
return TypedResults.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Results<NoContent, NotFound>> DeleteEmployerUser(Guid employerId, Guid userId, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var ok = await mediator.Send(new DeleteEmployerUserCommand(employerId, userId), ct);
|
|
||||||
if (!ok) return TypedResults.NotFound();
|
|
||||||
return TypedResults.NoContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
namespace MinAttest.Api.Features.Health;
|
|
||||||
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
public static class Health
|
|
||||||
{
|
|
||||||
public static RouteGroupBuilder MapHealth(this RouteGroupBuilder group)
|
|
||||||
{
|
|
||||||
group.MapGet("/health", GetHealth)
|
|
||||||
.WithName("Health")
|
|
||||||
.WithOpenApi();
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> GetHealth(AppDbContext db, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var canConnect = await db.Database.CanConnectAsync(ct);
|
|
||||||
var payload = new
|
|
||||||
{
|
|
||||||
status = canConnect ? "OK" : "DEGRADED",
|
|
||||||
database = canConnect ? "Up" : "Down",
|
|
||||||
time = DateTimeOffset.UtcNow
|
|
||||||
};
|
|
||||||
return canConnect ? Results.Ok(payload) : Results.Json(payload, statusCode: StatusCodes.Status503ServiceUnavailable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
|
||||||
using MinAttest.Contracts.Persons;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MediatR;
|
|
||||||
using MinAttest.Application.Features.Persons.Commands;
|
|
||||||
using MinAttest.Application.Features.Persons.Queries;
|
|
||||||
using MinAttest.Application.Features.Attests.Commands;
|
|
||||||
using MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
namespace MinAttest.Api.Features.Persons;
|
|
||||||
|
|
||||||
public static class Persons
|
|
||||||
{
|
|
||||||
public static RouteGroupBuilder MapPersons(this RouteGroupBuilder group)
|
|
||||||
{
|
|
||||||
var persons = group.MapGroup("/persons");
|
|
||||||
|
|
||||||
persons.MapPost("/", UpsertPerson)
|
|
||||||
.WithName("UpsertPerson")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
persons.MapGet("/{id:guid}", GetPerson)
|
|
||||||
.WithName("GetPerson")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
persons.MapPost("/{id:guid}/attests", UploadAttestForPerson)
|
|
||||||
.WithName("UploadAttestForPerson")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
persons.MapGet("/{id:guid}/attests", ListAttestsForPerson)
|
|
||||||
.WithName("ListAttestsForPerson")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
persons.MapGet("/{id:guid}/attests/{attestId:guid}/download", DownloadAttestForPerson)
|
|
||||||
.WithName("DownloadAttestForPerson")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> UpsertPerson(PersonUpsertRequest req, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new UpsertPersonCommand(req.NationalIdHash, req.Email, req.Phone), ct);
|
|
||||||
return Results.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<Results<Ok<PersonResponse>, NotFound>> GetPerson(Guid id, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var resp = await mediator.Send(new GetPersonQuery(id), ct);
|
|
||||||
if (resp is null) return TypedResults.NotFound();
|
|
||||||
return TypedResults.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> UploadAttestForPerson(Guid id, PersonAttestUploadRequest req, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var createdId = await mediator.Send(new PersonUploadAttestCommand(id, req), ct);
|
|
||||||
if (createdId is null) return Results.NotFound();
|
|
||||||
return Results.Created($"/api/v1/persons/{id}/attests/{createdId}", new { attestId = createdId });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> ListAttestsForPerson(Guid id, int? take, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var items = await mediator.Send(new ListPersonAttestsQuery(id, take), ct);
|
|
||||||
return Results.Ok(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> DownloadAttestForPerson(Guid id, Guid attestId, bool? inline, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var content = await mediator.Send(new GetAttestContentQuery(attestId), ct);
|
|
||||||
if (content is null) return Results.NotFound();
|
|
||||||
var contentType = NormalizeContentType(content.Content, content.ContentType);
|
|
||||||
var fileName = NormalizeFileName(content.FileName, attestId, contentType);
|
|
||||||
if (inline == true)
|
|
||||||
{
|
|
||||||
// Inline view: omit download filename to avoid attachment disposition
|
|
||||||
return Results.File(content.Content, contentType);
|
|
||||||
}
|
|
||||||
return Results.File(content.Content, contentType, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeContentType(byte[] data, string? original)
|
|
||||||
{
|
|
||||||
var ct = string.IsNullOrWhiteSpace(original) || string.Equals(original, "application/octet-stream", StringComparison.OrdinalIgnoreCase)
|
|
||||||
? null
|
|
||||||
: original;
|
|
||||||
// Sniff PDF signature: %PDF-
|
|
||||||
if (ct is null)
|
|
||||||
{
|
|
||||||
if (data is { Length: >= 4 } && data[0] == 0x25 && data[1] == 0x50 && data[2] == 0x44 && data[3] == 0x46)
|
|
||||||
{
|
|
||||||
return "application/pdf";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ct ?? "application/pdf"; // prefer PDF when unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeFileName(string? name, Guid attestId, string contentType)
|
|
||||||
{
|
|
||||||
var n = string.IsNullOrWhiteSpace(name) ? $"attest-{attestId}" : name!;
|
|
||||||
if (string.Equals(contentType, "application/pdf", StringComparison.OrdinalIgnoreCase) && !n.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
n += ".pdf";
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using MinAttest.Application.Features.ShareLinks.Commands;
|
|
||||||
using MinAttest.Application.Features.ShareLinks.Queries;
|
|
||||||
using MinAttest.Contracts.ShareLinks;
|
|
||||||
|
|
||||||
namespace MinAttest.Api.Features.ShareLinks;
|
|
||||||
|
|
||||||
public static class ShareLinks
|
|
||||||
{
|
|
||||||
public static RouteGroupBuilder MapShareLinks(this RouteGroupBuilder group)
|
|
||||||
{
|
|
||||||
group.MapPost("/attests/{id:guid}/sharelinks", CreateShareLink)
|
|
||||||
.WithName("CreateShareLinks")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
group.MapGet("/attests/{id:guid}/sharelinks", ListShareLinks)
|
|
||||||
.WithName("ListShareLinks")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
group.MapDelete("/attests/{id:guid}/sharelinks/{shareLinkId:guid}", RevokeShareLink)
|
|
||||||
.WithName("RevokeShareLinks")
|
|
||||||
.WithOpenApi();
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> CreateShareLink(Guid id, CreateShareLinkRequest req, HttpContext http, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var baseUrl = $"{http.Request.Scheme}://{http.Request.Host}";
|
|
||||||
var resp = await mediator.Send(new CreateShareLinkCommand(id, req, baseUrl), ct);
|
|
||||||
if (resp is null) return Results.NotFound();
|
|
||||||
return Results.Ok(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> ListShareLinks(Guid id, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var items = await mediator.Send(new ListShareLinksQuery(id), ct);
|
|
||||||
return Results.Ok(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<IResult> RevokeShareLink(Guid id, Guid shareLinkId, IMediator mediator, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var ok = await mediator.Send(new RevokeShareLinkCommand(id, shareLinkId), ct);
|
|
||||||
if (!ok) return Results.NotFound();
|
|
||||||
return Results.NoContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<RootNamespace>MinAttest.Api</RootNamespace>
|
|
||||||
<AssemblyName>MinAttest.Api</AssemblyName>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.*" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.*" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.*">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.*" />
|
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.*" />
|
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="9.*" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="7.*" />
|
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.*" />
|
|
||||||
<PackageReference Include="MediatR" Version="12.*" />
|
|
||||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.*" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.*" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\MinAttest.ServiceDefaults\MinAttest.ServiceDefaults.csproj" />
|
|
||||||
<ProjectReference Include="..\MinAttest.Infrastructure\MinAttest.Infrastructure.csproj" />
|
|
||||||
<ProjectReference Include="..\MinAttest.Domain\MinAttest.Domain.csproj" />
|
|
||||||
<ProjectReference Include="..\MinAttest.Application\MinAttest.Application.csproj" />
|
|
||||||
<ProjectReference Include="..\MinAttest.Contracts\MinAttest.Contracts.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
@host = http://localhost:5072
|
|
||||||
|
|
||||||
### Health
|
|
||||||
GET {{host}}/api/v1/health
|
|
||||||
|
|
||||||
### Persons
|
|
||||||
POST {{host}}/api/v1/persons
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"nationalIdHash": "hash123",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"phone": "+4712345678"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Employers
|
|
||||||
POST {{host}}/api/v1/employers
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"orgNumber": "123456789",
|
|
||||||
"name": "Bedrift AS"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Attests - list
|
|
||||||
GET {{host}}/api/v1/persons/{{personId}}/attests
|
|
||||||
|
|
||||||
### Person - upload attest (inline content)
|
|
||||||
POST {{host}}/api/v1/persons/{{personId}}/attests
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "Utvikler",
|
|
||||||
"from": "2021-01-01",
|
|
||||||
"to": "2023-12-31",
|
|
||||||
"summary": "Arbeidet med .NET",
|
|
||||||
"blobPath": "",
|
|
||||||
"blobHash": null,
|
|
||||||
"contentBase64": "<base64>",
|
|
||||||
"contentType": "application/pdf"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Person - download attest
|
|
||||||
GET {{host}}/api/v1/persons/{{personId}}/attests/{{attestId}}/download
|
|
||||||
|
|
||||||
### Employer - upsert employer
|
|
||||||
POST {{host}}/api/v1/employers
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"orgNumber": "123456789",
|
|
||||||
"name": "Bedrift AS"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Employer - issue attest for person
|
|
||||||
POST {{host}}/api/v1/employers/{{employerId}}/attests
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"personId": "{{personId}}",
|
|
||||||
"title": "Konsulent",
|
|
||||||
"from": "2022-01-01",
|
|
||||||
"to": "2024-01-01",
|
|
||||||
"summary": "Prosjektarbeid",
|
|
||||||
"blobPath": "",
|
|
||||||
"blobHash": null,
|
|
||||||
"contentBase64": "<base64>",
|
|
||||||
"contentType": "application/pdf"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Employer - list attests
|
|
||||||
GET {{host}}/api/v1/employers/{{employerId}}/attests
|
|
||||||
|
|
||||||
### Employer - download attest
|
|
||||||
GET {{host}}/api/v1/employers/{{employerId}}/attests/{{attestId}}/download
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Serilog;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using MinAttest.Api.Features.Employer;
|
|
||||||
using MinAttest.Api.Features.Health;
|
|
||||||
using MinAttest.Api.Features.ShareLinks;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
using MinAttest.Api.Features.Persons;
|
|
||||||
using MediatR;
|
|
||||||
using MinAttest.Application;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using FluentValidation;
|
|
||||||
using MinAttest.Application.Common.Behaviors;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
builder.AddServiceDefaults();
|
|
||||||
|
|
||||||
Directory.CreateDirectory("logs");
|
|
||||||
|
|
||||||
builder.Host.UseSerilog((ctx, services, cfg) =>
|
|
||||||
{
|
|
||||||
cfg.ReadFrom.Configuration(ctx.Configuration)
|
|
||||||
.Enrich.FromLogContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddOpenApi();
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen(options =>
|
|
||||||
{
|
|
||||||
options.SwaggerDoc("v1", new OpenApiInfo
|
|
||||||
{
|
|
||||||
Title = "MinAttest API",
|
|
||||||
Version = "v1"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ApplicationAssembly).Assembly));
|
|
||||||
builder.Services.AddScoped<IAppDbContext, MinAttest.Infrastructure.Data.AppDbContext>();
|
|
||||||
builder.Services.AddValidatorsFromAssemblyContaining<ApplicationAssembly>();
|
|
||||||
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
|
|
||||||
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
|
|
||||||
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
||||||
{
|
|
||||||
var cs = builder.Configuration.GetConnectionString("Default")
|
|
||||||
?? builder.Configuration.GetConnectionString("MinAttest")
|
|
||||||
?? builder.Configuration.GetConnectionString("minattest")
|
|
||||||
?? throw new InvalidOperationException("Missing connection string. Provide ConnectionStrings:Default or :MinAttest.");
|
|
||||||
options.UseSqlServer(cs, sql => sql.EnableRetryOnFailure());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add DB health check to readiness probe
|
|
||||||
builder.Services.AddHealthChecks()
|
|
||||||
.AddDbContextCheck<AppDbContext>(name: "database");
|
|
||||||
|
|
||||||
builder.Services.AddCors(options =>
|
|
||||||
{
|
|
||||||
options.AddPolicy("Frontend", policy =>
|
|
||||||
{
|
|
||||||
var origins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? Array.Empty<string>();
|
|
||||||
if (origins.Length > 0)
|
|
||||||
{
|
|
||||||
policy.WithOrigins(origins)
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowAnyMethod();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.MapOpenApi();
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI(c =>
|
|
||||||
{
|
|
||||||
// Use relative path so it works with PathBase/proxies
|
|
||||||
c.SwaggerEndpoint("../openapi/v1.json", "MinAttest API v1");
|
|
||||||
c.RoutePrefix = "swagger";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
|
||||||
app.UseCors("Frontend");
|
|
||||||
|
|
||||||
app.UseExceptionHandler(errApp =>
|
|
||||||
{
|
|
||||||
errApp.Run(async context =>
|
|
||||||
{
|
|
||||||
var feature = context.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature>();
|
|
||||||
var ex = feature?.Error;
|
|
||||||
var status = ex is FluentValidation.ValidationException ? StatusCodes.Status400BadRequest : StatusCodes.Status500InternalServerError;
|
|
||||||
|
|
||||||
var problem = new
|
|
||||||
{
|
|
||||||
type = status == 400 ? "https://datatracker.ietf.org/doc/html/rfc7807#section-3.1" : "about:blank",
|
|
||||||
title = status == 400 ? "One or more validation errors occurred." : "An unexpected error occurred.",
|
|
||||||
status,
|
|
||||||
detail = app.Environment.IsDevelopment() ? ex?.Message : null,
|
|
||||||
traceId = context.TraceIdentifier,
|
|
||||||
errors = ex is FluentValidation.ValidationException vex
|
|
||||||
? vex.Errors.GroupBy(e => e.PropertyName).ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray())
|
|
||||||
: null
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Response.StatusCode = status;
|
|
||||||
context.Response.ContentType = "application/problem+json";
|
|
||||||
await context.Response.WriteAsJsonAsync(problem);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var apiV1 = app.MapGroup("/api/v1");
|
|
||||||
|
|
||||||
apiV1.MapHealth();
|
|
||||||
apiV1.MapEmployer();
|
|
||||||
apiV1.MapPersons();
|
|
||||||
apiV1.MapShareLinks();
|
|
||||||
|
|
||||||
app.MapDefaultEndpoints();
|
|
||||||
|
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
|
||||||
{
|
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
|
|
||||||
var c = db.Database.GetDbConnection();
|
|
||||||
db.Database.Migrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Run();
|
|
||||||
|
|
||||||
public partial class Program { }
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
|
||||||
"profiles": {
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": false,
|
|
||||||
"applicationUrl": "http://localhost:5072",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": false,
|
|
||||||
"applicationUrl": "https://localhost:7172;http://localhost:5072",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ConnectionStrings": {
|
|
||||||
"Default": "Server=127.0.0.1,14335;Database=minattest-dev-sql;User Id=sa;Password=Your_password123;TrustServerCertificate=True;"
|
|
||||||
},
|
|
||||||
"Cors": {
|
|
||||||
"AllowedOrigins": [
|
|
||||||
"http://localhost:5173",
|
|
||||||
"http://localhost:3000"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Serilog": {
|
|
||||||
"Using": [ "Serilog.Sinks.File" ],
|
|
||||||
"MinimumLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Override": {
|
|
||||||
"Microsoft": "Warning",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"WriteTo": [
|
|
||||||
{
|
|
||||||
"Name": "File",
|
|
||||||
"Args": {
|
|
||||||
"path": "logs/api-.log",
|
|
||||||
"rollingInterval": "Day",
|
|
||||||
"retainedFileCountLimit": 7,
|
|
||||||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Enrich": [ "FromLogContext" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
public interface IAppDbContext
|
|
||||||
{
|
|
||||||
DbSet<Person> Persons { get; }
|
|
||||||
DbSet<Employer> Employers { get; }
|
|
||||||
DbSet<Attest> Attests { get; }
|
|
||||||
DbSet<ShareLink> ShareLinks { get; }
|
|
||||||
DbSet<AuditLog> AuditLogs { get; }
|
|
||||||
DbSet<EmployerUser> EmployerUsers { get; }
|
|
||||||
|
|
||||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace MinAttest.Application;
|
|
||||||
|
|
||||||
// Marker type for scanning application assembly (MediatR, validators)
|
|
||||||
public sealed class ApplicationAssembly { }
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Common.Behaviors;
|
|
||||||
|
|
||||||
public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
|
|
||||||
: IPipelineBehavior<TRequest, TResponse>
|
|
||||||
where TRequest : notnull
|
|
||||||
{
|
|
||||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var requestName = typeof(TRequest).Name;
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.LogInformation("Handling {RequestName}", requestName);
|
|
||||||
var response = await next();
|
|
||||||
sw.Stop();
|
|
||||||
logger.LogInformation("Handled {RequestName} in {ElapsedMs} ms", requestName, sw.ElapsedMilliseconds);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
sw.Stop();
|
|
||||||
logger.LogError(ex, "Error handling {RequestName} after {ElapsedMs} ms", requestName, sw.ElapsedMilliseconds);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
using MediatR;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Common.Behaviors;
|
|
||||||
|
|
||||||
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
|
|
||||||
: IPipelineBehavior<TRequest, TResponse>
|
|
||||||
where TRequest : notnull
|
|
||||||
{
|
|
||||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (!validators.Any())
|
|
||||||
return await next();
|
|
||||||
|
|
||||||
var context = new ValidationContext<TRequest>(request);
|
|
||||||
var failures = (await Task.WhenAll(validators.Select(v => v.ValidateAsync(context, cancellationToken))))
|
|
||||||
.SelectMany(r => r.Errors)
|
|
||||||
.Where(f => f is not null)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (failures.Count != 0)
|
|
||||||
{
|
|
||||||
throw new ValidationException(failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Commands;
|
|
||||||
|
|
||||||
public record CompleteUploadCommand(Guid UploadId, CompleteUploadRequest Request) : IRequest<Guid?>;
|
|
||||||
|
|
||||||
public class CompleteUploadCommandHandler(IAppDbContext db) : IRequestHandler<CompleteUploadCommand, Guid?>
|
|
||||||
{
|
|
||||||
public async Task<Guid?> Handle(CompleteUploadCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var r = request.Request;
|
|
||||||
var personExists = await db.Persons.AnyAsync(p => p.Id == r.PersonId, cancellationToken);
|
|
||||||
if (!personExists) return null;
|
|
||||||
|
|
||||||
if (r.EmployerId is Guid eid)
|
|
||||||
{
|
|
||||||
var exists = await db.Employers.AnyAsync(e => e.Id == eid, cancellationToken);
|
|
||||||
if (!exists) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[]? content = null;
|
|
||||||
long? length = null;
|
|
||||||
if (!string.IsNullOrWhiteSpace(r.ContentBase64))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
content = Convert.FromBase64String(r.ContentBase64);
|
|
||||||
length = content.LongLength;
|
|
||||||
}
|
|
||||||
catch (FormatException)
|
|
||||||
{
|
|
||||||
// invalid base64 -> treat as bad request
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var a = new Attest
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
PersonId = r.PersonId,
|
|
||||||
EmployerId = r.EmployerId,
|
|
||||||
Title = r.Title,
|
|
||||||
From = r.From,
|
|
||||||
To = r.To,
|
|
||||||
Summary = r.Summary,
|
|
||||||
BlobPath = r.BlobPath,
|
|
||||||
BlobHash = r.BlobHash,
|
|
||||||
Content = content,
|
|
||||||
ContentType = r.ContentType,
|
|
||||||
ContentLength = length,
|
|
||||||
IssuedAt = DateTimeOffset.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
db.Attests.Add(a);
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return a.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Commands;
|
|
||||||
|
|
||||||
public class CompleteUploadCommandValidator : AbstractValidator<CompleteUploadCommand>
|
|
||||||
{
|
|
||||||
public CompleteUploadCommandValidator()
|
|
||||||
{
|
|
||||||
RuleFor(x => x.Request.PersonId).NotEqual(Guid.Empty);
|
|
||||||
RuleFor(x => x.Request.Title).NotEmpty().MaximumLength(200);
|
|
||||||
RuleFor(x => x.Request.BlobPath).NotEmpty().MaximumLength(500);
|
|
||||||
// Allow either inline content or external reference
|
|
||||||
RuleFor(x => x.Request)
|
|
||||||
.Must(r => !string.IsNullOrWhiteSpace(r.ContentBase64) || !string.IsNullOrWhiteSpace(r.BlobPath))
|
|
||||||
.WithMessage("Either ContentBase64 or BlobPath must be provided");
|
|
||||||
When(x => !string.IsNullOrWhiteSpace(x.Request.ContentBase64), () =>
|
|
||||||
{
|
|
||||||
RuleFor(x => x.Request.ContentType).NotEmpty().MaximumLength(255);
|
|
||||||
});
|
|
||||||
RuleFor(x => x.Request.From).LessThanOrEqualTo(x => x.Request.To);
|
|
||||||
RuleFor(x => x.Request.Summary).MaximumLength(2000).When(x => !string.IsNullOrWhiteSpace(x.Request.Summary));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Commands;
|
|
||||||
|
|
||||||
public record EmployerIssueAttestCommand(Guid EmployerId, EmployerAttestUploadRequest Request) : IRequest<Guid?>;
|
|
||||||
|
|
||||||
public class EmployerIssueAttestCommandHandler(IAppDbContext db) : IRequestHandler<EmployerIssueAttestCommand, Guid?>
|
|
||||||
{
|
|
||||||
public async Task<Guid?> Handle(EmployerIssueAttestCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var r = request.Request;
|
|
||||||
var employerExists = await db.Employers.AnyAsync(e => e.Id == request.EmployerId, cancellationToken);
|
|
||||||
if (!employerExists) return null;
|
|
||||||
|
|
||||||
var personExists = await db.Persons.AnyAsync(p => p.Id == r.PersonId, cancellationToken);
|
|
||||||
if (!personExists) return null;
|
|
||||||
|
|
||||||
byte[]? content = null;
|
|
||||||
long? length = null;
|
|
||||||
if (!string.IsNullOrWhiteSpace(r.ContentBase64))
|
|
||||||
{
|
|
||||||
try { content = Convert.FromBase64String(r.ContentBase64); length = content.LongLength; }
|
|
||||||
catch (FormatException) { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
var a = new Attest
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
PersonId = r.PersonId,
|
|
||||||
EmployerId = request.EmployerId,
|
|
||||||
Title = r.Title,
|
|
||||||
From = r.From,
|
|
||||||
To = r.To,
|
|
||||||
Summary = r.Summary,
|
|
||||||
BlobPath = r.BlobPath,
|
|
||||||
BlobHash = r.BlobHash,
|
|
||||||
Content = content,
|
|
||||||
ContentType = r.ContentType,
|
|
||||||
ContentLength = length,
|
|
||||||
IssuedAt = DateTimeOffset.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
db.Attests.Add(a);
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return a.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Commands;
|
|
||||||
|
|
||||||
public class EmployerIssueAttestCommandValidator : AbstractValidator<EmployerIssueAttestCommand>
|
|
||||||
{
|
|
||||||
public EmployerIssueAttestCommandValidator()
|
|
||||||
{
|
|
||||||
RuleFor(x => x.EmployerId).NotEqual(Guid.Empty);
|
|
||||||
RuleFor(x => x.Request.PersonId).NotEqual(Guid.Empty);
|
|
||||||
RuleFor(x => x.Request.Title).NotEmpty().MaximumLength(200);
|
|
||||||
RuleFor(x => x.Request.BlobPath).MaximumLength(500);
|
|
||||||
RuleFor(x => x.Request.From).LessThanOrEqualTo(x => x.Request.To);
|
|
||||||
RuleFor(x => x.Request)
|
|
||||||
.Must(r => !string.IsNullOrWhiteSpace(r.BlobPath) || !string.IsNullOrWhiteSpace(r.ContentBase64))
|
|
||||||
.WithMessage("Either BlobPath or ContentBase64 must be provided");
|
|
||||||
When(x => !string.IsNullOrWhiteSpace(x.Request.ContentBase64), () =>
|
|
||||||
{
|
|
||||||
RuleFor(x => x.Request.ContentType).NotEmpty().MaximumLength(255);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Commands;
|
|
||||||
|
|
||||||
public record PersonUploadAttestCommand(Guid PersonId, PersonAttestUploadRequest Request) : IRequest<Guid?>;
|
|
||||||
|
|
||||||
public class PersonUploadAttestCommandHandler(IAppDbContext db) : IRequestHandler<PersonUploadAttestCommand, Guid?>
|
|
||||||
{
|
|
||||||
public async Task<Guid?> Handle(PersonUploadAttestCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var r = request.Request;
|
|
||||||
|
|
||||||
var person = await db.Persons.FindAsync([request.PersonId], cancellationToken);
|
|
||||||
if (person is null) return null;
|
|
||||||
|
|
||||||
byte[]? content = null;
|
|
||||||
long? length = null;
|
|
||||||
if (!string.IsNullOrWhiteSpace(r.ContentBase64))
|
|
||||||
{
|
|
||||||
try { content = Convert.FromBase64String(r.ContentBase64); length = content.LongLength; }
|
|
||||||
catch (FormatException) { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
var a = new Attest
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
PersonId = request.PersonId,
|
|
||||||
EmployerId = null,
|
|
||||||
Title = r.Title,
|
|
||||||
From = r.From,
|
|
||||||
To = r.To,
|
|
||||||
Summary = r.Summary,
|
|
||||||
BlobPath = r.BlobPath,
|
|
||||||
BlobHash = r.BlobHash,
|
|
||||||
Content = content,
|
|
||||||
ContentType = r.ContentType,
|
|
||||||
ContentLength = length,
|
|
||||||
IssuedAt = DateTimeOffset.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
db.Attests.Add(a);
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return a.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Commands;
|
|
||||||
|
|
||||||
public class PersonUploadAttestCommandValidator : AbstractValidator<PersonUploadAttestCommand>
|
|
||||||
{
|
|
||||||
public PersonUploadAttestCommandValidator()
|
|
||||||
{
|
|
||||||
RuleFor(x => x.PersonId).NotEqual(Guid.Empty);
|
|
||||||
RuleFor(x => x.Request.Title).NotEmpty().MaximumLength(200);
|
|
||||||
RuleFor(x => x.Request.BlobPath).MaximumLength(500);
|
|
||||||
RuleFor(x => x.Request.From).LessThanOrEqualTo(x => x.Request.To);
|
|
||||||
RuleFor(x => x.Request)
|
|
||||||
.Must(r => !string.IsNullOrWhiteSpace(r.BlobPath) || !string.IsNullOrWhiteSpace(r.ContentBase64))
|
|
||||||
.WithMessage("Either BlobPath or ContentBase64 must be provided");
|
|
||||||
When(x => !string.IsNullOrWhiteSpace(x.Request.ContentBase64), () =>
|
|
||||||
{
|
|
||||||
RuleFor(x => x.Request.ContentType).NotEmpty().MaximumLength(255);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
public record GetAttestByIdQuery(Guid Id) : IRequest<AttestDetails?>;
|
|
||||||
|
|
||||||
public class GetAttestByIdQueryHandler(IAppDbContext db) : IRequestHandler<GetAttestByIdQuery, AttestDetails?>
|
|
||||||
{
|
|
||||||
public async Task<AttestDetails?> Handle(GetAttestByIdQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var a = await db.Attests.AsNoTracking().Include(x => x.Employer)
|
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
|
|
||||||
if (a is null) return null;
|
|
||||||
|
|
||||||
return new AttestDetails(
|
|
||||||
a.Id,
|
|
||||||
a.Employer?.Name ?? string.Empty,
|
|
||||||
a.Title,
|
|
||||||
a.From,
|
|
||||||
a.To,
|
|
||||||
a.Summary ?? string.Empty,
|
|
||||||
a.Status == AttestStatus.Issued
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
public record GetAttestContentQuery(Guid AttestId) : IRequest<AttestContentResult?>;
|
|
||||||
|
|
||||||
public record AttestContentResult(byte[] Content, string? ContentType, string FileName);
|
|
||||||
|
|
||||||
public class GetAttestContentQueryHandler(IAppDbContext db) : IRequestHandler<GetAttestContentQuery, AttestContentResult?>
|
|
||||||
{
|
|
||||||
public async Task<AttestContentResult?> Handle(GetAttestContentQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var result = await db.Attests.AsNoTracking()
|
|
||||||
.Where(a => a.Id == request.AttestId)
|
|
||||||
.Select(a => new { a.Content, a.ContentType, a.Title })
|
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (result is null || result.Content is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var name = string.IsNullOrWhiteSpace(result.Title) ? "attest" : result.Title;
|
|
||||||
// crude filename, no extension without content type mapping
|
|
||||||
return new AttestContentResult(result.Content, result.ContentType, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
public record GetAttestsQuery(Guid? PersonId, Guid? EmployerId, int? Take) : IRequest<IReadOnlyList<AttestSummary>>;
|
|
||||||
|
|
||||||
public class GetAttestsQueryHandler(IAppDbContext db) : IRequestHandler<GetAttestsQuery, IReadOnlyList<AttestSummary>>
|
|
||||||
{
|
|
||||||
public async Task<IReadOnlyList<AttestSummary>> Handle(GetAttestsQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var query = db.Attests.AsNoTracking().Include(a => a.Employer).AsQueryable();
|
|
||||||
|
|
||||||
if (request.PersonId is Guid pid && pid != Guid.Empty)
|
|
||||||
query = query.Where(a => a.PersonId == pid);
|
|
||||||
if (request.EmployerId is Guid eid && eid != Guid.Empty)
|
|
||||||
query = query.Where(a => a.EmployerId == eid);
|
|
||||||
|
|
||||||
var items = await query
|
|
||||||
.OrderByDescending(a => a.IssuedAt)
|
|
||||||
.Take(Math.Clamp(request.Take ?? 50, 1, 200))
|
|
||||||
.Select(a => new AttestSummary(
|
|
||||||
a.Id,
|
|
||||||
a.Employer != null ? a.Employer.Name : string.Empty,
|
|
||||||
a.Title,
|
|
||||||
a.From,
|
|
||||||
a.To,
|
|
||||||
a.Status == AttestStatus.Issued
|
|
||||||
))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
public record ListEmployerAttestsQuery(Guid EmployerId, int? Take) : IRequest<IReadOnlyList<AttestSummary>>;
|
|
||||||
|
|
||||||
public class ListEmployerAttestsQueryHandler(IAppDbContext db) : IRequestHandler<ListEmployerAttestsQuery, IReadOnlyList<AttestSummary>>
|
|
||||||
{
|
|
||||||
public async Task<IReadOnlyList<AttestSummary>> Handle(ListEmployerAttestsQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await db.Attests.AsNoTracking().Include(a => a.Employer)
|
|
||||||
.Where(a => a.EmployerId == request.EmployerId)
|
|
||||||
.OrderByDescending(a => a.IssuedAt)
|
|
||||||
.Take(Math.Clamp(request.Take ?? 50, 1, 200))
|
|
||||||
.Select(a => new AttestSummary(a.Id, (a.Employer != null ? a.Employer.Name : string.Empty), a.Title, a.From, a.To, a.Status == AttestStatus.Issued))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Attests.Queries;
|
|
||||||
|
|
||||||
public record ListPersonAttestsQuery(Guid PersonId, int? Take) : IRequest<IReadOnlyList<AttestSummary>>;
|
|
||||||
|
|
||||||
public class ListPersonAttestsQueryHandler(IAppDbContext db) : IRequestHandler<ListPersonAttestsQuery, IReadOnlyList<AttestSummary>>
|
|
||||||
{
|
|
||||||
public async Task<IReadOnlyList<AttestSummary>> Handle(ListPersonAttestsQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await db.Attests.AsNoTracking().Include(a => a.Employer)
|
|
||||||
.Where(a => a.PersonId == request.PersonId)
|
|
||||||
.OrderByDescending(a => a.IssuedAt)
|
|
||||||
.Take(Math.Clamp(request.Take ?? 50, 1, 200))
|
|
||||||
.Select(a => new AttestSummary(a.Id, (a.Employer != null ? a.Employer.Name : string.Empty), a.Title, a.From, a.To, a.Status == AttestStatus.Issued))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Commands;
|
|
||||||
|
|
||||||
public record DeleteEmployerUserCommand(Guid EmployerId, Guid UserId) : IRequest<bool>;
|
|
||||||
|
|
||||||
public class DeleteEmployerUserCommandHandler(IAppDbContext db) : IRequestHandler<DeleteEmployerUserCommand, bool>
|
|
||||||
{
|
|
||||||
public async Task<bool> Handle(DeleteEmployerUserCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var user = await db.EmployerUsers.FirstOrDefaultAsync(u => u.Id == request.UserId && u.EmployerId == request.EmployerId, cancellationToken);
|
|
||||||
if (user is null) return false;
|
|
||||||
db.EmployerUsers.Remove(user);
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Commands;
|
|
||||||
|
|
||||||
public record UpsertEmployerCommand(string OrgNumber, string Name) : IRequest<EmployerResponse>;
|
|
||||||
|
|
||||||
public class UpsertEmployerCommandHandler(IAppDbContext db) : IRequestHandler<UpsertEmployerCommand, EmployerResponse>
|
|
||||||
{
|
|
||||||
public async Task<EmployerResponse> Handle(UpsertEmployerCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var existing = await db.Employers.FirstOrDefaultAsync(e => e.OrgNumber == request.OrgNumber, cancellationToken);
|
|
||||||
if (existing is null)
|
|
||||||
{
|
|
||||||
existing = new MinAttest.Domain.Entities.Employer { Id = Guid.NewGuid(), OrgNumber = request.OrgNumber, Name = request.Name };
|
|
||||||
db.Employers.Add(existing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
existing.Name = request.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return new EmployerResponse(existing.Id, existing.OrgNumber, existing.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Commands;
|
|
||||||
|
|
||||||
public class UpsertEmployerCommandValidator : AbstractValidator<UpsertEmployerCommand>
|
|
||||||
{
|
|
||||||
public UpsertEmployerCommandValidator()
|
|
||||||
{
|
|
||||||
RuleFor(x => x.OrgNumber).NotEmpty().MaximumLength(32);
|
|
||||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Commands;
|
|
||||||
|
|
||||||
public record UpsertEmployerUserCommand(
|
|
||||||
Guid EmployerId,
|
|
||||||
string ExternalObjectId,
|
|
||||||
string Email,
|
|
||||||
string? Name,
|
|
||||||
string Role
|
|
||||||
) : IRequest<EmployerUserResponse?>;
|
|
||||||
|
|
||||||
public class UpsertEmployerUserCommandHandler(IAppDbContext db)
|
|
||||||
: IRequestHandler<UpsertEmployerUserCommand, EmployerUserResponse?>
|
|
||||||
{
|
|
||||||
public async Task<EmployerUserResponse?> Handle(UpsertEmployerUserCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var employer = await db.Employers.FirstOrDefaultAsync(e => e.Id == request.EmployerId, cancellationToken);
|
|
||||||
if (employer is null) return null;
|
|
||||||
|
|
||||||
var roleParsed = Enum.TryParse<EmployerUserRole>(request.Role, ignoreCase: true, out var role)
|
|
||||||
? role : EmployerUserRole.Issuer;
|
|
||||||
|
|
||||||
var user = await db.EmployerUsers.FirstOrDefaultAsync(u => u.ExternalObjectId == request.ExternalObjectId, cancellationToken);
|
|
||||||
if (user is null)
|
|
||||||
{
|
|
||||||
user = new EmployerUser
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
EmployerId = employer.Id,
|
|
||||||
ExternalObjectId = request.ExternalObjectId,
|
|
||||||
Email = request.Email,
|
|
||||||
Name = request.Name,
|
|
||||||
Role = roleParsed
|
|
||||||
};
|
|
||||||
db.EmployerUsers.Add(user);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user.EmployerId = employer.Id;
|
|
||||||
user.Email = request.Email;
|
|
||||||
user.Name = request.Name;
|
|
||||||
user.Role = roleParsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
return new EmployerUserResponse(user.Id, user.EmployerId, user.ExternalObjectId, user.Email, user.Name, user.Role.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Commands;
|
|
||||||
|
|
||||||
public class UpsertEmployerUserCommandValidator : AbstractValidator<UpsertEmployerUserCommand>
|
|
||||||
{
|
|
||||||
public UpsertEmployerUserCommandValidator()
|
|
||||||
{
|
|
||||||
RuleFor(x => x.EmployerId).NotEqual(Guid.Empty);
|
|
||||||
RuleFor(x => x.ExternalObjectId).NotEmpty().MaximumLength(128);
|
|
||||||
RuleFor(x => x.Email).NotEmpty().MaximumLength(256).EmailAddress();
|
|
||||||
RuleFor(x => x.Name).MaximumLength(200).When(x => !string.IsNullOrWhiteSpace(x.Name));
|
|
||||||
RuleFor(x => x.Role).NotEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Queries;
|
|
||||||
|
|
||||||
public record GetEmployerQuery(Guid Id) : IRequest<EmployerResponse?>;
|
|
||||||
|
|
||||||
public class GetEmployerQueryHandler(IAppDbContext db) : IRequestHandler<GetEmployerQuery, EmployerResponse?>
|
|
||||||
{
|
|
||||||
public async Task<EmployerResponse?> Handle(GetEmployerQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var e = await db.Employers.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
|
|
||||||
if (e is null) return null;
|
|
||||||
return new EmployerResponse(e.Id, e.OrgNumber, e.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Queries;
|
|
||||||
|
|
||||||
public record GetEmployerUserQuery(Guid EmployerId, Guid UserId) : IRequest<EmployerUserResponse?>;
|
|
||||||
|
|
||||||
public class GetEmployerUserQueryHandler(IAppDbContext db) : IRequestHandler<GetEmployerUserQuery, EmployerUserResponse?>
|
|
||||||
{
|
|
||||||
public async Task<EmployerUserResponse?> Handle(GetEmployerUserQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var u = await db.EmployerUsers.FirstOrDefaultAsync(x => x.Id == request.UserId && x.EmployerId == request.EmployerId, cancellationToken);
|
|
||||||
if (u is null) return null;
|
|
||||||
return new EmployerUserResponse(u.Id, u.EmployerId, u.ExternalObjectId, u.Email, u.Name, u.Role.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Queries;
|
|
||||||
|
|
||||||
public record ListEmployerUsersQuery(Guid EmployerId) : IRequest<IReadOnlyList<EmployerUserResponse>>;
|
|
||||||
|
|
||||||
public class ListEmployerUsersQueryHandler(IAppDbContext db) : IRequestHandler<ListEmployerUsersQuery, IReadOnlyList<EmployerUserResponse>>
|
|
||||||
{
|
|
||||||
public async Task<IReadOnlyList<EmployerUserResponse>> Handle(ListEmployerUsersQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await db.EmployerUsers
|
|
||||||
.Where(u => u.EmployerId == request.EmployerId)
|
|
||||||
.OrderBy(u => u.Email)
|
|
||||||
.Select(u => new EmployerUserResponse(u.Id, u.EmployerId, u.ExternalObjectId, u.Email, u.Name, u.Role.ToString()))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Employers.Queries;
|
|
||||||
|
|
||||||
public record ListEmployersQuery() : IRequest<IReadOnlyList<EmployerResponse>>;
|
|
||||||
|
|
||||||
public class ListEmployersQueryHandler(IAppDbContext db) : IRequestHandler<ListEmployersQuery, IReadOnlyList<EmployerResponse>>
|
|
||||||
{
|
|
||||||
public async Task<IReadOnlyList<EmployerResponse>> Handle(ListEmployersQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await db.Employers
|
|
||||||
.OrderBy(e => e.Name)
|
|
||||||
.Select(e => new EmployerResponse(e.Id, e.OrgNumber, e.Name))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Persons;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Persons.Commands;
|
|
||||||
|
|
||||||
public record UpsertPersonCommand(string NationalIdHash, string? Email, string? Phone) : IRequest<PersonResponse>;
|
|
||||||
|
|
||||||
public class UpsertPersonCommandHandler(IAppDbContext db) : IRequestHandler<UpsertPersonCommand, PersonResponse>
|
|
||||||
{
|
|
||||||
public async Task<PersonResponse> Handle(UpsertPersonCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var p = await db.Persons.FirstOrDefaultAsync(x => x.NationalIdHash == request.NationalIdHash, cancellationToken);
|
|
||||||
if (p is null)
|
|
||||||
{
|
|
||||||
p = new Person
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
NationalIdHash = request.NationalIdHash,
|
|
||||||
Email = request.Email,
|
|
||||||
Phone = request.Phone
|
|
||||||
};
|
|
||||||
db.Persons.Add(p);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
p.Email = request.Email;
|
|
||||||
p.Phone = request.Phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return new PersonResponse(p.Id, p.NationalIdHash, p.Email, p.Phone, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Persons.Commands;
|
|
||||||
|
|
||||||
public class UpsertPersonCommandValidator : AbstractValidator<UpsertPersonCommand>
|
|
||||||
{
|
|
||||||
public UpsertPersonCommandValidator()
|
|
||||||
{
|
|
||||||
RuleFor(x => x.NationalIdHash).NotEmpty().MaximumLength(200);
|
|
||||||
RuleFor(x => x.Email).MaximumLength(256).When(x => !string.IsNullOrWhiteSpace(x.Email));
|
|
||||||
RuleFor(x => x.Phone).MaximumLength(50).When(x => !string.IsNullOrWhiteSpace(x.Phone));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Contracts.Persons;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.Persons.Queries;
|
|
||||||
|
|
||||||
public record GetPersonQuery(Guid Id) : IRequest<PersonResponse?>;
|
|
||||||
|
|
||||||
public class GetPersonQueryHandler(IAppDbContext db) : IRequestHandler<GetPersonQuery, PersonResponse?>
|
|
||||||
{
|
|
||||||
public async Task<PersonResponse?> Handle(GetPersonQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var person = await db.Persons
|
|
||||||
.Where(p => p.Id == request.Id)
|
|
||||||
.Select(p => new PersonResponse(
|
|
||||||
p.Id,
|
|
||||||
p.NationalIdHash,
|
|
||||||
p.Email,
|
|
||||||
p.Phone,
|
|
||||||
p.Attests
|
|
||||||
.Select(a => new AttestSummary(
|
|
||||||
a.Id,
|
|
||||||
a.Employer != null ? a.Employer.Name : string.Empty,
|
|
||||||
a.Title,
|
|
||||||
a.From,
|
|
||||||
a.To,
|
|
||||||
a.Status == Domain.Entities.AttestStatus.Issued
|
|
||||||
))
|
|
||||||
.ToList()
|
|
||||||
))
|
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
|
||||||
|
|
||||||
return person;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.ShareLinks;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.ShareLinks.Commands;
|
|
||||||
|
|
||||||
public record CreateShareLinkCommand(Guid AttestId, CreateShareLinkRequest Request, string BaseUrl) : IRequest<ShareLinkResponse?>;
|
|
||||||
|
|
||||||
public class CreateShareLinkCommandHandler(IAppDbContext db) : IRequestHandler<CreateShareLinkCommand, ShareLinkResponse?>
|
|
||||||
{
|
|
||||||
public async Task<ShareLinkResponse?> Handle(CreateShareLinkCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var attestExists = await db.Attests.AnyAsync(a => a.Id == request.AttestId, cancellationToken);
|
|
||||||
if (!attestExists) return null;
|
|
||||||
|
|
||||||
var code = GenerateCode(16);
|
|
||||||
var expiresAt = DateTimeOffset.UtcNow.Add(request.Request.ExpiresIn ?? TimeSpan.FromDays(7));
|
|
||||||
var oneTime = request.Request.OneTime ?? false;
|
|
||||||
|
|
||||||
var entity = new MinAttest.Domain.Entities.ShareLink
|
|
||||||
{
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
AttestId = request.AttestId,
|
|
||||||
Code = code,
|
|
||||||
ExpiresAt = expiresAt,
|
|
||||||
OneTime = oneTime,
|
|
||||||
RevokedAt = null
|
|
||||||
};
|
|
||||||
|
|
||||||
db.ShareLinks.Add(entity);
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
var url = new Uri($"{request.BaseUrl}/api/v1/verify/{code}");
|
|
||||||
return new ShareLinkResponse(entity.Id, code, url, expiresAt, oneTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GenerateCode(int bytes)
|
|
||||||
{
|
|
||||||
Span<byte> buffer = stackalloc byte[bytes];
|
|
||||||
RandomNumberGenerator.Fill(buffer);
|
|
||||||
var b64 = Convert.ToBase64String(buffer);
|
|
||||||
return b64.TrimEnd('=')
|
|
||||||
.Replace('+', '-')
|
|
||||||
.Replace('/', '_');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.ShareLinks.Commands;
|
|
||||||
|
|
||||||
public record RevokeShareLinkCommand(Guid AttestId, Guid ShareLinkId) : IRequest<bool>;
|
|
||||||
|
|
||||||
public class RevokeShareLinkCommandHandler(IAppDbContext db) : IRequestHandler<RevokeShareLinkCommand, bool>
|
|
||||||
{
|
|
||||||
public async Task<bool> Handle(RevokeShareLinkCommand request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var link = await db.ShareLinks.FirstOrDefaultAsync(l => l.Id == request.ShareLinkId && l.AttestId == request.AttestId, cancellationToken);
|
|
||||||
if (link is null) return false;
|
|
||||||
if (link.RevokedAt is not null) return true; // already revoked
|
|
||||||
link.RevokedAt = DateTimeOffset.UtcNow;
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Contracts.ShareLinks;
|
|
||||||
|
|
||||||
namespace MinAttest.Application.Features.ShareLinks.Queries;
|
|
||||||
|
|
||||||
public record ListShareLinksQuery(Guid AttestId) : IRequest<IReadOnlyList<ShareLinkResponse>>;
|
|
||||||
|
|
||||||
public class ListShareLinksQueryHandler(IAppDbContext db) : IRequestHandler<ListShareLinksQuery, IReadOnlyList<ShareLinkResponse>>
|
|
||||||
{
|
|
||||||
public async Task<IReadOnlyList<ShareLinkResponse>> Handle(ListShareLinksQuery request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await db.ShareLinks.AsNoTracking()
|
|
||||||
.Where(l => l.AttestId == request.AttestId && l.RevokedAt == null)
|
|
||||||
.OrderBy(l => l.ExpiresAt)
|
|
||||||
.Select(l => new ShareLinkResponse(l.Id, l.Code, new Uri($"https://app.example/verify/{l.Code}"), l.ExpiresAt, l.OneTime))
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="MediatR" Version="12.*" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.*" />
|
|
||||||
<PackageReference Include="FluentValidation" Version="12.*" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.11" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MinAttest.Domain\MinAttest.Domain.csproj" />
|
|
||||||
<ProjectReference Include="..\MinAttest.Contracts\MinAttest.Contracts.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.Attests;
|
|
||||||
|
|
||||||
public record AttestSummary(
|
|
||||||
Guid Id,
|
|
||||||
string Employer,
|
|
||||||
string Title,
|
|
||||||
DateOnly From,
|
|
||||||
DateOnly To,
|
|
||||||
bool Verified
|
|
||||||
);
|
|
||||||
|
|
||||||
public record AttestDetails(
|
|
||||||
Guid Id,
|
|
||||||
string Employer,
|
|
||||||
string Title,
|
|
||||||
DateOnly From,
|
|
||||||
DateOnly To,
|
|
||||||
string Summary,
|
|
||||||
bool Verified
|
|
||||||
);
|
|
||||||
|
|
||||||
public record CompleteUploadRequest(
|
|
||||||
Guid PersonId,
|
|
||||||
Guid? EmployerId,
|
|
||||||
string Title,
|
|
||||||
DateOnly From,
|
|
||||||
DateOnly To,
|
|
||||||
string? Summary,
|
|
||||||
string BlobPath,
|
|
||||||
string? BlobHash,
|
|
||||||
string? ContentBase64,
|
|
||||||
string? ContentType
|
|
||||||
);
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.Attests;
|
|
||||||
|
|
||||||
public record EmployerAttestUploadRequest(
|
|
||||||
Guid PersonId,
|
|
||||||
string Title,
|
|
||||||
DateOnly From,
|
|
||||||
DateOnly To,
|
|
||||||
string? Summary,
|
|
||||||
string BlobPath,
|
|
||||||
string? BlobHash,
|
|
||||||
string? ContentBase64,
|
|
||||||
string? ContentType
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.Attests;
|
|
||||||
|
|
||||||
public record PersonAttestUploadRequest(
|
|
||||||
string Title,
|
|
||||||
DateOnly From,
|
|
||||||
DateOnly To,
|
|
||||||
string? Summary,
|
|
||||||
string BlobPath,
|
|
||||||
string? BlobHash,
|
|
||||||
string? ContentBase64,
|
|
||||||
string? ContentType
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.Employers;
|
|
||||||
|
|
||||||
public record EmployerUserUpsertRequest(
|
|
||||||
string ExternalObjectId,
|
|
||||||
string Email,
|
|
||||||
string? Name,
|
|
||||||
string Role
|
|
||||||
);
|
|
||||||
|
|
||||||
public record EmployerUserResponse(
|
|
||||||
Guid Id,
|
|
||||||
Guid EmployerId,
|
|
||||||
string ExternalObjectId,
|
|
||||||
string Email,
|
|
||||||
string? Name,
|
|
||||||
string Role
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.Employers;
|
|
||||||
|
|
||||||
public record EmployerUpsertRequest(
|
|
||||||
string OrgNumber,
|
|
||||||
string Name
|
|
||||||
);
|
|
||||||
|
|
||||||
public record EmployerResponse(
|
|
||||||
Guid Id,
|
|
||||||
string OrgNumber,
|
|
||||||
string Name
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.Persons;
|
|
||||||
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
|
|
||||||
public record PersonUpsertRequest(
|
|
||||||
string NationalIdHash,
|
|
||||||
string? Email,
|
|
||||||
string? Phone
|
|
||||||
);
|
|
||||||
|
|
||||||
public record PersonResponse(
|
|
||||||
Guid Id,
|
|
||||||
string NationalIdHash,
|
|
||||||
string? Email,
|
|
||||||
string? Phone,
|
|
||||||
IReadOnlyList<AttestSummary> Attests
|
|
||||||
);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace MinAttest.Contracts.ShareLinks;
|
|
||||||
|
|
||||||
public record CreateShareLinkRequest(TimeSpan? ExpiresIn, bool? OneTime);
|
|
||||||
|
|
||||||
public record ShareLinkResponse(Guid ShareId, string Code, Uri Url, DateTimeOffset ExpiresAt, bool OneTime);
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public class Attest
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public Guid PersonId { get; set; }
|
|
||||||
public Person Person { get; set; } = null!;
|
|
||||||
|
|
||||||
public Guid? EmployerId { get; set; }
|
|
||||||
public Employer? Employer { get; set; }
|
|
||||||
|
|
||||||
public string Title { get; set; } = string.Empty;
|
|
||||||
public DateOnly From { get; set; }
|
|
||||||
public DateOnly To { get; set; }
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
|
|
||||||
public string BlobPath { get; set; } = string.Empty;
|
|
||||||
public string? BlobHash { get; set; }
|
|
||||||
|
|
||||||
// Temporary in-DB storage (to be replaced by Azure Blob)
|
|
||||||
public byte[]? Content { get; set; }
|
|
||||||
public string? ContentType { get; set; }
|
|
||||||
public long? ContentLength { get; set; }
|
|
||||||
|
|
||||||
public AttestStatus Status { get; set; } = AttestStatus.Unverified;
|
|
||||||
public DateTimeOffset IssuedAt { get; set; } = DateTimeOffset.UtcNow;
|
|
||||||
public string? IssuedBy { get; set; }
|
|
||||||
|
|
||||||
public ICollection<ShareLink> ShareLinks { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public class AuditLog
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public ActorType ActorType { get; set; }
|
|
||||||
public Guid? ActorId { get; set; }
|
|
||||||
public string Action { get; set; } = string.Empty;
|
|
||||||
public string TargetType { get; set; } = string.Empty;
|
|
||||||
public Guid TargetId { get; set; }
|
|
||||||
public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;
|
|
||||||
public string? Ip { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public class Employer
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public string OrgNumber { get; set; } = string.Empty;
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public ICollection<Attest> Attests { get; set; } = [];
|
|
||||||
public ICollection<EmployerUser> Users { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public class EmployerUser
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public Guid EmployerId { get; set; }
|
|
||||||
public Employer Employer { get; set; } = null!;
|
|
||||||
|
|
||||||
// Entra ID object id of the user
|
|
||||||
public string ExternalObjectId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string Email { get; set; } = string.Empty;
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public EmployerUserRole Role { get; set; } = EmployerUserRole.Issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public enum AttestStatus
|
|
||||||
{
|
|
||||||
Issued = 1,
|
|
||||||
Unverified = 2,
|
|
||||||
Revoked = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ActorType
|
|
||||||
{
|
|
||||||
Person = 1,
|
|
||||||
Employer = 2,
|
|
||||||
Verifier = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum EmployerUserRole
|
|
||||||
{
|
|
||||||
Issuer = 1,
|
|
||||||
Admin = 2
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public class Person
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public string NationalIdHash { get; set; } = null!;
|
|
||||||
public string? NationalIdEncrypted { get; set; }
|
|
||||||
public string? Email { get; set; }
|
|
||||||
public string? Phone { get; set; }
|
|
||||||
|
|
||||||
public ICollection<Attest> Attests { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace MinAttest.Domain.Entities;
|
|
||||||
|
|
||||||
public class ShareLink
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public Guid AttestId { get; set; }
|
|
||||||
public Attest Attest { get; set; } = null!;
|
|
||||||
|
|
||||||
public string Code { get; set; } = string.Empty;
|
|
||||||
public DateTimeOffset ExpiresAt { get; set; }
|
|
||||||
public DateTimeOffset? RevokedAt { get; set; }
|
|
||||||
public bool OneTime { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MinAttest.Domain.Entities;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options), IAppDbContext
|
|
||||||
{
|
|
||||||
public DbSet<Person> Persons => Set<Person>();
|
|
||||||
public DbSet<Employer> Employers => Set<Employer>();
|
|
||||||
public DbSet<Attest> Attests => Set<Attest>();
|
|
||||||
public DbSet<ShareLink> ShareLinks => Set<ShareLink>();
|
|
||||||
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
|
||||||
public DbSet<EmployerUser> EmployerUsers => Set<EmployerUser>();
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder.Entity<Person>(b =>
|
|
||||||
{
|
|
||||||
b.HasKey(x => x.Id);
|
|
||||||
b.Property(x => x.NationalIdHash).HasMaxLength(200);
|
|
||||||
b.HasIndex(x => x.NationalIdHash);
|
|
||||||
b.ToTable(t => t.IsTemporal(tt => tt
|
|
||||||
.UseHistoryTable("PersonsHistory")
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<Employer>(b =>
|
|
||||||
{
|
|
||||||
b.HasKey(x => x.Id);
|
|
||||||
b.Property(x => x.OrgNumber).HasMaxLength(32);
|
|
||||||
b.HasIndex(x => x.OrgNumber).IsUnique(false);
|
|
||||||
b.ToTable(t => t.IsTemporal(tt => tt
|
|
||||||
.UseHistoryTable("EmployersHistory")
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<Attest>(b =>
|
|
||||||
{
|
|
||||||
b.HasKey(x => x.Id);
|
|
||||||
b.Property(x => x.Title).HasMaxLength(200);
|
|
||||||
b.Property(x => x.Summary).HasMaxLength(2000);
|
|
||||||
b.Property(x => x.BlobPath).HasMaxLength(500);
|
|
||||||
b.Property(x => x.BlobHash).HasMaxLength(128);
|
|
||||||
b.Property(x => x.Content).HasColumnType("varbinary(max)");
|
|
||||||
b.Property(x => x.ContentType).HasMaxLength(255);
|
|
||||||
b.Property(x => x.ContentLength).HasColumnType("bigint");
|
|
||||||
b.HasOne(x => x.Person)
|
|
||||||
.WithMany(x => x.Attests)
|
|
||||||
.HasForeignKey(x => x.PersonId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
b.HasOne(x => x.Employer)
|
|
||||||
.WithMany(x => x.Attests)
|
|
||||||
.HasForeignKey(x => x.EmployerId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
// Consistency: EmployerId null <=> Unverified, EmployerId set => Issued/Revoked
|
|
||||||
b.ToTable(t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint(
|
|
||||||
"CK_Attests_Verification",
|
|
||||||
"(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
t.IsTemporal(tt => tt.UseHistoryTable("AttestsHistory"));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helpful composite indexes for common queries
|
|
||||||
b.HasIndex(x => new { x.PersonId, x.Status });
|
|
||||||
b.HasIndex(x => new { x.EmployerId, x.Status });
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<ShareLink>(b =>
|
|
||||||
{
|
|
||||||
b.HasKey(x => x.Id);
|
|
||||||
b.Property(x => x.Code).HasMaxLength(200);
|
|
||||||
b.HasIndex(x => x.Code).IsUnique();
|
|
||||||
b.HasOne(x => x.Attest)
|
|
||||||
.WithMany(x => x.ShareLinks)
|
|
||||||
.HasForeignKey(x => x.AttestId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
b.ToTable(t => t.IsTemporal(tt => tt
|
|
||||||
.UseHistoryTable("ShareLinksHistory")
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<EmployerUser>(b =>
|
|
||||||
{
|
|
||||||
b.HasKey(x => x.Id);
|
|
||||||
b.Property(x => x.ExternalObjectId).HasMaxLength(128);
|
|
||||||
b.Property(x => x.Email).HasMaxLength(256);
|
|
||||||
b.Property(x => x.Name).HasMaxLength(200);
|
|
||||||
b.HasIndex(x => x.ExternalObjectId).IsUnique();
|
|
||||||
b.HasIndex(x => x.EmployerId);
|
|
||||||
b.HasOne(x => x.Employer)
|
|
||||||
.WithMany(e => e.Users)
|
|
||||||
.HasForeignKey(x => x.EmployerId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
b.ToTable(t => t.IsTemporal(tt => tt
|
|
||||||
.UseHistoryTable("EmployerUsersHistory")
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<AuditLog>(b =>
|
|
||||||
{
|
|
||||||
b.HasKey(x => x.Id);
|
|
||||||
b.Property(x => x.Action).HasMaxLength(100);
|
|
||||||
b.Property(x => x.TargetType).HasMaxLength(100);
|
|
||||||
b.Property(x => x.Ip).HasMaxLength(64);
|
|
||||||
b.HasIndex(x => x.Timestamp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int SaveChanges(bool acceptAllChangesOnSuccess)
|
|
||||||
{
|
|
||||||
NormalizeAttestStatuses();
|
|
||||||
return base.SaveChanges(acceptAllChangesOnSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
NormalizeAttestStatuses();
|
|
||||||
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NormalizeAttestStatuses()
|
|
||||||
{
|
|
||||||
foreach (var entry in ChangeTracker.Entries<Attest>())
|
|
||||||
{
|
|
||||||
if (entry.State is not (EntityState.Added or EntityState.Modified)) continue;
|
|
||||||
var a = entry.Entity;
|
|
||||||
|
|
||||||
if (a.EmployerId is null)
|
|
||||||
{
|
|
||||||
// No employer -> always Unverified
|
|
||||||
a.Status = AttestStatus.Unverified;
|
|
||||||
}
|
|
||||||
else if (a.Status == AttestStatus.Unverified)
|
|
||||||
{
|
|
||||||
// Has employer but Unverified -> default to Issued
|
|
||||||
a.Status = AttestStatus.Issued;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
|
|
||||||
{
|
|
||||||
public AppDbContext CreateDbContext(string[] args)
|
|
||||||
{
|
|
||||||
var basePath = Directory.GetCurrentDirectory();
|
|
||||||
Directory.CreateDirectory(Path.Combine(basePath, "data"));
|
|
||||||
|
|
||||||
var config = new ConfigurationBuilder()
|
|
||||||
.SetBasePath(basePath)
|
|
||||||
.AddJsonFile("appsettings.json", optional: true)
|
|
||||||
.AddJsonFile("appsettings.Development.json", optional: true)
|
|
||||||
.AddEnvironmentVariables()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var cs = config.GetConnectionString("Default")
|
|
||||||
?? config.GetConnectionString("MinAttest")
|
|
||||||
?? "Server=localhost,1433;Database=MinAttest;User Id=sa;Password=Your_password123;TrustServerCertificate=True;";
|
|
||||||
|
|
||||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
|
||||||
.UseSqlServer(cs)
|
|
||||||
.Options;
|
|
||||||
|
|
||||||
return new AppDbContext(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
-249
@@ -1,249 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
[Migration("20250913095523_InitialSqlServer")]
|
|
||||||
partial class InitialSqlServer
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("BlobHash")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("BlobPath")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("From")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("IssuedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<string>("IssuedBy")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<Guid>("PersonId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
|
||||||
.HasMaxLength(2000)
|
|
||||||
.HasColumnType("nvarchar(2000)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("To")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId");
|
|
||||||
|
|
||||||
b.HasIndex("PersonId");
|
|
||||||
|
|
||||||
b.ToTable("Attests");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.AuditLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("ActorId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("ActorType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid>("TargetId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("TargetType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Timestamp")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Timestamp");
|
|
||||||
|
|
||||||
b.ToTable("AuditLogs");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("OrgNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("nvarchar(32)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OrgNumber");
|
|
||||||
|
|
||||||
b.ToTable("Employers");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdEncrypted")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<string>("Phone")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NationalIdHash");
|
|
||||||
|
|
||||||
b.ToTable("Persons");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<Guid>("AttestId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Code")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ExpiresAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<bool>("OneTime")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("RevokedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AttestId");
|
|
||||||
|
|
||||||
b.HasIndex("Code")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Api.Data.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("MinAttest.Api.Data.Entities.Person", "Person")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("PersonId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
|
|
||||||
b.Navigation("Person");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Api.Data.Entities.Attest", "Attest")
|
|
||||||
.WithMany("ShareLinks")
|
|
||||||
.HasForeignKey("AttestId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Attest");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-172
@@ -1,172 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class InitialSqlServer : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "AuditLogs",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
ActorType = table.Column<int>(type: "int", nullable: false),
|
|
||||||
ActorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
|
||||||
Action = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
|
||||||
TargetType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
|
||||||
TargetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
Timestamp = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
|
|
||||||
Ip = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_AuditLogs", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Employers",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
OrgNumber = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
|
||||||
Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Employers", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Persons",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
NationalIdHash = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
|
||||||
NationalIdEncrypted = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
|
||||||
Email = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
|
||||||
Phone = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Persons", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Attests",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
PersonId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
EmployerId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
|
||||||
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
|
||||||
From = table.Column<DateOnly>(type: "date", nullable: false),
|
|
||||||
To = table.Column<DateOnly>(type: "date", nullable: false),
|
|
||||||
Summary = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
|
||||||
BlobPath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
|
||||||
BlobHash = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
|
||||||
Status = table.Column<int>(type: "int", nullable: false),
|
|
||||||
IssuedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
|
|
||||||
IssuedBy = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Attests", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Attests_Employers_EmployerId",
|
|
||||||
column: x => x.EmployerId,
|
|
||||||
principalTable: "Employers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Attests_Persons_PersonId",
|
|
||||||
column: x => x.PersonId,
|
|
||||||
principalTable: "Persons",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "ShareLinks",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
AttestId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
Code = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
|
||||||
ExpiresAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
|
|
||||||
RevokedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
|
|
||||||
OneTime = table.Column<bool>(type: "bit", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_ShareLinks", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_ShareLinks_Attests_AttestId",
|
|
||||||
column: x => x.AttestId,
|
|
||||||
principalTable: "Attests",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Attests_EmployerId",
|
|
||||||
table: "Attests",
|
|
||||||
column: "EmployerId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Attests_PersonId",
|
|
||||||
table: "Attests",
|
|
||||||
column: "PersonId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_AuditLogs_Timestamp",
|
|
||||||
table: "AuditLogs",
|
|
||||||
column: "Timestamp");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Employers_OrgNumber",
|
|
||||||
table: "Employers",
|
|
||||||
column: "OrgNumber");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Persons_NationalIdHash",
|
|
||||||
table: "Persons",
|
|
||||||
column: "NationalIdHash");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_ShareLinks_AttestId",
|
|
||||||
table: "ShareLinks",
|
|
||||||
column: "AttestId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_ShareLinks_Code",
|
|
||||||
table: "ShareLinks",
|
|
||||||
column: "Code",
|
|
||||||
unique: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "AuditLogs");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "ShareLinks");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Employers");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Persons");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-252
@@ -1,252 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
[Migration("20250913104919_AddAttestVerificationCheck")]
|
|
||||||
partial class AddAttestVerificationCheck
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("BlobHash")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("BlobPath")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("From")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("IssuedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<string>("IssuedBy")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<Guid>("PersonId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
|
||||||
.HasMaxLength(2000)
|
|
||||||
.HasColumnType("nvarchar(2000)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("To")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId", "Status");
|
|
||||||
|
|
||||||
b.HasIndex("PersonId", "Status");
|
|
||||||
|
|
||||||
b.ToTable("Attests", t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("CK_Attests_Verification", "(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.AuditLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("ActorId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("ActorType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid>("TargetId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("TargetType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Timestamp")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Timestamp");
|
|
||||||
|
|
||||||
b.ToTable("AuditLogs");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("OrgNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("nvarchar(32)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OrgNumber");
|
|
||||||
|
|
||||||
b.ToTable("Employers");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdEncrypted")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<string>("Phone")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NationalIdHash");
|
|
||||||
|
|
||||||
b.ToTable("Persons");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<Guid>("AttestId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Code")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ExpiresAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<bool>("OneTime")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("RevokedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AttestId");
|
|
||||||
|
|
||||||
b.HasIndex("Code")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Api.Data.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("MinAttest.Api.Data.Entities.Person", "Person")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("PersonId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
|
|
||||||
b.Navigation("Person");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Api.Data.Entities.Attest", "Attest")
|
|
||||||
.WithMany("ShareLinks")
|
|
||||||
.HasForeignKey("AttestId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Attest");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Api.Data.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddAttestVerificationCheck : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Attests_EmployerId",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Attests_PersonId",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Attests_EmployerId_Status",
|
|
||||||
table: "Attests",
|
|
||||||
columns: new[] { "EmployerId", "Status" });
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Attests_PersonId_Status",
|
|
||||||
table: "Attests",
|
|
||||||
columns: new[] { "PersonId", "Status" });
|
|
||||||
|
|
||||||
migrationBuilder.AddCheckConstraint(
|
|
||||||
name: "CK_Attests_Verification",
|
|
||||||
table: "Attests",
|
|
||||||
sql: "(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Attests_EmployerId_Status",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Attests_PersonId_Status",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.DropCheckConstraint(
|
|
||||||
name: "CK_Attests_Verification",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Attests_EmployerId",
|
|
||||||
table: "Attests",
|
|
||||||
column: "EmployerId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Attests_PersonId",
|
|
||||||
table: "Attests",
|
|
||||||
column: "PersonId");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
backend/src/MinAttest.Infrastructure/Data/Migrations/20250913112309_EnableTemporalTables.Designer.cs
Generated
-336
@@ -1,336 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
[Migration("20250913112309_EnableTemporalTables")]
|
|
||||||
partial class EnableTemporalTables
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("BlobHash")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("BlobPath")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("From")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("IssuedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<string>("IssuedBy")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<Guid>("PersonId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
|
||||||
.HasMaxLength(2000)
|
|
||||||
.HasColumnType("nvarchar(2000)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("To")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId", "Status");
|
|
||||||
|
|
||||||
b.HasIndex("PersonId", "Status");
|
|
||||||
|
|
||||||
b.ToTable("Attests", t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("CK_Attests_Verification", "(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("AttestsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.AuditLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("ActorId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("ActorType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid>("TargetId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("TargetType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Timestamp")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Timestamp");
|
|
||||||
|
|
||||||
b.ToTable("AuditLogs");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("OrgNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("nvarchar(32)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OrgNumber");
|
|
||||||
|
|
||||||
b.ToTable("Employers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdEncrypted")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<string>("Phone")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NationalIdHash");
|
|
||||||
|
|
||||||
b.ToTable("Persons");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("PersonsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<Guid>("AttestId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Code")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ExpiresAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<bool>("OneTime")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("RevokedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AttestId");
|
|
||||||
|
|
||||||
b.HasIndex("Code")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("ShareLinks");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("ShareLinksHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Person", "Person")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("PersonId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
|
|
||||||
b.Navigation("Person");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Attest", "Attest")
|
|
||||||
.WithMany("ShareLinks")
|
|
||||||
.HasForeignKey("AttestId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Attest");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-187
@@ -1,187 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class EnableTemporalTables : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "ShareLinks")
|
|
||||||
.Annotation("SqlServer:IsTemporal", true)
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableName", "ShareLinksHistory")
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "Persons")
|
|
||||||
.Annotation("SqlServer:IsTemporal", true)
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableName", "PersonsHistory")
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "Employers")
|
|
||||||
.Annotation("SqlServer:IsTemporal", true)
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableName", "EmployersHistory")
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "Attests")
|
|
||||||
.Annotation("SqlServer:IsTemporal", true)
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableName", "AttestsHistory")
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "ShareLinks",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "ShareLinks",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "Persons",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "Persons",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "Employers",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "Employers",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "Attests",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "Attests",
|
|
||||||
type: "datetime2",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "ShareLinks")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "ShareLinks")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "Persons")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "Persons")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "Employers")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "Employers")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodEnd",
|
|
||||||
table: "Attests")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PeriodStart",
|
|
||||||
table: "Attests")
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "ShareLinks")
|
|
||||||
.OldAnnotation("SqlServer:IsTemporal", true)
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableName", "ShareLinksHistory")
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "Persons")
|
|
||||||
.OldAnnotation("SqlServer:IsTemporal", true)
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableName", "PersonsHistory")
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "Employers")
|
|
||||||
.OldAnnotation("SqlServer:IsTemporal", true)
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableName", "EmployersHistory")
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.AlterTable(
|
|
||||||
name: "Attests")
|
|
||||||
.OldAnnotation("SqlServer:IsTemporal", true)
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableName", "AttestsHistory")
|
|
||||||
.OldAnnotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.OldAnnotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
-406
@@ -1,406 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
[Migration("20250913122233_AddEmployerUser")]
|
|
||||||
partial class AddEmployerUser
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("BlobHash")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("BlobPath")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("From")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("IssuedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<string>("IssuedBy")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<Guid>("PersonId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
|
||||||
.HasMaxLength(2000)
|
|
||||||
.HasColumnType("nvarchar(2000)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("To")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId", "Status");
|
|
||||||
|
|
||||||
b.HasIndex("PersonId", "Status");
|
|
||||||
|
|
||||||
b.ToTable("Attests", t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("CK_Attests_Verification", "(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("AttestsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.AuditLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("ActorId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("ActorType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid>("TargetId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("TargetType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Timestamp")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Timestamp");
|
|
||||||
|
|
||||||
b.ToTable("AuditLogs");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("OrgNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("nvarchar(32)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OrgNumber");
|
|
||||||
|
|
||||||
b.ToTable("Employers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.EmployerUser", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<Guid>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("ExternalObjectId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<int>("Role")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId");
|
|
||||||
|
|
||||||
b.HasIndex("ExternalObjectId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("EmployerUsers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployerUsersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdEncrypted")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<string>("Phone")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NationalIdHash");
|
|
||||||
|
|
||||||
b.ToTable("Persons");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("PersonsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<Guid>("AttestId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Code")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ExpiresAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<bool>("OneTime")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("RevokedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AttestId");
|
|
||||||
|
|
||||||
b.HasIndex("Code")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("ShareLinks");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("ShareLinksHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Person", "Person")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("PersonId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
|
|
||||||
b.Navigation("Person");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.EmployerUser", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Users")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Attest", "Attest")
|
|
||||||
.WithMany("ShareLinks")
|
|
||||||
.HasForeignKey("AttestId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Attest");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
|
|
||||||
b.Navigation("Users");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-69
@@ -1,69 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddEmployerUser : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "EmployerUsers",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
EmployerId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
ExternalObjectId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
|
||||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
|
||||||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
|
||||||
Role = table.Column<int>(type: "int", nullable: false),
|
|
||||||
PeriodEnd = table.Column<DateTime>(type: "datetime2", nullable: false)
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true),
|
|
||||||
PeriodStart = table.Column<DateTime>(type: "datetime2", nullable: false)
|
|
||||||
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_EmployerUsers", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_EmployerUsers_Employers_EmployerId",
|
|
||||||
column: x => x.EmployerId,
|
|
||||||
principalTable: "Employers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
})
|
|
||||||
.Annotation("SqlServer:IsTemporal", true)
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableName", "EmployerUsersHistory")
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_EmployerUsers_EmployerId",
|
|
||||||
table: "EmployerUsers",
|
|
||||||
column: "EmployerId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_EmployerUsers_ExternalObjectId",
|
|
||||||
table: "EmployerUsers",
|
|
||||||
column: "ExternalObjectId",
|
|
||||||
unique: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "EmployerUsers")
|
|
||||||
.Annotation("SqlServer:IsTemporal", true)
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableName", "EmployerUsersHistory")
|
|
||||||
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
|
|
||||||
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
|
|
||||||
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-416
@@ -1,416 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
[Migration("20250913124425_AddAttestContentColumns")]
|
|
||||||
partial class AddAttestContentColumns
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("BlobHash")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("BlobPath")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Content")
|
|
||||||
.HasColumnType("varbinary(max)");
|
|
||||||
|
|
||||||
b.Property<long?>("ContentLength")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.Property<string>("ContentType")
|
|
||||||
.HasMaxLength(255)
|
|
||||||
.HasColumnType("nvarchar(255)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("From")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("IssuedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<string>("IssuedBy")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<Guid>("PersonId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
|
||||||
.HasMaxLength(2000)
|
|
||||||
.HasColumnType("nvarchar(2000)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("To")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId", "Status");
|
|
||||||
|
|
||||||
b.HasIndex("PersonId", "Status");
|
|
||||||
|
|
||||||
b.ToTable("Attests", t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("CK_Attests_Verification", "(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("AttestsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.AuditLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("ActorId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("ActorType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid>("TargetId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("TargetType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Timestamp")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Timestamp");
|
|
||||||
|
|
||||||
b.ToTable("AuditLogs");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("OrgNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("nvarchar(32)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OrgNumber");
|
|
||||||
|
|
||||||
b.ToTable("Employers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.EmployerUser", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<Guid>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("ExternalObjectId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<int>("Role")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId");
|
|
||||||
|
|
||||||
b.HasIndex("ExternalObjectId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("EmployerUsers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployerUsersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdEncrypted")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<string>("Phone")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NationalIdHash");
|
|
||||||
|
|
||||||
b.ToTable("Persons");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("PersonsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<Guid>("AttestId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Code")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ExpiresAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<bool>("OneTime")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("RevokedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AttestId");
|
|
||||||
|
|
||||||
b.HasIndex("Code")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("ShareLinks");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("ShareLinksHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Person", "Person")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("PersonId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
|
|
||||||
b.Navigation("Person");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.EmployerUser", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Users")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Attest", "Attest")
|
|
||||||
.WithMany("ShareLinks")
|
|
||||||
.HasForeignKey("AttestId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Attest");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
|
|
||||||
b.Navigation("Users");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddAttestContentColumns : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<byte[]>(
|
|
||||||
name: "Content",
|
|
||||||
table: "Attests",
|
|
||||||
type: "varbinary(max)",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<long>(
|
|
||||||
name: "ContentLength",
|
|
||||||
table: "Attests",
|
|
||||||
type: "bigint",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "ContentType",
|
|
||||||
table: "Attests",
|
|
||||||
type: "nvarchar(255)",
|
|
||||||
maxLength: 255,
|
|
||||||
nullable: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Content",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ContentLength",
|
|
||||||
table: "Attests");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ContentType",
|
|
||||||
table: "Attests");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,413 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace MinAttest.Infrastructure.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDbContext))]
|
|
||||||
partial class AppDbContextModelSnapshot : ModelSnapshot
|
|
||||||
{
|
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("BlobHash")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("BlobPath")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Content")
|
|
||||||
.HasColumnType("varbinary(max)");
|
|
||||||
|
|
||||||
b.Property<long?>("ContentLength")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.Property<string>("ContentType")
|
|
||||||
.HasMaxLength(255)
|
|
||||||
.HasColumnType("nvarchar(255)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("From")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("IssuedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<string>("IssuedBy")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<Guid>("PersonId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
|
||||||
.HasMaxLength(2000)
|
|
||||||
.HasColumnType("nvarchar(2000)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateOnly>("To")
|
|
||||||
.HasColumnType("date");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId", "Status");
|
|
||||||
|
|
||||||
b.HasIndex("PersonId", "Status");
|
|
||||||
|
|
||||||
b.ToTable("Attests", t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("CK_Attests_Verification", "(([EmployerId] IS NULL AND [Status] = 2) OR ([EmployerId] IS NOT NULL AND [Status] IN (1,3)))");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("AttestsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.AuditLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("ActorId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<int>("ActorType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<Guid>("TargetId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("TargetType")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("Timestamp")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("Timestamp");
|
|
||||||
|
|
||||||
b.ToTable("AuditLogs");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("OrgNumber")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("nvarchar(32)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OrgNumber");
|
|
||||||
|
|
||||||
b.ToTable("Employers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.EmployerUser", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("nvarchar(256)");
|
|
||||||
|
|
||||||
b.Property<Guid>("EmployerId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("ExternalObjectId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("nvarchar(128)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<int>("Role")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("EmployerId");
|
|
||||||
|
|
||||||
b.HasIndex("ExternalObjectId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("EmployerUsers");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("EmployerUsersHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdEncrypted")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("NationalIdHash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<string>("Phone")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NationalIdHash");
|
|
||||||
|
|
||||||
b.ToTable("Persons");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("PersonsHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<Guid>("AttestId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<string>("Code")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("ExpiresAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.Property<bool>("OneTime")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodEnd")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
|
|
||||||
b.Property<DateTime>("PeriodStart")
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("RevokedAt")
|
|
||||||
.HasColumnType("datetimeoffset");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AttestId");
|
|
||||||
|
|
||||||
b.HasIndex("Code")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("ShareLinks");
|
|
||||||
|
|
||||||
b.ToTable(tb => tb.IsTemporal(ttb =>
|
|
||||||
{
|
|
||||||
ttb.UseHistoryTable("ShareLinksHistory");
|
|
||||||
ttb
|
|
||||||
.HasPeriodStart("PeriodStart")
|
|
||||||
.HasColumnName("PeriodStart");
|
|
||||||
ttb
|
|
||||||
.HasPeriodEnd("PeriodEnd")
|
|
||||||
.HasColumnName("PeriodEnd");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Person", "Person")
|
|
||||||
.WithMany("Attests")
|
|
||||||
.HasForeignKey("PersonId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
|
|
||||||
b.Navigation("Person");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.EmployerUser", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Employer", "Employer")
|
|
||||||
.WithMany("Users")
|
|
||||||
.HasForeignKey("EmployerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Employer");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.ShareLink", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MinAttest.Domain.Entities.Attest", "Attest")
|
|
||||||
.WithMany("ShareLinks")
|
|
||||||
.HasForeignKey("AttestId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Attest");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Attest", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ShareLinks");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Employer", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
|
|
||||||
b.Navigation("Users");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MinAttest.Domain.Entities.Person", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Attests");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.11" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MinAttest.Domain\MinAttest.Domain.csproj" />
|
|
||||||
<ProjectReference Include="..\MinAttest.Application\MinAttest.Application.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using MinAttest.Application.Features.Attests.Commands;
|
|
||||||
using MinAttest.Application.Features.Employers.Commands;
|
|
||||||
using MinAttest.Application.Features.Persons.Commands;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Domain;
|
|
||||||
|
|
||||||
public class ValidationTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void UpsertPersonCommand_invalid_when_missing_hash()
|
|
||||||
{
|
|
||||||
var v = new UpsertPersonCommandValidator();
|
|
||||||
var r = v.Validate(new UpsertPersonCommand("", null, null));
|
|
||||||
r.IsValid.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void UpsertEmployerCommand_invalid_when_missing_fields()
|
|
||||||
{
|
|
||||||
var v = new UpsertEmployerCommandValidator();
|
|
||||||
var r = v.Validate(new UpsertEmployerCommand("", ""));
|
|
||||||
r.IsValid.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PersonUploadAttestCommand_requires_title_and_dates()
|
|
||||||
{
|
|
||||||
var v = new PersonUploadAttestCommandValidator();
|
|
||||||
var cmd = new PersonUploadAttestCommand(Guid.NewGuid(), new PersonAttestUploadRequest(
|
|
||||||
Title: "",
|
|
||||||
From: new DateOnly(2024,12,31),
|
|
||||||
To: new DateOnly(2024,1,1),
|
|
||||||
Summary: null,
|
|
||||||
BlobPath: "",
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: null,
|
|
||||||
ContentType: null));
|
|
||||||
var r = v.Validate(cmd);
|
|
||||||
r.IsValid.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void EmployerIssueAttestCommand_requires_person_title_and_dates()
|
|
||||||
{
|
|
||||||
var v = new EmployerIssueAttestCommandValidator();
|
|
||||||
var cmd = new EmployerIssueAttestCommand(Guid.Empty, new EmployerAttestUploadRequest(
|
|
||||||
PersonId: Guid.Empty,
|
|
||||||
Title: "",
|
|
||||||
From: new DateOnly(2024,12,31),
|
|
||||||
To: new DateOnly(2024,1,1),
|
|
||||||
Summary: null,
|
|
||||||
BlobPath: "",
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: null,
|
|
||||||
ContentType: null));
|
|
||||||
var r = v.Validate(cmd);
|
|
||||||
r.IsValid.Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using FluentAssertions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Contracts.Employers;
|
|
||||||
using MinAttest.Contracts.Persons;
|
|
||||||
using MinAttest.Tests.Integration.Server;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration.Api;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class EmployersApiTests : IClassFixture<ApiWebAppFactory>
|
|
||||||
{
|
|
||||||
private readonly HttpClient _client;
|
|
||||||
public EmployersApiTests(ApiWebAppFactory factory) => _client = factory.CreateClient();
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Employer_issue_and_list_and_download_via_http()
|
|
||||||
{
|
|
||||||
// Upsert employer
|
|
||||||
var employerUpsert = new EmployerUpsertRequest("321654987", "ApiCo AS");
|
|
||||||
var employerResp = await _client.PostAsJsonAsync("/api/v1/employers", employerUpsert);
|
|
||||||
employerResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
var employer = await employerResp.Content.ReadFromJsonAsync<EmployerResponse>();
|
|
||||||
employer.Should().NotBeNull();
|
|
||||||
|
|
||||||
// Upsert person
|
|
||||||
var personResp = await _client.PostAsJsonAsync("/api/v1/persons", new PersonUpsertRequest("hash-api-emp-1", "emp-person@example.com", null));
|
|
||||||
personResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
var person = await personResp.Content.ReadFromJsonAsync<PersonResponse>();
|
|
||||||
person.Should().NotBeNull();
|
|
||||||
|
|
||||||
// Issue attest for person
|
|
||||||
var issueReq = new EmployerAttestUploadRequest(
|
|
||||||
PersonId: person!.Id,
|
|
||||||
Title: "ApiEmployerAttest",
|
|
||||||
From: new DateOnly(2024,1,1),
|
|
||||||
To: new DateOnly(2024,6,1),
|
|
||||||
Summary: null,
|
|
||||||
BlobPath: string.Empty,
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: Convert.ToBase64String(new byte[]{7,8,9}),
|
|
||||||
ContentType: "application/octet-stream");
|
|
||||||
|
|
||||||
var issueResp = await _client.PostAsJsonAsync($"/api/v1/employers/{employer!.Id}/attests", issueReq);
|
|
||||||
issueResp.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
||||||
var created = await issueResp.Content.ReadFromJsonAsync<Dictionary<string, object>>();
|
|
||||||
created.Should().NotBeNull();
|
|
||||||
var createdId = Guid.Parse(created!["attestId"].ToString()!);
|
|
||||||
|
|
||||||
// List employer attests
|
|
||||||
var listResp = await _client.GetAsync($"/api/v1/employers/{employer.Id}/attests");
|
|
||||||
listResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
|
|
||||||
// Download
|
|
||||||
var dlResp = await _client.GetAsync($"/api/v1/employers/{employer.Id}/attests/{createdId}/download");
|
|
||||||
dlResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
(await dlResp.Content.ReadAsByteArrayAsync()).Length.Should().BeGreaterThan(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using FluentAssertions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Contracts.Persons;
|
|
||||||
using MinAttest.Tests.Integration.Server;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration.Api;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class PersonsApiTests : IClassFixture<ApiWebAppFactory>
|
|
||||||
{
|
|
||||||
private readonly HttpClient _client;
|
|
||||||
public PersonsApiTests(ApiWebAppFactory factory) => _client = factory.CreateClient();
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Upsert_person_and_upload_and_list()
|
|
||||||
{
|
|
||||||
var upsertResp = await _client.PostAsJsonAsync("/api/v1/persons", new PersonUpsertRequest("hash-api-1", "api@example.com", null));
|
|
||||||
upsertResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
var person = await upsertResp.Content.ReadFromJsonAsync<PersonResponse>();
|
|
||||||
person.Should().NotBeNull();
|
|
||||||
|
|
||||||
var uploadReq = new PersonAttestUploadRequest(
|
|
||||||
Title: "ApiAttest",
|
|
||||||
From: new DateOnly(2024,1,1),
|
|
||||||
To: new DateOnly(2024,6,1),
|
|
||||||
Summary: "",
|
|
||||||
BlobPath: "",
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: Convert.ToBase64String(new byte[]{1,2,3}),
|
|
||||||
ContentType: "application/octet-stream");
|
|
||||||
|
|
||||||
var uploadResp = await _client.PostAsJsonAsync($"/api/v1/persons/{person!.Id}/attests", uploadReq);
|
|
||||||
uploadResp.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
||||||
|
|
||||||
var listResp = await _client.GetAsync($"/api/v1/persons/{person.Id}/attests");
|
|
||||||
listResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using FluentAssertions;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using MinAttest.Contracts.Persons;
|
|
||||||
using MinAttest.Contracts.ShareLinks;
|
|
||||||
using MinAttest.Tests.Integration.Server;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration.Api;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class ShareLinksApiTests : IClassFixture<ApiWebAppFactory>
|
|
||||||
{
|
|
||||||
private readonly HttpClient _client;
|
|
||||||
public ShareLinksApiTests(ApiWebAppFactory factory) => _client = factory.CreateClient();
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Create_list_revoke_sharelink_via_http()
|
|
||||||
{
|
|
||||||
// Upsert person
|
|
||||||
var personResp = await _client.PostAsJsonAsync("/api/v1/persons", new PersonUpsertRequest("hash-api-share-1", "share-person@example.com", null));
|
|
||||||
personResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
var person = await personResp.Content.ReadFromJsonAsync<PersonResponse>();
|
|
||||||
|
|
||||||
// Upload attest
|
|
||||||
var upload = new PersonAttestUploadRequest(
|
|
||||||
Title: "ShareAttest",
|
|
||||||
From: new DateOnly(2024,1,1),
|
|
||||||
To: new DateOnly(2024,6,1),
|
|
||||||
Summary: null,
|
|
||||||
BlobPath: string.Empty,
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: Convert.ToBase64String(new byte[]{1,2,3,4}),
|
|
||||||
ContentType: "application/pdf");
|
|
||||||
var upResp = await _client.PostAsJsonAsync($"/api/v1/persons/{person!.Id}/attests", upload);
|
|
||||||
upResp.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
||||||
var created = await upResp.Content.ReadFromJsonAsync<Dictionary<string, object>>();
|
|
||||||
var attestId = Guid.Parse(created!["attestId"].ToString()!);
|
|
||||||
|
|
||||||
// Create share link
|
|
||||||
var createReq = new CreateShareLinkRequest(TimeSpan.FromDays(7), false);
|
|
||||||
var slResp = await _client.PostAsJsonAsync($"/api/v1/attests/{attestId}/sharelinks", createReq);
|
|
||||||
slResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
var link = await slResp.Content.ReadFromJsonAsync<ShareLinkResponse>();
|
|
||||||
link.Should().NotBeNull();
|
|
||||||
|
|
||||||
// List
|
|
||||||
var listResp = await _client.GetAsync($"/api/v1/attests/{attestId}/sharelinks");
|
|
||||||
listResp.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
||||||
|
|
||||||
// Revoke
|
|
||||||
var delResp = await _client.DeleteAsync($"/api/v1/attests/{attestId}/sharelinks/{link!.ShareId}");
|
|
||||||
delResp.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MinAttest.Application.Features.Attests.Commands;
|
|
||||||
using MinAttest.Application.Features.Attests.Queries;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class AttestsIntegrationTests
|
|
||||||
{
|
|
||||||
private readonly IntegrationTestFixture _fixture;
|
|
||||||
|
|
||||||
public AttestsIntegrationTests(IntegrationTestFixture fixture) => _fixture = fixture;
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Person_upload_and_list_attest_success()
|
|
||||||
{
|
|
||||||
var sp = _fixture.Services;
|
|
||||||
var mediator = sp.GetRequiredService<IMediator>();
|
|
||||||
|
|
||||||
// Ensure person exists
|
|
||||||
var upsert = await mediator.Send(new MinAttest.Application.Features.Persons.Commands.UpsertPersonCommand("person-hash-123", "person@example.com", null));
|
|
||||||
var personId = upsert.Id;
|
|
||||||
|
|
||||||
var req = new PersonAttestUploadRequest(
|
|
||||||
Title: "TestAttest",
|
|
||||||
From: new DateOnly(2023,1,1),
|
|
||||||
To: new DateOnly(2023,12,31),
|
|
||||||
Summary: "Test summary",
|
|
||||||
BlobPath: string.Empty,
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: Convert.ToBase64String(new byte[] {1,2,3}),
|
|
||||||
ContentType: "application/octet-stream"
|
|
||||||
);
|
|
||||||
|
|
||||||
var createdId = await mediator.Send(new PersonUploadAttestCommand(personId, req));
|
|
||||||
createdId.Should().NotBeNull();
|
|
||||||
|
|
||||||
var list = await mediator.Send(new ListPersonAttestsQuery(personId, 10));
|
|
||||||
list.Should().NotBeNull();
|
|
||||||
list!.Any(a => a.Id == createdId).Should().BeTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MinAttest.Application.Features.Employers.Commands;
|
|
||||||
using MinAttest.Application.Features.Employers.Queries;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class EmployerUsersIntegrationTests
|
|
||||||
{
|
|
||||||
private readonly IntegrationTestFixture _fixture;
|
|
||||||
public EmployerUsersIntegrationTests(IntegrationTestFixture fixture) => _fixture = fixture;
|
|
||||||
[Fact]
|
|
||||||
public async Task Upsert_list_get_delete_employer_user()
|
|
||||||
{
|
|
||||||
var mediator = _fixture.Services.GetRequiredService<IMediator>();
|
|
||||||
|
|
||||||
// Ensure employer exists
|
|
||||||
var employer = await mediator.Send(new UpsertEmployerCommand("999999999", "TestBedrift AS"));
|
|
||||||
|
|
||||||
// Upsert user
|
|
||||||
var user = await mediator.Send(new UpsertEmployerUserCommand(
|
|
||||||
employer.Id, "oid-123", "hr@example.com", "Hr User", "Issuer"));
|
|
||||||
user.Should().NotBeNull();
|
|
||||||
|
|
||||||
// List
|
|
||||||
var list = await mediator.Send(new ListEmployerUsersQuery(employer.Id));
|
|
||||||
list.Should().Contain(u => u.ExternalObjectId == "oid-123");
|
|
||||||
|
|
||||||
// Get
|
|
||||||
var got = await mediator.Send(new GetEmployerUserQuery(employer.Id, user!.Id));
|
|
||||||
got.Should().NotBeNull();
|
|
||||||
got!.Email.Should().Be("hr@example.com");
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
var deleted = await mediator.Send(new DeleteEmployerUserCommand(employer.Id, user.Id));
|
|
||||||
deleted.Should().BeTrue();
|
|
||||||
|
|
||||||
// Delete again -> false or idempotent? we return false when not found
|
|
||||||
var deletedAgain = await mediator.Send(new DeleteEmployerUserCommand(employer.Id, user.Id));
|
|
||||||
deletedAgain.Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MinAttest.Application.Features.Attests.Commands;
|
|
||||||
using MinAttest.Application.Features.Attests.Queries;
|
|
||||||
using MinAttest.Application.Features.Employers.Commands;
|
|
||||||
using MinAttest.Contracts.Attests;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class EmployersAttestsIntegrationTests
|
|
||||||
{
|
|
||||||
private readonly IntegrationTestFixture _fixture;
|
|
||||||
public EmployersAttestsIntegrationTests(IntegrationTestFixture fixture) => _fixture = fixture;
|
|
||||||
[Fact]
|
|
||||||
public async Task Employer_issue_and_list_attests()
|
|
||||||
{
|
|
||||||
var mediator = _fixture.Services.GetRequiredService<IMediator>();
|
|
||||||
|
|
||||||
// Ensure employer & person
|
|
||||||
var employer = await mediator.Send(new UpsertEmployerCommand("555555555", "NewCo AS"));
|
|
||||||
var person = await mediator.Send(new MinAttest.Application.Features.Persons.Commands.UpsertPersonCommand("person-hash-456", "person2@example.com", null));
|
|
||||||
|
|
||||||
var req = new EmployerAttestUploadRequest(
|
|
||||||
PersonId: person.Id,
|
|
||||||
Title: "IssuedAttest",
|
|
||||||
From: new DateOnly(2023,1,1),
|
|
||||||
To: new DateOnly(2023,12,31),
|
|
||||||
Summary: null,
|
|
||||||
BlobPath: string.Empty,
|
|
||||||
BlobHash: null,
|
|
||||||
ContentBase64: Convert.ToBase64String(new byte[]{4,5,6}),
|
|
||||||
ContentType: "application/octet-stream");
|
|
||||||
|
|
||||||
var createdId = await mediator.Send(new EmployerIssueAttestCommand(employer.Id, req));
|
|
||||||
createdId.Should().NotBeNull();
|
|
||||||
|
|
||||||
var list = await mediator.Send(new ListEmployerAttestsQuery(employer.Id, 10));
|
|
||||||
list.Should().Contain(a => a.Id == createdId);
|
|
||||||
|
|
||||||
// Content
|
|
||||||
var content = await mediator.Send(new GetAttestContentQuery(createdId!.Value));
|
|
||||||
content.Should().NotBeNull();
|
|
||||||
content!.Content.Length.Should().BeGreaterThan(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MinAttest.Application.Features.Employers.Commands;
|
|
||||||
using MinAttest.Application.Features.Employers.Queries;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration;
|
|
||||||
|
|
||||||
[Collection("IntegrationCollection")]
|
|
||||||
public class EmployersIntegrationTests
|
|
||||||
{
|
|
||||||
private readonly IntegrationTestFixture _fixture;
|
|
||||||
public EmployersIntegrationTests(IntegrationTestFixture fixture) => _fixture = fixture;
|
|
||||||
[Fact]
|
|
||||||
public async Task Upsert_and_get_and_list_employers_work()
|
|
||||||
{
|
|
||||||
var mediator = _fixture.Services.GetRequiredService<IMediator>();
|
|
||||||
|
|
||||||
var upsert = await mediator.Send(new UpsertEmployerCommand("123456789", "Acme AS"));
|
|
||||||
upsert.OrgNumber.Should().Be("123456789");
|
|
||||||
upsert.Name.Should().Be("Acme AS");
|
|
||||||
|
|
||||||
var list = await mediator.Send(new ListEmployersQuery());
|
|
||||||
list.Should().Contain(e => e.OrgNumber == "123456789");
|
|
||||||
|
|
||||||
var get = await mediator.Send(new GetEmployerQuery(upsert.Id));
|
|
||||||
get.Should().NotBeNull();
|
|
||||||
get!.Name.Should().Be("Acme AS");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration;
|
|
||||||
|
|
||||||
[CollectionDefinition("IntegrationCollection")]
|
|
||||||
public class IntegrationCollection : ICollectionFixture<IntegrationTestFixture>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MinAttest.Application;
|
|
||||||
using MinAttest.Application.Abstractions;
|
|
||||||
using MinAttest.Application.Common.Behaviors;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration;
|
|
||||||
|
|
||||||
public class IntegrationTestFixture : IAsyncLifetime
|
|
||||||
{
|
|
||||||
public IServiceProvider Services { get; private set; } = default!;
|
|
||||||
public string ConnectionString { get; private set; } = string.Empty;
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
|
||||||
{
|
|
||||||
await Server.TestDb.StartAsync();
|
|
||||||
ConnectionString = Server.TestDb.ConnectionString;
|
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
|
|
||||||
services.AddDbContext<AppDbContext>(opt => opt.UseSqlServer(ConnectionString));
|
|
||||||
services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
|
|
||||||
|
|
||||||
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ApplicationAssembly).Assembly));
|
|
||||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
|
|
||||||
|
|
||||||
Services = services.BuildServiceProvider();
|
|
||||||
|
|
||||||
// Migrate and seed
|
|
||||||
using var scope = Services.CreateScope();
|
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
await db.Database.MigrateAsync();
|
|
||||||
|
|
||||||
await SeedAsync(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DisposeAsync()
|
|
||||||
{
|
|
||||||
await Server.TestDb.StopAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task SeedAsync(AppDbContext db)
|
|
||||||
{
|
|
||||||
if (!db.Persons.Any())
|
|
||||||
{
|
|
||||||
var personId = Guid.NewGuid();
|
|
||||||
db.Persons.Add(new MinAttest.Domain.Entities.Person
|
|
||||||
{
|
|
||||||
Id = personId,
|
|
||||||
NationalIdHash = "person-hash-123",
|
|
||||||
Email = "person@example.com"
|
|
||||||
});
|
|
||||||
|
|
||||||
var employerId = Guid.NewGuid();
|
|
||||||
db.Employers.Add(new MinAttest.Domain.Entities.Employer
|
|
||||||
{
|
|
||||||
Id = employerId,
|
|
||||||
OrgNumber = "999999999",
|
|
||||||
Name = "TestBedrift AS"
|
|
||||||
});
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MinAttest.Infrastructure.Data;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration.Server;
|
|
||||||
|
|
||||||
public class ApiWebAppFactory : WebApplicationFactory<Program>
|
|
||||||
{
|
|
||||||
protected override void ConfigureWebHost(Microsoft.AspNetCore.Hosting.IWebHostBuilder builder)
|
|
||||||
{
|
|
||||||
builder.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
|
|
||||||
if (descriptor is not null)
|
|
||||||
{
|
|
||||||
services.Remove(descriptor);
|
|
||||||
}
|
|
||||||
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(TestDb.ConnectionString));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
using Testcontainers.MsSql;
|
|
||||||
|
|
||||||
namespace MinAttest.Tests.Integration.Server;
|
|
||||||
|
|
||||||
public static class TestDb
|
|
||||||
{
|
|
||||||
private static MsSqlContainer? _container;
|
|
||||||
private static readonly object _lock = new();
|
|
||||||
private static bool _started;
|
|
||||||
|
|
||||||
public static string ConnectionString { get; private set; } = string.Empty;
|
|
||||||
|
|
||||||
public static async Task StartAsync()
|
|
||||||
{
|
|
||||||
if (_started) return;
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_container == null)
|
|
||||||
{
|
|
||||||
_container = new MsSqlBuilder()
|
|
||||||
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
|
|
||||||
.WithPassword("Your_password123")
|
|
||||||
.WithPortBinding(14333, 1433)
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await _container!.StartAsync();
|
|
||||||
ConnectionString = _container.GetConnectionString() + ";Database=MinAttest_Integration";
|
|
||||||
_started = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task StopAsync()
|
|
||||||
{
|
|
||||||
if (_container is null) return;
|
|
||||||
await _container.StopAsync();
|
|
||||||
await _container.DisposeAsync();
|
|
||||||
_container = null;
|
|
||||||
_started = false;
|
|
||||||
ConnectionString = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user