diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..ae0394d9
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @hmcts/api-marketplace
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 00000000..e407d1cb
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# Contribution guidelines
+
+We're happy to accept 3rd-party contributions. Please make sure you read this document before you do any work though,
+as we have some expectations related to the content and quality of change sets.
+
+## Before contributing
+
+Any ideas on the user journeys and general service experience you may have **should be first consulted
+with us by submitting a new issue** to this repository. Ideas are always welcome, but if something is divergent or unrelated
+to what we're trying to achieve we won't be able to accept it. Please keep this in mind as we don't want to waste anybody's time.
+
+In the interest of creating a friendly collaboration environment, please read and adhere to an open source contributor's
+[code of conduct](http://contributor-covenant.org/version/1/4/).
+
+## Making a contribution
+
+After your idea has been accepted you can implement it. We don't allow direct changes to the codebase from the public,
+they have to go through a review first.
+
+Here's what you should do:
+1. [fork](https://help.github.com/articles/fork-a-repo/) this repository and clone it to your machine,
+2. create a new branch for your change:
+ * use the latest *main* to branch from,
+3. implement the change in your branch:
+ * if the change is non-trivial it's a good practice to split it into several logically independent units and deliver
+ each one as a separate commit,
+ * make sure the commit messages use proper language and accurately describe commit's content, e.g. *"Unify postcode lookup elements spacing"*.
+ More information on good commit messages can be found [here](http://chris.beams.io/posts/git-commit/),
+4. test if your feature works as expected and does not break any existing features, this may include implementing additional automated tests or amending existing ones,
+5. push the change to your GitHub fork,
+6. submit a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) to our repository:
+ * ensure that the pull request and related GitHub issue reference each other.
+
+At this point the pull request will wait for someone from our team to review. It may be accepted straight away,
+or we may ask you to make some additional amendments before incorporating it into the main branch.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..d1f90693
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,5 @@
+### What would you like to change?
+
+### How do you think that would improve the project?
+
+### If this entry is related to a bug, please provide the steps to reproduce it
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..dfadfd11
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,23 @@
+**Before creating a pull request make sure that:**
+
+- [ ] commit messages are meaningful and follow good commit message guidelines
+- [ ] README and other documentation has been updated / added (if needed)
+- [ ] tests have been updated / new tests has been added (if needed)
+
+Please remove this line and everything above and fill the following sections:
+
+
+### JIRA link (if applicable) ###
+
+
+
+### Change description ###
+
+
+
+**Does this PR introduce a breaking change?** (check one with "x")
+
+```
+[ ] Yes
+[ ] No
+```
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..3ce9f9df
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,18 @@
+version: 2
+updates:
+ - package-ecosystem: "gradle"
+ directory: "/" # location of build.gradle
+ schedule:
+ interval: "daily" # or "weekly", "monthly"
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "chore(deps)"
+ groups:
+ all-dependencies:
+ patterns:
+ - "*"
+
+ - package-ecosystem: "github-actions"
+ directory: "/" # location of your workflow files
+ schedule:
+ interval: "weekly"
\ No newline at end of file
diff --git a/.github/pmd-ruleset.xml b/.github/pmd-ruleset.xml
new file mode 100644
index 00000000..35916718
--- /dev/null
+++ b/.github/pmd-ruleset.xml
@@ -0,0 +1,34 @@
+
+
+
+ Custom ruleset including exclusions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.github/workflows/ci-build-publish.yml b/.github/workflows/ci-build-publish.yml
new file mode 100644
index 00000000..50f80634
--- /dev/null
+++ b/.github/workflows/ci-build-publish.yml
@@ -0,0 +1,188 @@
+name: CI Build and Publish
+
+on:
+ workflow_call:
+ secrets:
+ AZURE_DEVOPS_ARTIFACT_USERNAME:
+ required: true
+ AZURE_DEVOPS_ARTIFACT_TOKEN:
+ required: true
+ HMCTS_ADO_PAT:
+ required: true
+ inputs:
+ is_release:
+ required: false
+ type: boolean
+ default: false
+ is_publish:
+ required: true
+ type: boolean
+ trigger_docker:
+ required: true
+ type: boolean
+ trigger_deploy:
+ required: true
+ type: boolean
+
+
+jobs:
+ Artefact-Version:
+ runs-on: ubuntu-latest
+ outputs:
+ artefact_version: ${{ inputs.is_release && steps.artefact.outputs.release_version || steps.artefact.outputs.draft_version }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - name: Generate Artefact Version
+ id: artefact
+ uses: hmcts/artefact-version-action@v1
+ with:
+ release: ${{ inputs.is_release }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ Build:
+ needs: [Artefact-Version]
+ runs-on: ubuntu-latest
+ outputs:
+ repo_name: ${{ steps.repo_vars.outputs.repo_name }}
+ artefact_name: ${{ steps.repo_vars.outputs.artefact_name }}
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v6
+
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v5
+ with:
+ gradle-version: current
+
+ - name: Gradle Build
+ env:
+ ARTEFACT_VERSION: ${{ needs.Artefact-Version.outputs.artefact_version }}
+ run: |
+ echo "Building with ARTEFACT_VERSION=$ARTEFACT_VERSION"
+ gradle build -DARTEFACT_VERSION=$ARTEFACT_VERSION
+
+ - name: Extract repo name
+ id: repo_vars
+ run: |
+ repo_name=${GITHUB_REPOSITORY##*/}
+ echo "repo_name=${repo_name}" >> $GITHUB_OUTPUT
+ echo "artefact_name=${repo_name}-${{ needs.Artefact-Version.outputs.artefact_version }}" >> $GITHUB_OUTPUT
+
+ - name: Upload JAR Artefact
+ uses: actions/upload-artifact@v5
+ with:
+ name: app-jar
+ path: build/libs/${{ steps.repo_vars.outputs.artefact_name }}.jar
+
+ Provider-Deploy:
+ needs: [ Artefact-Version, Build ]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v6
+
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v5
+ with:
+ gradle-version: current
+
+ - name: Gradle Publish
+ if: ${{ inputs.is_publish }}
+ env:
+ ARTEFACT_VERSION: ${{ needs.Artefact-Version.outputs.artefact_version }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ AZURE_DEVOPS_ARTIFACT_USERNAME: ${{ secrets.AZURE_DEVOPS_ARTIFACT_USERNAME }}
+ AZURE_DEVOPS_ARTIFACT_TOKEN: ${{ secrets.AZURE_DEVOPS_ARTIFACT_TOKEN }}
+ run: |
+ if [ -z "AZURE_DEVOPS_ARTIFACT_USERNAME" ]; then
+ echo "::warning::AZURE_DEVOPS_ARTIFACT_USERNAME is null or not set"
+ fi
+
+ if [ -z "$AZURE_DEVOPS_ARTIFACT_TOKEN" ]; then
+ echo "::warning::AZURE_DEVOPS_ARTIFACT_TOKEN is null or not set"
+ fi
+
+ echo "Publishing artefact for version: $ARTEFACT_VERSION"
+
+ gradle publish \
+ -DARTEFACT_VERSION=$ARTEFACT_VERSION \
+ -DGITHUB_REPOSITORY=${{ github.repository }} \
+ -DGITHUB_ACTOR=${{ github.actor }} \
+ -DGITHUB_TOKEN=$GITHUB_TOKEN \
+ -DAZURE_DEVOPS_ARTIFACT_USERNAME=$AZURE_DEVOPS_ARTIFACT_USERNAME \
+ -DAZURE_DEVOPS_ARTIFACT_TOKEN=$AZURE_DEVOPS_ARTIFACT_TOKEN
+
+ Build-Docker:
+ needs: [ Provider-Deploy, Build, Artefact-Version ]
+ if: ${{ inputs.trigger_docker }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v6
+
+ - name: Download JAR Artefact
+ uses: actions/download-artifact@v6
+ with:
+ name: app-jar
+ path: build/libs
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to GitHub Packages
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and Push Docker Image to GitHub
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: Dockerfile
+ push: true
+ tags: |
+ ghcr.io/${{ github.repository }}:${{ needs.Artefact-Version.outputs.artefact_version }}
+ build-args: |
+ BASE_IMAGE=openjdk:21-jdk-slim
+ JAR_FILENAME=${{ needs.Build.outputs.artefact_name }}.jar
+
+ Deploy:
+ needs: [ Provider-Deploy, Build, Artefact-Version ]
+ if: ${{ inputs.trigger_deploy }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Extract repo name
+ run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
+
+ - name: Trigger ADO pipeline
+ uses: hmcts/trigger-ado-pipeline@v1
+ with:
+ pipeline_id: 460
+ ado_pat: ${{ secrets.HMCTS_ADO_PAT }}
+ template_parameters: >
+ {
+ "GROUP_ID": "uk.gov.hmcts.cp",
+ "ARTIFACT_ID": "${{ env.REPO_NAME }}",
+ "ARTIFACT_VERSION": "${{ needs.Artefact-Version.outputs.artefact_version }}",
+ "TARGET_REPOSITORY": "${{ github.repository }}"
+ }
+
diff --git a/.github/workflows/ci-draft.yml b/.github/workflows/ci-draft.yml
new file mode 100644
index 00000000..5316ed78
--- /dev/null
+++ b/.github/workflows/ci-draft.yml
@@ -0,0 +1,23 @@
+name: Build and Publish (Non-Release)
+
+on:
+ pull_request:
+ branches:
+ - master
+ - main
+ push:
+ branches:
+ - master
+ - main
+
+jobs:
+ ci-draft:
+ uses: ./.github/workflows/ci-build-publish.yml
+ secrets:
+ AZURE_DEVOPS_ARTIFACT_USERNAME: ${{ secrets.AZURE_DEVOPS_ARTIFACT_USERNAME }}
+ AZURE_DEVOPS_ARTIFACT_TOKEN: ${{ secrets.AZURE_DEVOPS_ARTIFACT_TOKEN }}
+ HMCTS_ADO_PAT: ${{ secrets.HMCTS_CP_ADO_PAT }}
+ with:
+ is_publish: ${{ github.event_name == 'push' }}
+ trigger_docker: ${{ github.event_name == 'push' }}
+ trigger_deploy: ${{ github.event_name == 'push' }}
diff --git a/.github/workflows/ci-released.yml b/.github/workflows/ci-released.yml
new file mode 100644
index 00000000..81d9e3cd
--- /dev/null
+++ b/.github/workflows/ci-released.yml
@@ -0,0 +1,19 @@
+name: CI Build and Publish â Release
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+jobs:
+ ci-release:
+ uses: ./.github/workflows/ci-build-publish.yml
+ secrets:
+ AZURE_DEVOPS_ARTIFACT_USERNAME: ${{ secrets.AZURE_DEVOPS_ARTIFACT_USERNAME }}
+ AZURE_DEVOPS_ARTIFACT_TOKEN: ${{ secrets.AZURE_DEVOPS_ARTIFACT_TOKEN }}
+ HMCTS_ADO_PAT: ${{ secrets.HMCTS_CP_ADO_PAT }}
+ with:
+ is_release: true
+ is_publish: true
+ trigger_docker: true
+ trigger_deploy: true
diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml
new file mode 100644
index 00000000..86b6f381
--- /dev/null
+++ b/.github/workflows/code-analysis.yml
@@ -0,0 +1,36 @@
+name: Code analysis
+
+on:
+ pull_request:
+ branches:
+ - master
+ - main
+
+jobs:
+ pmd-analysis:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ - uses: pmd/pmd-github-action@v2
+ id: pmd
+ with:
+ rulesets: '.github/pmd-ruleset.xml'
+ sourcePath: 'src/main/java'
+ analyzeModifiedFilesOnly: false
+
+ - name: Fail build if there are violations
+ if: always()
+ run: |
+ VIOLATIONS="${{ steps.pmd.outputs.violations }}"
+ if [ -n "$VIOLATIONS" ] && [ "$VIOLATIONS" != "0" ]; then
+ echo "PMD found $VIOLATIONS violations"
+ exit 1
+ fi
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 00000000..c0fe9c93
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,121 @@
+name: CodeQL
+
+on:
+ pull_request:
+ branches:
+ - master
+ - main
+
+ schedule:
+ - cron: '36 5 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+ language: [ 'java' ]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+ with:
+ languages: ${{ matrix.language }}
+ queries: security-extended
+
+ - uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: 21
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v5
+ with:
+ gradle-version: current
+
+ - name: Gradle Build
+ run: |
+ gradle build cyclonedxBom -x test
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ # If the (auto)build fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: "/language:${{matrix.language}}"
+
+ - name: Log generated SBOM Hash
+ run: sha256sum build/resources/main/META-INF/sbom/application.cdx.json || true
+
+ # This ensures:
+ # - The SBOM is archived with the CodeQL scan output
+ # - It's available to download and inspect from the GitHub Actions UI
+ - name: Upload SBOM
+ if: always()
+ uses: actions/upload-artifact@v5
+ with:
+ name: sbom
+ path: build/resources/main/META-INF/sbom/application.cdx.json
+
+ DAST:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: 21
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v5
+ with:
+ gradle-version: current
+
+ - name: Gradle Build
+ run: gradle build -x test -x api
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: DAST - Build and run containerised app
+ run: |
+ docker compose -f docker/docker-compose.yml up -d
+
+ echo "Waiting for health endpoint..."
+ for i in {1..30}; do
+ if curl -s http://localhost:8082/health > /dev/null; then
+ echo "App is healthy"
+ break
+ fi
+ echo "Waiting for app to be healthy ($i)..."
+ sleep 2
+ done
+
+ - name: Run OWASP ZAP DAST Scan
+ uses: zaproxy/action-baseline@v0.15.0
+ with:
+ target: "http://localhost:8082"
+ cmd_options: "-a -J zap_report.json -r zap_report.html"
+
+ - name: Upload ZAP HTML Report
+ uses: actions/upload-artifact@v5
+ with:
+ name: zap-html-report
+ path: zap_report.html
diff --git a/.github/workflows/secrets-scanner.yml b/.github/workflows/secrets-scanner.yml
new file mode 100644
index 00000000..121e0713
--- /dev/null
+++ b/.github/workflows/secrets-scanner.yml
@@ -0,0 +1,23 @@
+name: Secrets Scanner
+on:
+ pull_request:
+ branches:
+ - master
+ - main
+ schedule:
+ - cron: '0 4 * * 4' # Every Thursday at 04:00
+ workflow_dispatch:
+
+jobs:
+ scan:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - uses: hmcts/secrets-scanner@main
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ gitleaks_license: ${{ secrets.GITLEAKS_LICENSE }}
+ gitleaks_regex_internal_url: ${{ secrets.HMCTS_CP_GITLEAKS_REGEX_INTERNAL_URL }}
diff --git a/README.md b/README.md
index 812ee308..90af7556 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# API HMCTS
-The Court Hearing Cases API provides details of a criminal court case results
+The Court Hearing Cases API publishes events relating criminal court cases
### Contribute to This Repository
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..44052908
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'application'
+ id 'java'
+ id 'org.springframework.boot' version '4.0.0'
+ id 'io.spring.dependency-management' version '1.1.7'
+ id 'jacoco'
+ id 'maven-publish'
+ id 'com.github.ben-manes.versions' version '0.53.0'
+ id 'org.cyclonedx.bom' version '2.4.1'
+ id 'com.gorylenko.gradle-git-properties' version '2.5.3'
+ id 'com.avast.gradle.docker-compose' version '0.17.12'
+}
+
+group = 'uk.gov.hmcts.cp'
+version = System.getProperty('ARTEFACT_VERSION') ?: '0.0.999'
+
+apply {
+ from("$rootDir/gradle/dependencies/java-core.gradle")
+ from("$rootDir/gradle/dependencies/spring-core.gradle")
+
+ from("$rootDir/gradle/github/repositories.gradle")
+ from("$rootDir/gradle/github/java.gradle")
+ from("$rootDir/gradle/github/dependency.gradle")
+ from("$rootDir/gradle/github/pmd.gradle")
+ from("$rootDir/gradle/github/test.gradle")
+ from("$rootDir/gradle/github/jar.gradle")
+
+ from("$rootDir/gradle/tasks/apitest.gradle")
+ from("$rootDir/gradle/tasks/docker.gradle")
+}
+
+springBoot {
+ buildInfo {
+ properties {
+ name = project.name
+ version = project.version.toString()
+ }
+ }
+}
+
+dependencies {
+ implementation('uk.gov.hmcts.cp:api-cp-crime-courthearing-cases-results:0.0.0-a65d290')
+}
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 00000000..b78ded9c
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,10 @@
+# Dockerfile (project root)
+FROM eclipse-temurin:21
+
+WORKDIR /app
+
+COPY build/libs/*.jar /app/
+
+EXPOSE ${SERVER_PORT:-8082}
+
+ENTRYPOINT ["sh","-c","exec java -jar $(ls /app/*.jar | grep -v 'plain' | head -n1)"]
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 00000000..c8116301
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,10 @@
+services:
+ app:
+ container_name: app
+ build:
+ context: ..
+ dockerfile: docker/Dockerfile
+ ports:
+ - "8082:8082"
+ environment:
+ SERVER_PORT: 8082
diff --git a/gradle/dependencies/java-core.gradle b/gradle/dependencies/java-core.gradle
new file mode 100644
index 00000000..27b07839
--- /dev/null
+++ b/gradle/dependencies/java-core.gradle
@@ -0,0 +1,21 @@
+ext {
+ log4JVersion = "2.24.3"
+ logbackVersion = "1.5.18"
+ lombokVersion = "1.18.38"
+ mapstructVersion = "1.5.5.Final"
+}
+
+dependencies {
+ implementation 'net.logstash.logback:logstash-logback-encoder:8.1'
+ implementation 'org.apache.logging.log4j:log4j-to-slf4j'
+ implementation 'ch.qos.logback:logback-classic'
+ implementation 'ch.qos.logback:logback-core'
+
+ compileOnly group: 'org.projectlombok', name: 'lombok', version: lombokVersion
+ annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombokVersion
+ testCompileOnly group: 'org.projectlombok', name: 'lombok', version: lombokVersion
+ testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombokVersion
+
+ implementation "org.mapstruct:mapstruct:$mapstructVersion"
+ annotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
+}
diff --git a/gradle/dependencies/spring-core.gradle b/gradle/dependencies/spring-core.gradle
new file mode 100644
index 00000000..6c6d3f8c
--- /dev/null
+++ b/gradle/dependencies/spring-core.gradle
@@ -0,0 +1,11 @@
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-aspectj'
+
+ testImplementation "org.springframework.boot:spring-boot-starter-webmvc-test"
+
+ testImplementation('org.springframework.boot:spring-boot-starter-test') {
+ exclude group: 'junit', module: 'junit'
+ exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+ }
+}
\ No newline at end of file
diff --git a/gradle/github/dependency.gradle b/gradle/github/dependency.gradle
new file mode 100644
index 00000000..09621b82
--- /dev/null
+++ b/gradle/github/dependency.gradle
@@ -0,0 +1,11 @@
+// check dependencies upon release ONLY
+tasks.named("dependencyUpdates").configure {
+ def isNonStable = { String version ->
+ def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { qualifier -> version.toUpperCase().contains(qualifier) }
+ def regex = /^[0-9,.v-]+$/
+ return !stableKeyword && !(version ==~ regex)
+ }
+ rejectVersionIf {
+ isNonStable(it.candidate.version) && !isNonStable(it.currentVersion)
+ }
+}
diff --git a/gradle/github/jar.gradle b/gradle/github/jar.gradle
new file mode 100644
index 00000000..ab1810db
--- /dev/null
+++ b/gradle/github/jar.gradle
@@ -0,0 +1,34 @@
+jar {
+ enabled = true
+ archiveClassifier.set('plain')
+ manifest {
+ attributes(
+ 'Implementation-Title': project.name,
+ 'Implementation-Version': project.version.toString()
+ )
+ }
+ if (file("CHANGELOG.md").exists()) {
+ from('CHANGELOG.md') {
+ into 'META-INF'
+ }
+ } else {
+ println "â ī¸ CHANGELOG.md not found, skipping inclusion in JAR"
+ }
+}
+
+bootJar {
+ archiveFileName = "${rootProject.name}-${project.version}.jar"
+
+ manifest {
+ attributes('Implementation-Version': project.version.toString())
+ }
+}
+
+tasks.named('composeBuild') {
+ dependsOn tasks.named('bootJar')
+}
+
+tasks.withType(AbstractArchiveTask).configureEach {
+ preserveFileTimestamps = false
+ reproducibleFileOrder = true
+}
diff --git a/gradle/github/java.gradle b/gradle/github/java.gradle
new file mode 100644
index 00000000..d7b4c7d2
--- /dev/null
+++ b/gradle/github/java.gradle
@@ -0,0 +1,12 @@
+java {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+}
+tasks.withType(JavaCompile).configureEach {
+ options.compilerArgs << "-Xlint:unchecked" << "-Werror"
+}
+
+// https://github.com/gradle/gradle/issues/16791
+tasks.withType(JavaExec).configureEach {
+ javaLauncher.set(javaToolchains.launcherFor(java.toolchain))
+}
diff --git a/gradle/github/pmd.gradle b/gradle/github/pmd.gradle
new file mode 100644
index 00000000..3804534c
--- /dev/null
+++ b/gradle/github/pmd.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'pmd'
+
+pmd {
+ ruleSets = []
+ ruleSetFiles = files(".github/pmd-ruleset.xml")
+ ignoreFailures = false
+}
+
+tasks.named("pmdMain").configure {
+ onlyIf { gradle.startParameter.taskNames.contains(name) }
+}
+
+tasks.named("pmdTest").configure {
+ enabled = false
+}
+
+tasks.withType(Pmd) {
+ reports {
+ xml.required.set(true)
+ html.required.set(true)
+ }
+}
+
+tasks.withType(Checkstyle).configureEach {
+ def generatedDir = file("${layout.buildDirectory.get().asFile.absolutePath}/generated/src/main/java").canonicalPath
+ source = source.filter { file ->
+ !file.canonicalPath.startsWith(generatedDir)
+ }
+}
diff --git a/gradle/github/repositories.gradle b/gradle/github/repositories.gradle
new file mode 100644
index 00000000..c149d032
--- /dev/null
+++ b/gradle/github/repositories.gradle
@@ -0,0 +1,46 @@
+def githubActor = project.findProperty("github.actor") ?: System.getenv("GITHUB_ACTOR")
+def githubToken = project.findProperty("github.token") ?: System.getenv("GITHUB_TOKEN")
+def githubRepo = System.getenv("GITHUB_REPOSITORY")
+
+def azureADOArtifactRepository = 'https://pkgs.dev.azure.com/hmcts/Artifacts/_packaging/hmcts-lib/maven/v1'
+def azureADOArtifactActor = System.getenv("AZURE_DEVOPS_ARTIFACT_USERNAME")
+def azureADOArtifactToken = System.getenv("AZURE_DEVOPS_ARTIFACT_TOKEN")
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven {
+ url = azureADOArtifactRepository
+ }
+}
+
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ artifact(tasks.named('bootJar'))
+ artifact(tasks.named('jar'))
+ pom {
+ name = project.name
+ url = "https://github.com/${githubRepo ?: 'org/repo'}"
+ }
+ }
+ }
+ repositories {
+ maven {
+ name = "GitHubPackages"
+ url = uri("https://maven.pkg.github.com/$githubRepo")
+ credentials {
+ username = githubActor
+ password = githubToken
+ }
+ }
+ maven {
+ name = "AzureArtifacts"
+ url = uri(azureADOArtifactRepository)
+ credentials {
+ username = azureADOArtifactActor
+ password = azureADOArtifactToken
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradle/github/test.gradle b/gradle/github/test.gradle
new file mode 100644
index 00000000..a37c3592
--- /dev/null
+++ b/gradle/github/test.gradle
@@ -0,0 +1,33 @@
+tasks.named('test') {
+ useJUnitPlatform {
+ }
+ systemProperty 'API_SPEC_VERSION', project.version
+ failFast = true
+ // Mockito must be added as an agent, see:
+ // https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
+ jvmArgs += [
+ "-javaagent:${configurations.testRuntimeClasspath.find { it.name.contains('mockito-core') }}", '-Xshare:off'
+ ]
+ testLogging {
+ events "passed", "skipped", "failed"
+ exceptionFormat = 'full'
+ showStandardStreams = true
+ }
+ reports {
+ junitXml.required.set(true)
+ html.required.set(true)
+ }
+}
+
+tasks.named('jacocoTestReport') {
+ dependsOn tasks.named('test')
+ executionData fileTree(
+ dir: layout.buildDirectory.dir("jacoco").get().asFile,
+ include: ["*.exec"]
+ )
+ reports {
+ xml.required.set(true)
+ csv.required.set(false)
+ html.required.set(true)
+ }
+}
\ No newline at end of file
diff --git a/gradle/tasks/apitest.gradle b/gradle/tasks/apitest.gradle
new file mode 100644
index 00000000..6d23937e
--- /dev/null
+++ b/gradle/tasks/apitest.gradle
@@ -0,0 +1,34 @@
+tasks.register('api', Test) {
+ description = "Runs api tests against docker-compose stack"
+ group = "Verification"
+
+ mustRunAfter tasks.named('test')
+
+ testClassesDirs = sourceSets.apiTest.output.classesDirs
+ classpath = sourceSets.apiTest.runtimeClasspath
+ useJUnitPlatform()
+
+ dependsOn tasks.composeUp
+ finalizedBy tasks.composeDown
+}
+
+tasks.named('check') {
+ dependsOn tasks.named('api')
+}
+
+tasks.named('build') {
+ dependsOn tasks.named('test')
+ dependsOn tasks.named('api')
+}
+
+sourceSets {
+ apiTest {
+ java {
+ compileClasspath += sourceSets.main.output
+ runtimeClasspath += sourceSets.main.output
+ }
+ }
+}
+
+dependencies {
+}
diff --git a/gradle/tasks/docker.gradle b/gradle/tasks/docker.gradle
new file mode 100644
index 00000000..97085af8
--- /dev/null
+++ b/gradle/tasks/docker.gradle
@@ -0,0 +1,18 @@
+dockerCompose {
+ useComposeFiles = ['docker/docker-compose.yml']
+ startedServices = ['app']
+
+ buildBeforeUp = true
+ waitForTcpPorts = true
+ upAdditionalArgs = ['--wait', '--wait-timeout', '30']
+
+ captureContainersOutput = true
+ removeOrphans = true
+ stopContainers = true
+ removeContainers = true
+
+ projectName = "${rootProject.name}"
+
+ useDockerComposeV2 = true
+ dockerExecutable = 'docker'
+}
\ No newline at end of file
diff --git a/src/main/java/uk/gov/hmcts/cp/Application.java b/src/main/java/uk/gov/hmcts/cp/Application.java
new file mode 100644
index 00000000..20276053
--- /dev/null
+++ b/src/main/java/uk/gov/hmcts/cp/Application.java
@@ -0,0 +1,13 @@
+package uk.gov.hmcts.cp;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+
+public class Application {
+
+ public static void main(final String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/uk/gov/hmcts/cp/controllers/RootController.java b/src/main/java/uk/gov/hmcts/cp/controllers/RootController.java
new file mode 100644
index 00000000..09bd774c
--- /dev/null
+++ b/src/main/java/uk/gov/hmcts/cp/controllers/RootController.java
@@ -0,0 +1,19 @@
+package uk.gov.hmcts.cp.controllers;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AllArgsConstructor
+@Slf4j
+public class RootController {
+
+ @GetMapping("/")
+ public ResponseEntity getRoot() {
+ log.info("START");
+ return ResponseEntity.ok("Hello");
+ }
+}
diff --git a/src/test/java/uk/gov/hmcts/cp/controllers/RootControllerTest.java b/src/test/java/uk/gov/hmcts/cp/controllers/RootControllerTest.java
new file mode 100644
index 00000000..96392b5a
--- /dev/null
+++ b/src/test/java/uk/gov/hmcts/cp/controllers/RootControllerTest.java
@@ -0,0 +1,7 @@
+package uk.gov.hmcts.cp.controllers;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class RootControllerTest {
+
+}
\ No newline at end of file
diff --git a/src/test/java/uk/gov/hmcts/cp/controllers/integration/IntegrationTestBase.java b/src/test/java/uk/gov/hmcts/cp/controllers/integration/IntegrationTestBase.java
new file mode 100644
index 00000000..1ffe987b
--- /dev/null
+++ b/src/test/java/uk/gov/hmcts/cp/controllers/integration/IntegrationTestBase.java
@@ -0,0 +1,16 @@
+package uk.gov.hmcts.cp.controllers.integration;
+
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@Slf4j
+public abstract class IntegrationTestBase {
+
+ @Resource
+ protected MockMvc mockMvc;
+}
diff --git a/src/test/java/uk/gov/hmcts/cp/controllers/integration/RootControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/controllers/integration/RootControllerIntegrationTest.java
new file mode 100644
index 00000000..8ae04ed5
--- /dev/null
+++ b/src/test/java/uk/gov/hmcts/cp/controllers/integration/RootControllerIntegrationTest.java
@@ -0,0 +1,15 @@
+package uk.gov.hmcts.cp.controllers.integration;
+
+import org.junit.jupiter.api.Test;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+class RootControllerIntegrationTest extends IntegrationTestBase {
+
+ @Test
+ void root_endpoint_should_be_ok() throws Exception {
+ mockMvc.perform(get("/"))
+ .andExpect(status().isOk());
+ }
+}
\ No newline at end of file