diff --git a/.github/workflows/docker-build-dev.yml b/.github/workflows/docker-build-dev.yml index be32d7f22a..d9cab254ff 100644 --- a/.github/workflows/docker-build-dev.yml +++ b/.github/workflows/docker-build-dev.yml @@ -24,7 +24,9 @@ env: GH_TOKEN: ${{secrets.GH_API_TOKEN}} OC_CLUSTER: ${{ vars.OPENSHIFT_CLUSTER }} OC_REGISTRY: ${{ vars.OPENSHIFT_REGISTRY }} - OC_AUTH_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} + OC_TOOLS_TOKEN: ${{ secrets.TOOLS_OPENSHIFT_TOKEN }} + OC_TOOLS_PROJECT: ${{ vars.TOOLS_OPENSHIFT_NAMESPACE }} + OC_TARGET_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} OC_TARGET_PROJECT: ${{ vars.OPENSHIFT_NAMESPACE }} JFROG_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} JFROG_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -47,7 +49,7 @@ jobs: run: | echo "Target: $TARGET_ENV" echo "BaseRef: $GITHUB_REF_NAME" - echo "Environment: $TARGET_ENV OC_TARGET_PROJECT=$OC_TARGET_PROJECT" + echo "Environment: $TARGET_ENV OC_TOOLS_PROJECT=$OC_TOOLS_PROJECT OC_TARGET_PROJECT=$OC_TARGET_PROJECT" echo "Environment: $TARGET_ENV JFROG_REPO_PATH=$JFROG_REPO_PATH" echo "..." env | sort @@ -124,18 +126,15 @@ jobs: docker build --build-arg UNITY_BUILD_VERSION=${{env.UGM_BUILD_VERSION}} --build-arg UNITY_BUILD_REVISION=${{env.UGM_BUILD_REVISION}} -t unity-grantmanager-web -f src/Unity.GrantManager.Web/Dockerfile . docker build -t unity-grantmanager-dbmigrator -f src/Unity.GrantManager.DbMigrator/Dockerfile . working-directory: ./applications/Unity.GrantManager - - name: Connect to JFrog Artifactory non-interactive login using --password-stdin - run: | - echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE - name: Push application images to Artifactory container registry + continue-on-error: true run: | + echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE docker tag unity-grantmanager-dbmigrator $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:latest docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:latest docker tag unity-grantmanager-web $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:latest docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:latest - - name: Disconnect docker from JFrog Artifactory - run: | - docker logout + docker logout $JFROG_SERVICE - name: Install OpenShift CLI run: | curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/oc/latest/linux/oc.tar.gz @@ -143,17 +142,30 @@ jobs: sudo mv oc /usr/local/bin - name: Verify OpenShift CLI installation run: oc version - - name: Connect to OpenShift API non-interactive login using current session token + - name: Push application images into ce395f-tools run: | - oc login --token=$OC_AUTH_TOKEN --server=$OC_CLUSTER + oc login --token=$OC_TOOLS_TOKEN --server=$OC_CLUSTER oc registry login docker login -u unused -p $(oc whoami -t) $OC_REGISTRY - - name: Push application images to OpenShift container registry + docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:latest + docker push $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:latest + docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:latest + docker push $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:latest + docker logout $OC_REGISTRY + - name: Promote dbMigrator into ce395f-dev and run migrations run: | - docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-dbmigrator - docker push $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-dbmigrator - docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-web - docker push $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-web - - name: Disconnect docker from OpenShift container registry + oc login --token=$OC_TARGET_TOKEN --server=$OC_CLUSTER + oc tag $OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:latest $OC_TARGET_PROJECT/$TARGET_ENV-unity-dbmigrator:latest + oc -n $OC_TARGET_PROJECT delete jobs $TARGET_ENV-unity-dbmigrator --ignore-not-found=true + oc -n $OC_TARGET_PROJECT process unity-grantmanager-dbmigrator-job \ + -p APPLICATION_GROUP=$TARGET_ENV-unity-grantmanager \ + -p DATABASE_SERVICE_NAME=$TARGET_ENV-unity-data-postgres \ + -p APPLICATION_NAME=$TARGET_ENV-unity-dbmigrator \ + -p IMAGEPULL_NAMESPACE=$OC_TARGET_PROJECT \ + -p IMAGESTREAM_NAME=$TARGET_ENV-unity-dbmigrator \ + -p IMAGESTREAM_TAG=latest | oc -n $OC_TARGET_PROJECT create -f - + oc -n $OC_TARGET_PROJECT wait jobs/$TARGET_ENV-unity-dbmigrator --for condition=complete --timeout=300s + - name: Promote grantmanager-web into ce395f-dev and wait run: | - docker logout + oc tag $OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:latest $OC_TARGET_PROJECT/$TARGET_ENV-unity-grantmanager:latest + oc -n $OC_TARGET_PROJECT rollout status deployment/$TARGET_ENV-unity-grantmanager-web --timeout=180s diff --git a/.github/workflows/docker-build-dev2.yml b/.github/workflows/docker-build-dev2.yml new file mode 100644 index 0000000000..8b5911c07f --- /dev/null +++ b/.github/workflows/docker-build-dev2.yml @@ -0,0 +1,120 @@ +name: Dev2 - Build & Push docker images + +on: + push: + branches: [ "dev2" ] + paths-ignore: + - '.github/**' + - '.gitignore' + - 'database/**' + - 'documentation/**' + - '**/docs/**' + - '**/README*' + - 'CODE_OF_CONDUCT.md' + - 'COMPLIANCE.yaml' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'README.md' + - 'SECURITY.md' + # Allow manual workflow triggering + workflow_dispatch: + +env: + TARGET_ENV: dev2 + GH_TOKEN: ${{secrets.GH_API_TOKEN}} + OC_CLUSTER: ${{ vars.OPENSHIFT_CLUSTER }} + OC_REGISTRY: ${{ vars.OPENSHIFT_REGISTRY }} + OC_TARGET_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} + OC_TARGET_PROJECT: ${{ vars.OPENSHIFT_NAMESPACE }} + JFROG_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + JFROG_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + JFROG_REPO_PATH: ${{ vars.ARTIFACTORY_REPO }} + JFROG_SERVICE: ${{ vars.ARTIFACTORY_SERVICE }} + +jobs: + Setup: + runs-on: ubuntu-latest + environment: dev2 + permissions: + contents: read + steps: + - name: Get variables + run: | + echo "Target: $TARGET_ENV" + echo "BaseRef: $GITHUB_REF_NAME" + echo "Environment: $TARGET_ENV OC_TARGET_PROJECT=$OC_TARGET_PROJECT" + echo "Environment: $TARGET_ENV JFROG_REPO_PATH=$JFROG_REPO_PATH" + echo "..." + env | sort + Branch: + needs: [Setup] + runs-on: ubuntu-latest + environment: dev2 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: '1' + - name: Get short commitId + id: get_commit + run: | + echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + outputs: + SHA_SHORT: ${{steps.get_commit.outputs.SHA_SHORT}} + Build: + needs: [Setup,Branch] + runs-on: ubuntu-latest + environment: dev2 + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Build Docker images + run: | + rm -f ./docker-compose.override.yml + docker build --build-arg UNITY_BUILD_VERSION=dev2 --build-arg UNITY_BUILD_REVISION=${{needs.Branch.outputs.SHA_SHORT}} -t unity-grantmanager-web -f src/Unity.GrantManager.Web/Dockerfile . + docker build -t unity-grantmanager-dbmigrator -f src/Unity.GrantManager.DbMigrator/Dockerfile . + working-directory: ./applications/Unity.GrantManager + - name: Push application images to Artifactory container registry + continue-on-error: true + run: | + echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE + docker tag unity-grantmanager-dbmigrator $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:latest + docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:latest + docker tag unity-grantmanager-web $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:latest + docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:latest + docker logout $JFROG_SERVICE + - name: Install OpenShift CLI + run: | + curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/oc/latest/linux/oc.tar.gz + tar -xvf oc.tar.gz + sudo mv oc /usr/local/bin + - name: Verify OpenShift CLI installation + run: oc version + - name: Push application images into ce395f-dev (no ce395f-tools hop for dev2) + run: | + oc login --token=$OC_TARGET_TOKEN --server=$OC_CLUSTER + oc registry login + docker login -u unused -p $(oc whoami -t) $OC_REGISTRY + docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TARGET_PROJECT/$TARGET_ENV-unity-dbmigrator:latest + docker push $OC_REGISTRY/$OC_TARGET_PROJECT/$TARGET_ENV-unity-dbmigrator:latest + docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TARGET_PROJECT/$TARGET_ENV-unity-grantmanager:latest + docker push $OC_REGISTRY/$OC_TARGET_PROJECT/$TARGET_ENV-unity-grantmanager:latest + docker logout $OC_REGISTRY + - name: Run dbMigrator Job + run: | + oc -n $OC_TARGET_PROJECT delete jobs $TARGET_ENV-unity-dbmigrator --ignore-not-found=true + oc -n $OC_TARGET_PROJECT process unity-grantmanager-dbmigrator-job \ + -p APPLICATION_GROUP=$TARGET_ENV-unity-grantmanager \ + -p DATABASE_SERVICE_NAME=$TARGET_ENV-unity-data-postgres \ + -p APPLICATION_NAME=$TARGET_ENV-unity-dbmigrator \ + -p IMAGEPULL_NAMESPACE=$OC_TARGET_PROJECT \ + -p IMAGESTREAM_NAME=$TARGET_ENV-unity-dbmigrator \ + -p IMAGESTREAM_TAG=latest | oc -n $OC_TARGET_PROJECT create -f - + oc -n $OC_TARGET_PROJECT wait jobs/$TARGET_ENV-unity-dbmigrator --for condition=complete --timeout=300s + - name: Roll out grantmanager-web and wait + run: | + oc -n $OC_TARGET_PROJECT rollout restart deployment/$TARGET_ENV-unity-grantmanager-web + oc -n $OC_TARGET_PROJECT rollout status deployment/$TARGET_ENV-unity-grantmanager-web --timeout=180s diff --git a/.github/workflows/docker-build-main.yml b/.github/workflows/docker-build-main.yml index b146da454a..f967c20619 100644 --- a/.github/workflows/docker-build-main.yml +++ b/.github/workflows/docker-build-main.yml @@ -24,8 +24,8 @@ env: GH_TOKEN: ${{secrets.GH_API_TOKEN}} OC_CLUSTER: ${{ vars.OPENSHIFT_CLUSTER }} OC_REGISTRY: ${{ vars.OPENSHIFT_REGISTRY }} - OC_AUTH_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} - OC_TARGET_PROJECT: ${{ vars.OPENSHIFT_NAMESPACE }} + OC_TOOLS_TOKEN: ${{ secrets.TOOLS_OPENSHIFT_TOKEN }} + OC_TOOLS_PROJECT: ${{ vars.TOOLS_OPENSHIFT_NAMESPACE }} JFROG_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} JFROG_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} JFROG_REPO_PATH: ${{ vars.ARTIFACTORY_REPO }} @@ -47,7 +47,7 @@ jobs: run: | echo "Target: $TARGET_ENV" echo "BaseRef: $GITHUB_REF_NAME" - echo "Environment: $TARGET_ENV OC_TARGET_PROJECT=$OC_TARGET_PROJECT" + echo "Environment: $TARGET_ENV OC_TOOLS_PROJECT=$OC_TOOLS_PROJECT" echo "Environment: $TARGET_ENV JFROG_REPO_PATH=$JFROG_REPO_PATH" echo "..." env | sort @@ -181,18 +181,15 @@ jobs: docker build --build-arg UNITY_BUILD_VERSION=${{env.UGM_BUILD_VERSION}} --build-arg UNITY_BUILD_REVISION=${{env.UGM_BUILD_REVISION}} -t unity-grantmanager-web -f src/Unity.GrantManager.Web/Dockerfile . docker build -t unity-grantmanager-dbmigrator -f src/Unity.GrantManager.DbMigrator/Dockerfile . working-directory: ./applications/Unity.GrantManager - - name: Connect to JFrog Artifactory non-interactive login using --password-stdin - run: | - echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE - name: Push application images to Artifactory container registry + continue-on-error: true run: | + echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE docker tag unity-grantmanager-dbmigrator $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:stable docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:stable docker tag unity-grantmanager-web $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:stable docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:stable - - name: Disconnect docker from JFrog Artifactory - run: | - docker logout + docker logout $JFROG_SERVICE - name: Install OpenShift CLI run: | curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/oc/latest/linux/oc.tar.gz @@ -200,17 +197,13 @@ jobs: sudo mv oc /usr/local/bin - name: Verify OpenShift CLI installation run: oc version - - name: Connect to OpenShift API non-interactive login using current session token + - name: Push application images into ce395f-tools run: | - oc login --token=$OC_AUTH_TOKEN --server=$OC_CLUSTER + oc login --token=$OC_TOOLS_TOKEN --server=$OC_CLUSTER oc registry login docker login -u unused -p $(oc whoami -t) $OC_REGISTRY - - name: Push application images to OpenShift container registry - run: | - docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-dbmigrator:stable - docker push $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-dbmigrator:stable - docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-web:stable - docker push $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-web:stable - - name: Disconnect docker from OpenShift container registry - run: | - docker logout + docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:stable + docker push $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:stable + docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:stable + docker push $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:stable + docker logout $OC_REGISTRY diff --git a/.github/workflows/docker-build-test.yml b/.github/workflows/docker-build-test.yml index 9728ee15d8..9ea19ba14e 100644 --- a/.github/workflows/docker-build-test.yml +++ b/.github/workflows/docker-build-test.yml @@ -24,7 +24,9 @@ env: GH_TOKEN: ${{secrets.GH_API_TOKEN}} OC_CLUSTER: ${{ vars.OPENSHIFT_CLUSTER }} OC_REGISTRY: ${{ vars.OPENSHIFT_REGISTRY }} - OC_AUTH_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} + OC_TOOLS_TOKEN: ${{ secrets.TOOLS_OPENSHIFT_TOKEN }} + OC_TOOLS_PROJECT: ${{ vars.TOOLS_OPENSHIFT_NAMESPACE }} + OC_TARGET_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} OC_TARGET_PROJECT: ${{ vars.OPENSHIFT_NAMESPACE }} JFROG_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} JFROG_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} @@ -47,7 +49,7 @@ jobs: run: | echo "Target: $TARGET_ENV" echo "BaseRef: $GITHUB_REF_NAME" - echo "Environment: $TARGET_ENV OC_TARGET_PROJECT=$OC_TARGET_PROJECT" + echo "Environment: $TARGET_ENV OC_TOOLS_PROJECT=$OC_TOOLS_PROJECT OC_TARGET_PROJECT=$OC_TARGET_PROJECT" echo "Environment: $TARGET_ENV JFROG_REPO_PATH=$JFROG_REPO_PATH" echo "..." env | sort @@ -160,18 +162,15 @@ jobs: docker build --build-arg UNITY_BUILD_VERSION=${{env.UGM_BUILD_VERSION}} --build-arg UNITY_BUILD_REVISION=${{env.UGM_BUILD_REVISION}} -t unity-grantmanager-web -f src/Unity.GrantManager.Web/Dockerfile . docker build -t unity-grantmanager-dbmigrator -f src/Unity.GrantManager.DbMigrator/Dockerfile . working-directory: ./applications/Unity.GrantManager - - name: Connect to JFrog Artifactory non-interactive login using --password-stdin - run: | - echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE - name: Push application images to Artifactory container registry + continue-on-error: true run: | + echo "$JFROG_PASSWORD" | docker login -u "$JFROG_USERNAME" --password-stdin $JFROG_SERVICE docker tag unity-grantmanager-dbmigrator $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:latest docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-dbmigrator:latest docker tag unity-grantmanager-web $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:latest docker push $JFROG_SERVICE/$JFROG_REPO_PATH/unity-grantmanager-web:latest - - name: Disconnect docker from JFrog Artifactory - run: | - docker logout + docker logout $JFROG_SERVICE - name: Install OpenShift CLI run: | curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/oc/latest/linux/oc.tar.gz @@ -179,17 +178,30 @@ jobs: sudo mv oc /usr/local/bin - name: Verify OpenShift CLI installation run: oc version - - name: Connect to OpenShift API non-interactive login using current session token + - name: Push application images into ce395f-tools run: | - oc login --token=$OC_AUTH_TOKEN --server=$OC_CLUSTER + oc login --token=$OC_TOOLS_TOKEN --server=$OC_CLUSTER oc registry login docker login -u unused -p $(oc whoami -t) $OC_REGISTRY - - name: Push application images to OpenShift container registry + docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:latest + docker push $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:latest + docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:latest + docker push $OC_REGISTRY/$OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:latest + docker logout $OC_REGISTRY + - name: Promote dbMigrator into ce395f-test and run migrations run: | - docker tag unity-grantmanager-dbmigrator $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-dbmigrator - docker push $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-dbmigrator - docker tag unity-grantmanager-web $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-web - docker push $OC_REGISTRY/$OC_TARGET_PROJECT/unity-grantmanager-web - - name: Disconnect docker from OpenShift container registry + oc login --token=$OC_TARGET_TOKEN --server=$OC_CLUSTER + oc tag $OC_TOOLS_PROJECT/$TARGET_ENV-unity-dbmigrator-build:latest $OC_TARGET_PROJECT/$TARGET_ENV-unity-dbmigrator:latest + oc -n $OC_TARGET_PROJECT delete jobs $TARGET_ENV-unity-dbmigrator --ignore-not-found=true + oc -n $OC_TARGET_PROJECT process unity-grantmanager-dbmigrator-job \ + -p APPLICATION_GROUP=$TARGET_ENV-unity-grantmanager \ + -p DATABASE_SERVICE_NAME=$TARGET_ENV-unity-data-postgres \ + -p APPLICATION_NAME=$TARGET_ENV-unity-dbmigrator \ + -p IMAGEPULL_NAMESPACE=$OC_TARGET_PROJECT \ + -p IMAGESTREAM_NAME=$TARGET_ENV-unity-dbmigrator \ + -p IMAGESTREAM_TAG=latest | oc -n $OC_TARGET_PROJECT create -f - + oc -n $OC_TARGET_PROJECT wait jobs/$TARGET_ENV-unity-dbmigrator --for condition=complete --timeout=300s + - name: Promote grantmanager-web into ce395f-test and wait run: | - docker logout + oc tag $OC_TOOLS_PROJECT/$TARGET_ENV-unity-grantmanager-build:latest $OC_TARGET_PROJECT/$TARGET_ENV-unity-grantmanager:latest + oc -n $OC_TARGET_PROJECT rollout status deployment/$TARGET_ENV-unity-grantmanager-web --timeout=180s diff --git a/applications/Unity.GrantManager/.env.example b/applications/Unity.GrantManager/.env.example index c937e86596..42e07659d4 100644 --- a/applications/Unity.GrantManager/.env.example +++ b/applications/Unity.GrantManager/.env.example @@ -54,7 +54,7 @@ AuthServer__OidcSignoutCallback="http://localhost:44342/signout-callback-oidc" ##S3__SecretAccessKey="******************" ##S3__ApplicationS3Folder="Unity/Application" ##S3__AssessmentS3Folder="Unity/Adjudication" -##S3__DisallowedFileTypes="[ "exe" , "sh" , "ksh" , "bat" , "cmd" ]" +##S3__AllowedFileTypes=["pdf","doc","docx","xls","xlsx","ppt","pptx","jpg","jpeg","png","gif","txt","csv","zip","odt","ods","odp","rtf","bmp","tif","tiff","webp","heic","heif","eml","msg"] ##S3__MaxFileSize="25" ##S3__EmailAttachmentMaxFileSize="20" ##S3__EmailAttachmentsTotalMaxFileSize="25" diff --git a/applications/Unity.GrantManager/Directory.Build.props b/applications/Unity.GrantManager/Directory.Build.props index 4239024ce7..0e9f5007d6 100644 --- a/applications/Unity.GrantManager/Directory.Build.props +++ b/applications/Unity.GrantManager/Directory.Build.props @@ -10,7 +10,7 @@ - + diff --git a/applications/Unity.GrantManager/common.props b/applications/Unity.GrantManager/common.props index bdf9a7442f..e28f59fbc5 100644 --- a/applications/Unity.GrantManager/common.props +++ b/applications/Unity.GrantManager/common.props @@ -19,7 +19,7 @@ - + \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/AI/Responses/FormWorksheetResponse.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/AI/Responses/FormWorksheetResponse.cs index 4fea2802d2..1ee1d837bc 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/AI/Responses/FormWorksheetResponse.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/AI/Responses/FormWorksheetResponse.cs @@ -1,69 +1,6 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - namespace Unity.AI.Responses; public class FormWorksheetResponse { public string Worksheet { get; set; } = string.Empty; - - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; - - [JsonPropertyName("version")] - public uint Version { get; set; } = 1; - - [JsonPropertyName("published")] - public bool Published { get; set; } - - [JsonPropertyName("reportColumns")] - public string ReportColumns { get; set; } = string.Empty; - - [JsonPropertyName("reportKeys")] - public string ReportKeys { get; set; } = string.Empty; - - [JsonPropertyName("reportViewName")] - public string ReportViewName { get; set; } = string.Empty; - - [JsonPropertyName("sections")] - public List Sections { get; set; } = []; -} - -public class FormWorksheetSectionResponse -{ - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - [JsonPropertyName("order")] - public uint Order { get; set; } - - [JsonPropertyName("fields")] - public List Fields { get; set; } = []; -} - -public class FormWorksheetFieldResponse -{ - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; - - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; - - [JsonPropertyName("type")] - public int Type { get; set; } - - [JsonPropertyName("order")] - public uint Order { get; set; } - - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } = true; - - [JsonPropertyName("definition")] - public string? Definition { get; set; } } diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Unity.AI.Application.Contracts.csproj b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Unity.AI.Application.Contracts.csproj index d086c0cf1d..2222c49ef6 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Unity.AI.Application.Contracts.csproj +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application.Contracts/Unity.AI.Application.Contracts.csproj @@ -6,15 +6,15 @@ Unity.AI - - - - + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Runtime/AIProviderPayloadValidator.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Runtime/AIProviderPayloadValidator.cs index 0f17bbd1cd..23d9ed10da 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Runtime/AIProviderPayloadValidator.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/AI/Runtime/AIProviderPayloadValidator.cs @@ -129,18 +129,10 @@ public static AIResponseValidationResult ValidateFormWorksheetJson(string respon return AIResponseValidationResult.Invalid("Form worksheet response was not valid JSON."); } - if (!root.TryGetProperty("title", out var title) - || title.ValueKind != JsonValueKind.String - || string.IsNullOrWhiteSpace(title.GetString())) + if (!root.TryGetProperty("fields", out var fields) + || fields.ValueKind != JsonValueKind.Array) { - return AIResponseValidationResult.Invalid("Form worksheet response is missing a non-empty 'title'."); - } - - if (!root.TryGetProperty("sections", out var sections) - || sections.ValueKind != JsonValueKind.Array - || sections.GetArrayLength() == 0) - { - return AIResponseValidationResult.Invalid("Form worksheet response must include at least one section."); + return AIResponseValidationResult.Invalid("Form worksheet response must include a 'fields' array."); } return AIResponseValidationResult.Success(); diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs index 044da04dc5..7df54a2a2a 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/DataSeed/AIPromptDataSeeder.cs @@ -848,8 +848,8 @@ Return only valid JSON in the exact mapping shape requested. // ── v2/form-worksheet.system.txt ─────────────────────────────────────── private const string FormWorksheetSystemV2 = """ - You are a worksheet definition generator for Unity Grant Manager. - Generate a recommended worksheet definition JSON that can be used to create a Flex worksheet. + You are a custom-field suggestion generator for Unity Grant Manager. + Recommend only the additional fields needed for a Flex worksheet. Return only valid JSON. """; @@ -860,47 +860,30 @@ Return only valid JSON. OUTPUT { - "Name": "", - "Title": "", - "Version": , - "Published": true, - "Sections": [ - { - "Name": "", - "Order": 1, - "Fields": [ - { - "Name": "", - "Key": "", - "Label": "", - "Type": , - "Definition": "" - } - ] - } - ], - "ReportColumns": "", - "ReportKeys": "", - "ReportViewName": "" + "fields": [ + { "key": "", "label": "", "type": "Text" } + ] } Rules: - - Return one worksheet definition JSON object only. + - Return one field-suggestion JSON object only. - chefsFields contains the available CHEFS source fields. - unityCoreFields contains existing Unity core fields. Do not create a custom field when one of these already fits. - - existingMapping contains any current confirmed Unity-to-CHEFS mappings. Do not duplicate those mappings with a custom field. - - existingWorksheets contains the previous AI worksheet definition, if one exists. Refine it rather than duplicating its custom fields. + - existingMapping contains the current saved Unity-to-CHEFS mappings. Do not duplicate those mappings with a custom field. + - existingCustomFields is a flattened list of fields from worksheets currently linked to this form version. Each entry includes its worksheet name, field name, label, and type. Do not create duplicate custom fields. - formSchema contains detailed CHEFS control configuration when labels and types need more context. - Use the provided form context to decide which custom fields are genuinely needed. - Prefer existing Unity core fields when they already satisfy the need. - Only create additional worksheet custom fields when the form genuinely needs them. - - Keep the worksheet structure valid for Flex. + - Do not include a worksheet title, sections, order, publish state, reporting fields, enabled flag, or field definition. + - Each key and label must be non-empty. Do not repeat a key. + - type must be one of: Text, TextArea, Numeric, Currency, Date, DateTime, Email, Phone, YesNo, Checkbox. Use the type name, never a number. - Return valid plain JSON only. """; private const string FormWorksheetMetadataV2 = """ { - "DATA": "Serialized JSON payload containing form metadata, CHEFS fields, Unity core fields, the current mapping, form schema, and the existing AI worksheet." + "DATA": "Serialized JSON payload containing form metadata, CHEFS fields, Unity core fields, the current mapping, form schema, and custom fields from worksheets currently linked to the form version." } """; diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj index 2be3ad05e5..91c741e446 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Unity.AI.Application.csproj @@ -6,22 +6,21 @@ Unity.AI - + - + - - - + + + - - - - - - - - + + + + + + + @@ -29,8 +28,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Unity.AI.Shared.csproj b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Unity.AI.Shared.csproj index f094575dec..30508215fd 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Unity.AI.Shared.csproj +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Domain.Shared/Unity.AI.Shared.csproj @@ -10,12 +10,12 @@ - - + + - + @@ -24,8 +24,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj index b37df03bc2..4b5d57bd20 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Web/Unity.AI.Web.csproj @@ -12,16 +12,14 @@ - + - - - - + + - + @@ -40,8 +38,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Unity.Flex.Application.Contracts.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Unity.Flex.Application.Contracts.csproj index 259d86d1c9..0b0a20199a 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Unity.Flex.Application.Contracts.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application.Contracts/Unity.Flex.Application.Contracts.csproj @@ -9,13 +9,13 @@ - - - - - - - + + + + + + + @@ -23,8 +23,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj index 331e21863e..43505d8f82 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Application/Unity.Flex.Application.csproj @@ -9,24 +9,24 @@ - - - - - + + + + + - - - - - + + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Unity.Flex.Shared.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Unity.Flex.Shared.csproj index 9963d2d2bd..7da95766a1 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Unity.Flex.Shared.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Unity.Flex.Shared.csproj @@ -10,15 +10,15 @@ - - - - - + + + + + - + @@ -30,8 +30,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs index 3d184fac84..28ac11f104 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Shared/Worksheets/Definitions/DefinitionResolver.cs @@ -60,20 +60,20 @@ public static string Resolve(CustomFieldType type, object? definition) { CustomFieldType.Undefined => "{}", CustomFieldType.BCAddress => "{}", - CustomFieldType.Numeric => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Text => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Date => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.DateTime => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Currency => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.YesNo => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Email => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Phone => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Radio => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.Checkbox => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.CheckboxGroup => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.SelectList => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.TextArea => JsonSerializer.Serialize(element.ToString()), - CustomFieldType.DataGrid => JsonSerializer.Serialize(element.ToString()), + CustomFieldType.Numeric => element.GetRawText(), + CustomFieldType.Text => element.GetRawText(), + CustomFieldType.Date => element.GetRawText(), + CustomFieldType.DateTime => element.GetRawText(), + CustomFieldType.Currency => element.GetRawText(), + CustomFieldType.YesNo => element.GetRawText(), + CustomFieldType.Email => element.GetRawText(), + CustomFieldType.Phone => element.GetRawText(), + CustomFieldType.Radio => element.GetRawText(), + CustomFieldType.Checkbox => element.GetRawText(), + CustomFieldType.CheckboxGroup => element.GetRawText(), + CustomFieldType.SelectList => element.GetRawText(), + CustomFieldType.TextArea => element.GetRawText(), + CustomFieldType.DataGrid => element.GetRawText(), _ => throw new NotImplementedException(), }; } @@ -216,4 +216,4 @@ public static bool ResolveIsDynamic(CustomFieldDefinition field) }; } } -} \ No newline at end of file +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj index 1b9333d47f..f51c9ebdb9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.Flex.Web/Unity.Flex.Web.csproj @@ -12,13 +12,13 @@ - + - + - + @@ -55,8 +55,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Unity.Flex.Application.Tests.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Unity.Flex.Application.Tests.csproj index fbe7add032..16ef810103 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Unity.Flex.Application.Tests.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Unity.Flex.Application.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -12,20 +12,22 @@ - - - - - - - - - + + + + + + + + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Worksheets/DefinitionResolverTests.cs b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Worksheets/DefinitionResolverTests.cs new file mode 100644 index 0000000000..6a065af65e --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Application.Tests/Worksheets/DefinitionResolverTests.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using Shouldly; +using Unity.Flex; +using Unity.Flex.Worksheets; +using Unity.Flex.Worksheets.Definitions; +using Xunit; + +namespace Unity.Flex.Application.Tests.Worksheets; + +public class DefinitionResolverTests +{ + [Fact] + public void Resolve_Should_Preserve_JsonObject_When_Definition_Is_JsonElement() + { + using var document = JsonDocument.Parse("""{"required":true,"maxLength":100}"""); + + var definition = DefinitionResolver.Resolve(CustomFieldType.Text, document.RootElement); + + definition.ShouldBe("""{"required":true,"maxLength":100}"""); + definition.ConvertDefinition(CustomFieldType.Text)!.Required.ShouldBeTrue(); + } +} diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Unity.Flex.TestBase.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Unity.Flex.TestBase.csproj index bc1afd5896..840463a334 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Unity.Flex.TestBase.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Unity.Flex.TestBase.csproj @@ -1,4 +1,4 @@ - + @@ -9,30 +9,30 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Unity.Flex.Web.Tests.csproj b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Unity.Flex.Web.Tests.csproj index ce4d41158a..dac96e2ab9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Unity.Flex.Web.Tests.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.Web.Tests/Unity.Flex.Web.Tests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -10,24 +10,19 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Unity.Identity.Web.csproj b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Unity.Identity.Web.csproj index ac7d62ebe2..86d284e211 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Unity.Identity.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/src/Unity.Identity.Web.csproj @@ -31,17 +31,15 @@ - - - + + + - - - + - + diff --git a/applications/Unity.GrantManager/modules/Unity.Identity.Web/test/Unity.Identity.Web.Tests/Unity.Identity.Web.Tests.csproj b/applications/Unity.GrantManager/modules/Unity.Identity.Web/test/Unity.Identity.Web.Tests/Unity.Identity.Web.Tests.csproj index 4f55729056..cb31380f07 100644 --- a/applications/Unity.GrantManager/modules/Unity.Identity.Web/test/Unity.Identity.Web.Tests/Unity.Identity.Web.Tests.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Identity.Web/test/Unity.Identity.Web.Tests/Unity.Identity.Web.Tests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -10,16 +10,16 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Unity.Notifications.Application.Contracts.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Unity.Notifications.Application.Contracts.csproj index 0e41f2f1c4..cb37cfb67e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Unity.Notifications.Application.Contracts.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application.Contracts/Unity.Notifications.Application.Contracts.csproj @@ -1,34 +1,33 @@ - - - - - - netstandard2.1;net10.0 - enable - Unity.Notifications - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - + + + + + + netstandard2.1;net10.0 + enable + Unity.Notifications + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs index 2c3974a5ee..0abcfd1e37 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/EmailNotificaions/EmailNotificationService.cs @@ -1,8 +1,10 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; +using System.IO; using System.Collections.Generic; using System.Linq; using System.Net; @@ -29,7 +31,8 @@ public class EmailNotificationService( IExternalUserLookupServiceProvider externalUserLookupServiceProvider, ISettingManager settingManager, IFeatureChecker featureChecker, - IHttpContextAccessor httpContextAccessor) : ApplicationService, IEmailNotificationService + IConfiguration configuration, + IWebHostEnvironment webHostEnvironment) : ApplicationService, IEmailNotificationService { public async Task InitializeDraftAsync(Guid applicationId) @@ -80,22 +83,16 @@ protected virtual async Task NotifyTeamsChannel(string chesEmailError) public Task GetBaseUrlAsync() { - var httpContext = httpContextAccessor.HttpContext - ?? throw new InvalidOperationException("No active HTTP context available to resolve base URL."); - - var request = httpContext.Request; - - var host = request.Headers["X-Forwarded-Host"].FirstOrDefault() - ?? request.Host.Value; - - var scheme = request.Headers["X-Forwarded-Proto"].FirstOrDefault() - ?? request.Scheme; - - var pathBase = request.Headers["X-Forwarded-Prefix"].FirstOrDefault() - ?? request.PathBase.Value - ?? string.Empty; - - return Task.FromResult($"{scheme}://{host}{pathBase}".TrimEnd('/')); + var selfUrl = configuration["App:SelfUrl"]; + + if (string.IsNullOrWhiteSpace(selfUrl)) + { + throw new InvalidOperationException( + "App:SelfUrl configuration is not set. Cannot resolve base URL for email notifications. " + + "Ensure the configuration is properly set in appsettings or environment variables."); + } + + return Task.FromResult(selfUrl.TrimEnd('/')); } public async Task SendCommentNotification(EmailCommentDto input) @@ -129,32 +126,7 @@ public async Task SendCommentNotification(EmailCommentDto i _ => CurrentUser.UserName ?? "Unknown User" }; - string htmlBody = $@" - - -

