Matthew's ATS is a functional, single-tenant, and cloud-native web application serving as an Applicant Tracking System.
As an employer:
- Post new jobs, fill them, or close them!
- Schedule interviews with prospective candidates and download the interview to your calendar!
- View and download every applicant's resume with the click of a button!
As a job seeker:
- View all open, unfilled positions!
- Apply to jobs with your resume and put your best foot forward!
This project consists of two Azure components.
The first is the application itself. It is an Azure Container Instances (ACI) group.
The second is the companion matts.AzFunctions
project for Azure Functions used by the application.
The ACI Group will need a system assigned managed identity with permissions to access the key vault, app config store, storage account, and the blob container (important!).
The key vault is also used in the GH Action pipeline. A service account will need to be created for your Azure Resource Group. Refer to the project's GitHub Actions folder for more details.
For the nitty-gritty on the container setup, see the .azure/template.json
file. Since ARM is being used instead of docker-compose for deployment, ensure what's in the docker-compose file is mirrored in the ARM template.
Each volume for a container will need to be assigned an Azure File Share. It's share per volume, apparently.
These settings need to be in the configuration store. The application will filter by its prefix and the build environment that it was built against.
The service endpoint of the App Config itself will need to be placed within the appropriate appsettings.json
file.
NOTE: This screenshot may not contain everything. Refer to the C# classes within the Configuration
folder as well as the json configuration files.
For HTTPS, currently the application utilizes a self-signed certificate. Place a PFX certificate file within the root of the apphttps
file share. The DnsName of the cert needs to match the deployment url, which will remain the same after the container group is created, even if the group needs to be deleted due to certain prop changes. If you create one first in Azure Portal, be sure to add together the values for memory and cpu of both containers, so you don't have to delete it before a deployment.
After a successful deployment, the application is accessible at the url attached to the deployment.
Make sure you have Powershell 7.
As Administrator, run these powershell commands to setup your environment. Once setup, you can start and stop the container to access the application.
If you can't run as admin, change the certstore location to CurrentUser
.
# Cert Path Setup
$certsFolder = "$(If ($env:HOME -ne $null) {$env:HOME+'/.devcerts'} Else {$env:USERPROFILE+'\.devcerts'})"
New-Item -Force -ItemType "directory" -Path $certsFolder
$certKeyPath = "$certsFolder\matts-localhost.pfx"
# Create a self-signed Cert
$cert = New-SelfSignedCertificate -DnsName "localhost" -FriendlyName "localhost development certificate" -NotAfter (Get-Date).AddYears(15) -CertStoreLocation "cert:\LocalMachine\My"
$password = Get-Credential -Message 'Enter Password' -UserName 'SSL Certificate'
# Export the self-signed cert
$cert | Export-PfxCertificate -FilePath $certKeyPath -Password $password.Password
$rootCert = $(Import-PfxCertificate -FilePath $certKeyPath -CertStoreLocation 'Cert:\LocalMachine\Root' -Password $password.Password)
# Place your cert's password in an env file for the container
"ASPNETCORE_Kestrel__Certificates__Default__Password=" | Out-File -FilePath docker.development.secret.env -NoNewline
ConvertFrom-SecureString -SecureString $password.Password -AsPlainText | Out-File -FilePath docker.development.secret.env -Append
# Setup the user-secrets stuff
dotnet user-secrets set "Neo4J:User" "neo4j"
dotnet user-secrets set "Neo4J:Password" "changeme"
$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($(Get-Random).toString()))
$sha = Get-FileHash -InputStream $stream -Algorithm SHA256
$bytes = [byte[]] -split ($sha.Hash -replace '..', '0x$& ')
$key = [Convert]::ToBase64String($bytes)
dotnet user-secrets set "Jwt:SigningKey" $key
dotnet user-jwts create
# Compose container using the override profile for development
$env:MATTS_DEVCERT_FOLDER = $certsFolder
docker-compose --env-file ./docker.development.env -f docker-compose.yml -f docker-compose.development.yml up -d
This is all that's required once the initial secrets setup & cert generation has been performed.
$certsFolder = "$(If ($env:HOME -ne $null) {$env:HOME+'/.devcerts'} Else {$env:USERPROFILE+'\.devcerts'})"
$env:MATTS_DEVCERT_FOLDER = $certsFolder
docker-compose --env-file ./docker.development.env -f docker-compose.yml -f docker-compose.development.yml up --build matts
You need to have Neo4J setup. Once it's setup, you just start and stop it using Docker Desktop.
First, setup the container...
docker-compose --env-file ./docker.development.env -f docker-compose.yml -f docker-compose.development.yml up neo4j -d
(One time only) Next, you need to setup your secrets:
dotnet user-secrets set "Neo4J:User" "neo4j"
dotnet user-secrets set "Neo4J:Password" "changeme"
$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($(Get-Random).toString()))
$sha = Get-FileHash -InputStream $stream -Algorithm SHA256
$bytes = [byte[]] -split ($sha.Hash -replace '..', '0x$& ')
$key = [Convert]::ToBase64String($bytes)
dotnet user-secrets set "Jwt:SigningKey" $key
dotnet user-jwts create
Finally, all you need to do to run the app is to execute dotnet run
from the project root directory.
You need to setup the connection string. It varies slightly whether using Docker or not.
Be sure to also update the appropriate appsettings.json
too.
In the docker.development.secret.env
file created earlier, add the following line:
ConnectionStrings__AzureAppConfiguration=<YOUR CONNECTION STRING HERE>
ConnectionStrings__<ServiceName from Blob Config Item>=<YOUR CONNECTION STRING HERE>
Use the user secrets tool.
dotnet user-secrets set "ConnectionStrings:AzureAppConfiguration" "<YOUR CONNECTION STRING HERE>"
dotnet user-secrets set "ConnectionStrings:<ServiceName from Blob Config Item>" "<YOUR CONNECTION STRING HERE>"