Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
pull_request:
push:
branches:
- main
- master
- dev
- feature/**
Expand Down Expand Up @@ -81,30 +80,3 @@ jobs:
- name: Test
run: dotnet test ./JobFlow.API/JobFlow.API.csproj -c Release --no-build

api-e2e-playwright:
name: API E2E (Playwright)
runs-on: ubuntu-latest
if: ${{ secrets.JOBFLOW_API_BASE_URL != '' && secrets.JOBFLOW_API_BEARER_TOKEN != '' }}

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

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tests/e2e/package-lock.json

- name: Install E2E dependencies
working-directory: ./tests/e2e
run: npm ci

- name: Run API Playwright tests
working-directory: ./tests/e2e
env:
API_BASE_URL: ${{ secrets.JOBFLOW_API_BASE_URL }}
JOBFLOW_API_BEARER_TOKEN: ${{ secrets.JOBFLOW_API_BEARER_TOKEN }}
JOBFLOW_ORGANIZATION_ID: ${{ secrets.JOBFLOW_ORGANIZATION_ID }}
run: npm run test:e2e
3 changes: 0 additions & 3 deletions .github/workflows/master_jobflow-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
name: Build and deploy ASP.Net Core app to Azure Web App - jobflow-api

on:
push:
branches:
- master
workflow_dispatch:

jobs:
Expand Down
95 changes: 95 additions & 0 deletions .github/workflows/staging_jobflow-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Build and deploy ASP.Net Core app to Azure Web App - jobflow-api-staging

on:
push:
branches:
- master
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v4

- name: Set up .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Build with dotnet
run: dotnet build JobFlow.API/JobFlow.API.csproj --configuration Release

- name: dotnet publish
run: dotnet publish JobFlow.API/JobFlow.API.csproj --configuration Release --output ${{env.DOTNET_ROOT}}/myapp

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: .net-app
path: ${{env.DOTNET_ROOT}}/myapp

deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Staging'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write
contents: read

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: .net-app

- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_15A959992577476DBE8A0461C48B98E2 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_0D7B12FDA6784264AD6F6B11A9FD4D54 }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9763D46E278F4180A2B5D8CC4B8B9E88 }}

- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'jobflow-api-staging'
slot-name: 'Production'
package: .

deploy-prod:
runs-on: ubuntu-latest
needs: deploy
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write
contents: read

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: .net-app

- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_15A959992577476DBE8A0461C48B98E2 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_0D7B12FDA6784264AD6F6B11A9FD4D54 }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9763D46E278F4180A2B5D8CC4B8B9E88 }}

- name: Deploy to Azure Web App (prod)
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'jobflow-api'
slot-name: 'Production'
package: .
37 changes: 27 additions & 10 deletions JobFlow.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,42 @@
// FIREBASE INITIALIZATION
// ============================================================

var firebaseFilePath = Path.Combine(env.ContentRootPath, "job-flow-firebase-adminsdk.json");

if (!System.IO.File.Exists(firebaseFilePath))
throw new InvalidOperationException($"Firebase service account file not found: {firebaseFilePath}");

var firebaseAdminSdkJson = builder.Configuration[ConfigConstants.FIREBASE_ADMIN_SDK];
string firebaseProjectId;
using (var doc = JsonDocument.Parse(System.IO.File.ReadAllText(firebaseFilePath)))
GoogleCredential firebaseCredential;

if (!string.IsNullOrWhiteSpace(firebaseAdminSdkJson))
{
using var doc = JsonDocument.Parse(firebaseAdminSdkJson);
firebaseProjectId = doc.RootElement.GetProperty("project_id").GetString() ?? "";
var credential = CredentialFactory.FromJson<ServiceAccountCredential>(firebaseAdminSdkJson);
firebaseCredential = credential.ToGoogleCredential();
}
else
{
var firebaseFilePath = Path.Combine(env.ContentRootPath, "job-flow-firebase-adminsdk.json");

if (!System.IO.File.Exists(firebaseFilePath))
throw new InvalidOperationException(
$"Firebase admin credentials were not found. Configure '{ConfigConstants.FIREBASE_ADMIN_SDK}' in Key Vault or provide local file: {firebaseFilePath}");

var firebaseJson = System.IO.File.ReadAllText(firebaseFilePath);

using var doc = JsonDocument.Parse(firebaseJson);
firebaseProjectId = doc.RootElement.GetProperty("project_id").GetString() ?? "";
var credential = CredentialFactory.FromFile<ServiceAccountCredential>(firebaseFilePath);
firebaseCredential = credential.ToGoogleCredential();
}

if (string.IsNullOrWhiteSpace(firebaseProjectId))
throw new InvalidOperationException("Firebase project_id is missing in job-flow-firebase-adminsdk.json");
throw new InvalidOperationException("Firebase project_id is missing in configured Firebase admin credentials.");

// Create the Firebase Admin default app instance so FirebaseAuth.DefaultInstance is available.
if (FirebaseApp.DefaultInstance is null)
{
var credential = CredentialFactory.FromFile<ServiceAccountCredential>(firebaseFilePath);
FirebaseApp.Create(new AppOptions
{
Credential = credential.ToGoogleCredential()
Credential = firebaseCredential
});
}

Expand All @@ -119,7 +134,7 @@
})
.AddJwtBearer("ClientPortalJwt", options =>
{
var signingKey = builder.Configuration["Auth:ClientPortal:SigningKey"];
var signingKey = builder.Configuration["Auth-ClientPortal-SigningKey"];
if (string.IsNullOrWhiteSpace(signingKey))
throw new InvalidOperationException("Missing configuration: Auth:ClientPortal:SigningKey");

Expand Down Expand Up @@ -259,6 +274,8 @@
return host == "localhost"
|| host == "gojobflow.com"
|| host == "www.gojobflow.com"
|| host == "jobflow-ui-web-staging.web.app"
|| host == "jobflow-ui-web-staging.firebaseapp.com"
|| host.EndsWith(".gojobflow.app")
|| host.EndsWith(".gojobflow.com");
})
Expand Down
16 changes: 16 additions & 0 deletions JobFlow.API/appsettings.Staging.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"KeyVaultUri": "https://jobflow-staging.vault.azure.net/",
"Frontend": {
"BaseUrl": "https://staging.gojobflow.com"
},
"Backend": {
"BaseUrl": "https://api.staging.gojobflow.com"
}
}
12 changes: 6 additions & 6 deletions JobFlow.API/job-flow-firebase-adminsdk.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"type": "service_account",
"project_id": "jobflow-ui-web",
"private_key_id": "e9c3df7ce12c0500cc61cc285934a73ed8565480",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC66EWCPmbUwl25\nzROu+2tcDnbQlzMJ386pcx+YRGee5w/Q1al1zEls4/f8C7gOkTG4Ki9P9GCBOWGZ\nI41l+J3KiMsFVPb8amRtjyyLHjwD4tcbyl3DE5RGD2fS3LMNeWMxtwehKtV+6asc\nBEtMeswbrBC3vXQCe2AjDCYYsyxzOU+Qp2JQT2GNr3brZaow1nPqu6IAz7f92DBC\n44ezV3IJC0vYhIQq4bBngB97cC2hYI+f7ll3blYljccsDKQXxaNbQUJ1K+QJIlhg\nSSJ11SKcMk7a2l7KVknUligLrdllS/m652vV609nITmxlAGKUmjgle8YNLs0srFW\nSbjLZk53AgMBAAECggEAQPxScqMEuPPth5UUw3HaVbMXv5XaopPE9Ki48wXRq2+2\nUYuAdJs3altnFSTz9WipS1mrgpa62SNc2lSArNRA9LMUN8HfcEsDqQ4vVB2Ki2Vb\nGmgFqraLhsKDfE7NGKG8igQT7IcKnSpcmoypq6lEf1iXpXMDO3uvJPBr7ImbqmHK\nOJi+xO7z85fT//7gRh5I0I1RROJeH8go6OssnIVrgxSs8EI7Ai7HGMv1yxPlmGv1\n5naVvHpFRq18KrzU8LmirIwMByQC9IKJKPZtMmgNdPYc6YYKgfYIHhSXmJiBNPq3\nQQU/0bc3MTzUDNnnbIPwtj3San5jhRoiNVVX/rYEFQKBgQDc0wOaFJsoc82yn9RH\nwjNNHjyXllf6h4iNgcAYT8K7NVLg0KDppI64dFMw0ZKLd506QLevNFG/Kjcf9JgQ\ndQ2IkpGb4lK3vh8+lSdVK8Ngne35g6mPCTgNYUDbApb7KDf2rnwLcRh4WQTHP7w5\nljAXuzIN2wAyI+4onz84R8HJlQKBgQDYriig7qGdMNNfwfSHJEYmukWk/f61VQfX\n/rK48MXfDv4fq0YUHQvBDmQYAFN3hhbfCwa8Sw33Bbu49Zjydi8R+r7eBT7Vvv/e\nbeID1fYZdJQ5kotS3/IcYqck1ecd/X2SG9sc/0RVkeII1KcdYiVeyeCZr+zXQ8+a\nNTTUrERs2wKBgQDECCNjbjWLRLpvfwmRJmoqZNQ/cbzqb9UeYffo3S2uyZiocSzY\nHTiBsOqFJRal7urJ4tftllGXld9X4+f2fCMmgY73xoPOD95mzTwclPwd0jWHUoV8\nsB9taU+M3RCxJ7P+rkj6U0z40XW3d/IdYSGSf6DgwfC7kkADGdOin7j9vQKBgGR5\nFWPSY2RVQJ5VfIKxwkmw9BxWnqYMwK9abhstokMVW6bpr3wiH9IsTyOF+y4gIjjY\njw3+q4IQyYQxdfNv89GdeKXQvts0TscgIr5ul0gkc5ripfIO3+Bjqmd9PEb+xRxc\nCFVA1LntBGfd24PXf8adS6VYGzWSPxCdfVrkanIjAoGAHhrAzZqNJSQBISFWhk3G\n97EV8+AUSelIObsXQhQoEtlpEggffdKx0nC+i1nggkiB/WMa8ZxTCNB+ri3IfPeV\nrpjnDROjcWyZlHTp/4LPEDVIh4QvZWdial+A/v09/xErhDILyAlIrWEVCX9IcJm2\nJJ5pfEGgcIVSEGMV4cKPxAc=\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@jobflow-ui-web.iam.gserviceaccount.com",
"client_id": "115912080472474571179",
"project_id": "jobflow-ui-web-staging",
"private_key_id": "9708ddf5828958edeb9a3fe8b2d2bb2c8be9933f",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJlI2Plx+znQi7\nrsq0ZOZeoLTqo5pbczPj42DwLf+YNEXvyRVrVruyqXhffKUxDGHmR3zxslUsT1f/\nKjWh09y2fnRXH1su2f90Kax4Lff1chhqMH6/J/CNK6KulBnjCcWks1F5GBCwsEw8\nCA44cJmVYnGAyqrQvNkELBYgWiIblE8kWFlPviZyyB9krEEJo7U8T9cA/sc3NI1j\nRKfx7S97koNgIsMht+TY92hDwClGVGWL7v1cgkeKmll6NhEEJpUr8amtrTplSjJB\nykq1FjmPmv6cao2JOGPNQRv2rt0xHKNCNQjPMgQetlX3HGauNDQk+PqPLxeq1b4S\npMqPMFPrAgMBAAECggEAE2uU5yBbmlhxfBy/BE8eOeqd/hGsms0yqBdYAlD5frE2\niv836Y3tv6zVCc9T3jGqXYmbRMZ2BIf7sHu2trRXCgaNGyGhuBX3fDpRlohzNY8m\n7AASY0SBR9B24qkmWfmvNAq0jt0zKm/pqvTpuHqcnp2L1X5w+Mg2LiaN1n291c3c\nTcak9qN1Fnfw/uhVuRr4Dws2+xOEVTwLEfJpzPa/hJnGhNUAdCR61GRgG/PQ1Xqw\n1BnP8w8dRjah5aeefUziUyiao/g3mXh2XtyXuA5OBEfGAFf01yWBkbB2Z6CEuP+U\nPtLcVSnmhQW0+MCeFovltokkjShpdis7KdjmBHg8EQKBgQDy0TRPM/6QTh7B9dOg\nRUNh6zpnOFC0Or0NXbFPJObzqnljeKjS1MPFJ5HtzEDerxfsyy8HVux91k8BkdrG\npFSrCtRyr2VC1cjK21sA2S8uAvQ+dVZIDY8ozhpKGuZMwiK03BF/n13Wk+MMRuAq\n9quOpHj6hKkCOQh3IRkOT/xP+wKBgQDUhjeZlQLGf0fMq4XzGs45ppIGw9dyu1yj\nvIF7rpzqnAgtqI4VzLkhcskEvDm+WdCgnjSvEKGbZq4Dm5v9Obm5T3RGZdIhkLSx\ns2taL6tbznq4SDGBGgMzJl8rAHXX2P+SHRyCIzrYnz6ZfAB2KffoTBmsHMjPikbq\nMsnWPEKY0QKBgAtjqLJ2W+Bk6ahrYWvJE+oJ4Ilq6M4rWya/WEvADV0sh9kUlcad\n2DjtLDkdNYW8bMDcnu4XM6yLWtVWBA8BMj97mI9wjq1d3bc2JsSZa08bMF2ln1Bt\n4mMll7IWJOtAx+P31pJH5VzlPucag/U/8LgWGt6VTmAeULlVwhkbw1f1AoGBALUS\nfgDO4wR4oZYSdhhBOIAKGdTFu6U3WaDwFWppxaxmsNkmCZktSnbjM75jGNfD8mtH\nICAgjXC4NX9Bb9B7BHCM78ajLjwG7M2Szt6SSu/3pruoVvVmUl+cS+15gO4dJvM4\n9ncyyQqT82QWMNZ8v4oefKkWBUo+yFj2WN29jghhAoGAVKAE8JxD20qiAbWk1RKm\n6T2TyDiZU/nG4MGPkRuR0OmtEszoLkk5o968IVEdK4v2y9XLCV+ke2PTgfVNEcKg\nLRBFitx6yVlzUddDTkdSnyYtl6S4LRX1XKk2Rur8fCgHd59bEb1CLxiFm9e7B5zW\ntHB937uJRztg2Dy8GnipZQw=\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@jobflow-ui-web-staging.iam.gserviceaccount.com",
"client_id": "115002767484169273887",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40jobflow-ui-web.iam.gserviceaccount.com",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40jobflow-ui-web-staging.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
Loading