{currentUserText} mentioned you in a comment.

- - - - -
-

{input.Body}

-
-
- - - - -
- View Comment -
-

*Note - Please do not reply to this email as it is an automated notification.

- - "; + string htmlBody = await RenderCommentNotificationTemplateAsync(currentUserText, input.Body, commentLink); foreach (var email in input.MentionNamesEmail) { @@ -274,4 +246,68 @@ private async Task UpdateTenantSettings(string settingKey, string valueString) await settingManager.SetForCurrentTenantAsync(settingKey, valueString); } } + + /// + /// Renders the comment notification email template with the provided parameters. + /// + /// Display name of the user who mentioned + /// The comment body text (may contain HTML) + /// The URL link to view the comment + /// Rendered HTML email body + private async Task RenderCommentNotificationTemplateAsync(string currentUserText, string commentBody, string commentLink) + { + // Load template from embedded resources or file system + string templateContent = await LoadEmailTemplateAsync("CommentNotification"); + + // Replace placeholders with actual values + var renderedTemplate = templateContent + .Replace("@Model.CurrentUserText", currentUserText) + .Replace("@Html.Raw(Model.CommentBody)", commentBody) + .Replace("@Model.CommentLink", commentLink); + + return renderedTemplate; + } + + /// + /// Loads an email template from the Views/EmailTemplates directory. + /// + /// Template name without extension (e.g., "CommentNotification") + /// Template content as a string + private async Task LoadEmailTemplateAsync(string templateName) + { + try + { + // Content root is at: .../Unity.GrantManager/src/Unity.GrantManager.Web + // We need to go up 2 levels to reach Unity.GrantManager, then into modules + var contentRoot = webHostEnvironment.ContentRootPath; + + var templatePath = Path.Combine( + contentRoot, + "..", + "..", + "modules", + "Unity.Notifications", + "src", + "Unity.Notifications.Web", + "Views", + "EmailTemplates", + $"{templateName}.cshtml"); + + // Normalize the path to remove .. references + templatePath = Path.GetFullPath(templatePath); + + if (!File.Exists(templatePath)) + { + throw new FileNotFoundException($"Email template not found at: {templatePath}"); + } + + var content = await File.ReadAllTextAsync(templatePath); + return content; + } + catch (Exception ex) + { + Logger.LogError(ex, $"Failed to load email template '{templateName}'"); + throw; + } + } } diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs index 42c22f5d17..a68c3109cc 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailAttachmentService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Unity.Notifications.Emails; +using Volo.Abp.Authorization; using Volo.Abp.DependencyInjection; using Volo.Abp.Users; @@ -77,13 +78,19 @@ public async Task UploadAttachmentAsync( ContentType = contentType, FileSize = fileContent.Length, Time = DateTime.UtcNow, + // Unlike UploadUserAttachmentAsync below, this path is reached from + // EmailNotificationHandler - a local event handler that can run for + // system/schedule-triggered emails with no interactive user in context, so a missing + // ICurrentUser.Id here isn't necessarily an error condition. The caller already wraps + // this in a try/catch that logs and sends the email without the attachment on any + // failure, so Guid.Empty (rather than throwing) is the intentional "no user" marker. UserId = _currentUser.Id ?? Guid.Empty, TenantId = tenantId }; await _emailLogAttachmentRepository.InsertAsync(attachment); return attachment; - } + } public async Task DownloadFromS3Async(string s3ObjectKey) { @@ -143,7 +150,10 @@ public async Task UploadUserAttachmentAsync( ContentType = contentType, FileSize = fileContent.Length, Time = DateTime.UtcNow, - UserId = _currentUser.Id ?? Guid.Empty, + // A missing ICurrentUser.Id means this was reached without an authenticated user - + // fail loudly rather than silently attributing the attachment to Guid.Empty, which + // would look like a valid, specific user rather than an error state. + UserId = _currentUser.Id ?? throw new AbpAuthorizationException("Cannot save an email attachment without an authenticated user."), TenantId = tenantId }; diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailLogAttachmentAppService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailLogAttachmentAppService.cs index 1933d5e6da..d5db9a21e3 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailLogAttachmentAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Emails/EmailLogAttachmentAppService.cs @@ -122,6 +122,12 @@ public async Task GetTotalFileSizeByEmailLogIdAsync(Guid? emailLogId, Guid return await emailAttachmentService.GetTotalFileSizeAsync(emailLogId, templateId); } + // Not exposed over HTTP: this method takes raw fileName/content/contentType with none of the + // allowlist/size/content-type validation AttachmentController enforces before calling it. It + // must only ever be reached in-process, via IEmailLogAttachmentUploadService, from a caller + // (AttachmentController) that has already run those checks - never directly by an HTTP client, + // which would bypass validation entirely despite still needing the Email.Send permission. + [RemoteService(false)] public async Task UploadAsync(Guid? emailLogId, Guid? templateId, Guid? tenantId, string fileName, byte[] content, string contentType) { var attachment = await emailAttachmentService.UploadUserAttachmentAsync(emailLogId, templateId, tenantId, fileName, content, contentType); diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailQueueService.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailQueueService.cs index 6a954be51a..635e7c1034 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailQueueService.cs +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Integrations/RabbitMQ/EmailQueueService.cs @@ -36,7 +36,7 @@ public async Task SendToEmailDelayedQueueAsync(EmailNotificationEvent emai await Task.Delay(TimeSpan.FromMilliseconds(FiveMinutesInMilliSeconds * (emailNotificationEvent.RetryAttempts + 1))); - _queueProducer.PublishMessage(message); + await _queueProducer.PublishMessageAsync(message); } catch (Exception ex) { var ExceptionMessage = ex.Message; @@ -45,7 +45,7 @@ public async Task SendToEmailDelayedQueueAsync(EmailNotificationEvent emai return Task.CompletedTask; } - public Task SendToEmailEventQueueAsync(EmailNotificationEvent emailNotificationEvent) + public async Task SendToEmailEventQueueAsync(EmailNotificationEvent emailNotificationEvent) { try { @@ -55,13 +55,11 @@ public Task SendToEmailEventQueueAsync(EmailNotificationEvent emailNotificationE TenantId = emailNotificationEvent.TenantId ?? Guid.Empty, EmailNotificationEvent = emailNotificationEvent }; - _queueProducer.PublishMessage(message); + await _queueProducer.PublishMessageAsync(message); } catch (Exception ex) { var ExceptionMessage = ex.Message; _logger.LogError(ex, "SendToEmailEventQueueAsync Exception: {ExceptionMessage}", ExceptionMessage); } - - return Task.CompletedTask; } } \ No newline at end of file diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Unity.Notifications.Application.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Unity.Notifications.Application.csproj index 0e50decfcd..7a8eacb9a4 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Unity.Notifications.Application.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Application/Unity.Notifications.Application.csproj @@ -9,21 +9,19 @@ - - - - - - + + + + + + - - - - - - - - + + + + + + @@ -31,8 +29,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Unity.Notifications.Domain.Shared.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Unity.Notifications.Domain.Shared.csproj index 2eb1af44f6..5dcad10b53 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Unity.Notifications.Domain.Shared.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain.Shared/Unity.Notifications.Domain.Shared.csproj @@ -10,14 +10,14 @@ - - - - + + + + - + @@ -26,8 +26,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Unity.Notifications.Domain.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Unity.Notifications.Domain.csproj index a4cf76b437..926f8cad23 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Unity.Notifications.Domain.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Domain/Unity.Notifications.Domain.csproj @@ -9,16 +9,16 @@ - - - - + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Unity.Notifications.EntityFrameworkCore.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Unity.Notifications.EntityFrameworkCore.csproj index 35443ef745..02618456b9 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Unity.Notifications.EntityFrameworkCore.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.EntityFrameworkCore/Unity.Notifications.EntityFrameworkCore.csproj @@ -9,16 +9,16 @@ - - - - + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi.Client/Unity.Notifications.HttpApi.Client.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi.Client/Unity.Notifications.HttpApi.Client.csproj index 451ddaea79..c8c944ee7e 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi.Client/Unity.Notifications.HttpApi.Client.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi.Client/Unity.Notifications.HttpApi.Client.csproj @@ -9,9 +9,9 @@ - - - + + + @@ -21,8 +21,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi/Unity.Notifications.HttpApi.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi/Unity.Notifications.HttpApi.csproj index 4c17656712..7e309f547b 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi/Unity.Notifications.HttpApi.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.HttpApi/Unity.Notifications.HttpApi.csproj @@ -9,15 +9,15 @@ - - - + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Installer/Unity.Notifications.Installer.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Installer/Unity.Notifications.Installer.csproj index a6e97532ec..f9bdac728d 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Installer/Unity.Notifications.Installer.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Installer/Unity.Notifications.Installer.csproj @@ -10,8 +10,8 @@ - - + + @@ -23,8 +23,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Unity.Notifications.Web.csproj b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Unity.Notifications.Web.csproj index 3e110d2d7d..b5223b81ad 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Unity.Notifications.Web.csproj +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Unity.Notifications.Web.csproj @@ -12,15 +12,13 @@ - + - - - - - - - + + + + + @@ -29,7 +27,7 @@ - + @@ -56,8 +54,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/EmailTemplates/CommentNotification.cshtml b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/EmailTemplates/CommentNotification.cshtml new file mode 100644 index 0000000000..703a316737 --- /dev/null +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/EmailTemplates/CommentNotification.cshtml @@ -0,0 +1,46 @@ +@model dynamic + + + + + + Comment Notification + + +

@Model.CurrentUserText mentioned you in a comment.

+ + + + + + + + + + + +
Comment
+

@Html.Raw(Model.CommentBody)

+
+
+ + + + + + + + + + + +
Action
+ View Comment +
+

*Note - Please do not reply to this email as it is an automated notification.

+ + diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml index 1fc31ed1c2..015e4a3e13 100644 --- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml +++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/Default.cshtml @@ -63,7 +63,7 @@