From a5efa014352800b37ff78c8beb47f22844fd502e Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 15:01:13 +0000 Subject: [PATCH 01/13] Update readme --- README.md | 729 ++++++++++++++++--------------------- appserver-jersey/README.md | 61 ++++ microservices/README.md | 81 +++++ 3 files changed, 457 insertions(+), 414 deletions(-) create mode 100644 appserver-jersey/README.md create mode 100644 microservices/README.md diff --git a/README.md b/README.md index 8201925b..17858666 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,34 @@ # RADAR-Appserver -[![BCH compliance](https://bettercodehub.com/edge/badge/RADAR-base/RADAR-Appserver?branch=master)](https://bettercodehub.com/) [![Build Status](https://travis-ci.org/RADAR-base/RADAR-Appserver.svg?branch=master)](https://travis-ci.org/RADAR-base/RADAR-Appserver) [![Known Vulnerabilities](https://snyk.io//test/github/RADAR-base/RADAR-Appserver/badge.svg?targetFile=build.gradle)](https://snyk.io//test/github/RADAR-base/RADAR-Appserver?targetFile=build.gradle) -General purpose application server for the radar platform currently with capability to schedule push notifications. +General purpose application server for the RADAR platform with capability to schedule push notifications via Firebase Cloud Messaging. - +This project has two deployment modes: +- **appserver-jersey** — monolith (single service, single database) +- **microservices** — decomposed into independent services with separate databases + * [RADAR-Appserver](#radar-appserver) - * [Introduction](#introduction) - * [Getting Started](#getting-started) - * [REST API](#rest-api) - * [Quickstart](#quickstart) - * [FCM](#fcm) - * [AdminSDK](#adminsdk) - * [Docker/ Docker Compose](#docker-docker-compose) - * [Architecture](#architecture) - * [Notification Lifecycle](#notification-lifecycle) - * [Protocols](#protocols) - * [Documentation](#documentation) - * [Client](#client) - * [Security](#security) - * [Management Portal](#management-portal) - * [Management Portal Clients](#management-portal-clients) - * [Other Security Providers](#other-security-providers) - * [Monitoring](#monitoring) - * [Performance Testing](#performance-testing) - * [Code-Style and Quality](#code-style-and-quality) - * [Unit and Integration Testing](#unit-and-integration-testing) - * [Current Features](#current-features) - + * [Introduction](#introduction) + * [Getting Started](#getting-started) + * [REST API](#rest-api) + * [Quickstart](#quickstart) + * [FCM](#fcm) + * [AdminSDK](#adminsdk) + * [Docker / Docker Compose](#docker--docker-compose) + * [Architecture](#architecture) + * [Notification Lifecycle](#notification-lifecycle) + * [Protocols](#protocols) + * [Email Notifications](#email-notifications) + * [Documentation](#documentation) + * [Security](#security) + * [Management Portal](#management-portal) + * [Management Portal Clients](#management-portal-clients) + * [Other Security Providers](#other-security-providers) + * [Database Migrations](#database-migrations) + * [Code Quality and Testing](#code-quality-and-testing) + * [Monitoring](#monitoring) + * [Sentry](#sentry) + * [Current Features](#current-features) ## Introduction @@ -47,53 +48,52 @@ see the relevant section below. access to all the Firebase services. Follow the instructions on the [official docs](https://firebase.google.com/docs/) according to your platform. -2. Configure the Server Key and Sender ID (obtained from FCM) in application.properties. +2. The AppServer needs a database. You can use either a standalone PostgreSQL instance or an in-memory H2 database. -3. The AppServer needs a database to work. You can either use a `stand-alone` instance of the database of use an - in-memory `embedded` instance- + **Option A: Standalone PostgreSQL** (recommended for production) - 3.1. To use the standalone instance, run the database a docker service by - - ```bash - docker-compose -f src/integrationTest/resources/docker/non_appserver/docker-compose.yml up -d postgres - ``` - - This will start the database at `localhost:5432` + Start a PostgreSQL instance with Docker: + ```bash + docker run -d --name appserver-db -p 5432:5432 \ + -e POSTGRES_DB=appserver -e POSTGRES_USER=radar -e POSTGRES_PASSWORD=radar \ + postgres:15 + ``` - 3.2. To use as an embedded in-memory database instance (Not recommended for production deployments), set - the `spring.datasource.url=jdbc:hsqldb:mem:/appserver` in `application-.properties`. Also, change the - properties in `src/main/resources/application.properties` to dev or prod according to your requirements. + **Option B: In-memory H2 database** (for development only, not recommended for production) -4. Build the project using gradle wrapper and run using spring boot. Note: This project uses JAVA 17, please download - and install it before building. + In `appserver-jersey/src/main/resources/appserver.yml`, uncomment the H2 configuration: + ```yaml + database: + jdbcDriver: org.h2.Driver + jdbcUrl: jdbc:h2:mem:dev + hibernateDialect: org.hibernate.dialect.H2Dialect + ``` -5. The build will need to create a logs directory. The default path is `/usr/local/var/lib/radar/appserver/logs`. Either - create the directory there using `sudo mkdir -p /usr/local/var/lib/radar/appserver/logs` followed - by `sudo chown $USER /usr/local/var/lib/radar/appserver/logs` or change logs file directory - in `src/main/resources/logback-spring.xml` to local log directory like `` +3. Build the project (requires Java 17): + ```bash + # Monolith + ./gradlew :appserver-jersey:build -6. The appserver uses the Admin SDK to communicate with the Firebase Cloud Messaging. To - configure this, please look at the [FCM section](#fcm). + # Microservices + ./gradlew :microservices:build + ``` -7. To run the build, run the command below - +4. Run the monolith: ```bash - ./gradlew bootRun + ./gradlew :appserver-jersey:run ``` - You can also run in an IDE (like IntelliJ Idea) by giving - the `/src/main/java/org/radarbase/appserver/AppserverApplication.java` as the main class. + The server starts at `http://localhost:8080/`. -8. The App-server is now running and is able to send FCM messages. You can make the request to - create a project, a user and a notification using the [REST API](#rest-api). +5. The AppServer uses the Admin SDK to communicate with Firebase Cloud Messaging. To configure this, see the [FCM section](#fcm). -9. Voila!, you will now receive a notification at the schedule time (specified by `scheduledTime` in the payload) on - your device. +6. For microservices deployment, see [microservices/README.md](microservices/README.md). ## REST API -The full API specification and documentation is available via Swagger UI when you launch the app -server. Please refer to the [Documentation section](#documentation) below. +The full API specification is available via OpenAPI/Swagger when you launch the app server: +- OpenAPI spec: `http://localhost:8080/openapi.yaml` or `http://localhost:8080/openapi.json` -1. Create a project. If using Management portal, this should be exactly same as the project name +1. Create a project. If using Management Portal, this should be exactly same as the project name in management portal. ``` POST http://localhost:8080/projects/p1 @@ -107,7 +107,7 @@ server. Please refer to the [Documentation section](#documentation) below. POST http://localhost:8080/projects/p1/users/u2 { "subjectId": "u2", - "fcmToken" : "shdzdxcvc", + "fcmToken" : "shdzdxcvc", "enrolmentDate": "2018-11-29T00:00:00Z", "timezone": "Australia/Sydney", "language": "en" @@ -124,65 +124,53 @@ server. Please refer to the [Documentation section](#documentation) below. "type": "ers", "sourceType": "aRMT", "appPackage": "org.phidatalab.radar_armt", - "scheduledTime": "2022-02-23T09:04:00Z", - "additionalData": { - "questionnaire":"{\"name\":\"ers\",\"questionnaire\":{\"avsc\":\"questionnaire\",\"name\":\"ers\",\"repository\":\"https://raw.githubusercontent.com/RADAR-CNS/RADAR-REDCap-aRMT-Definitions/master/questionnaires/\"},\"protocol\":{\"clinicalProtocol\":null,\"completionWindow\":{\"amount\":1440,\"unit\":\"minutes\"},\"notification\":{\"title\":{\"en\":\"Questionnaire Time\"},\"text\":{\"en\":\"Urgent Questionnaire Pending. Please complete now.\"}},\"reminders\":{\"repeat\":0,\"amount\":0,\"unit\":\"day\"},\"repeatProtocol\":{\"amount\":9999999999,\"unit\":\"minutes\"},\"repeatQuestionnaire\":{\"unitsFromZero\":[0],\"unit\":\"minutes\"}},\"referenceTimestamp\":1645607040.000000000,\"showInCalendar\":true,\"showIntroduction\":false,\"estimatedCompletionTime\":1,\"order\":0,\"isDemo\":false,\"startText\":{},\"endText\":{},\"warn\":{}}", - "action":"QUESTIONNAIRE_TRIGGER", - "metadata":"\"{}\"" - - } + "scheduledTime": "2022-02-23T09:04:00Z" } ``` ### Quickstart -The same result as stated in [Getting Started](#getting-started) can be achieved using REST endpoints of the AppServer. - -1. Run the AppServer by following the first 3 steps in the [Getting Started](#getting-started) section. +1. Run the AppServer by following the steps in [Getting Started](#getting-started). -2. Create a new Project by making a `POST` request to the endpoint `http://localhost:8080/projects` with the following - body- +2. Create a new Project by making a `POST` request to `http://localhost:8080/projects/{projectId}`: ```json - { - "projectId": "radar" - } + { + "projectId": "radar" + } ``` -3. Create a new User in the Project by making a `POST` request to the - endpoint `http://localhost:8080/project/test/users` with the following body- +3. Create a new User in the Project by making a `POST` request to + `http://localhost:8080/projects/radar/users/sub-1`: ```json - { - "subjectId": "sub-1", - "fcmToken" : "get-this-from-the-device", - "enrolmentDate": "2019-07-29T00:00:00Z", - "timezone": 7200, - "language": "en" - } + { + "subjectId": "sub-1", + "fcmToken": "get-this-from-the-device", + "enrolmentDate": "2019-07-29T00:00:00Z", + "timezone": "Europe/London", + "language": "en" + } ``` - **Note:** You will need to get the FCM token from the device and the app. Please see - the [setup info](https://firebase.google.com/docs/cloud-messaging) for your platform. + **Note:** You will need to get the FCM token from the device and the app. See + the [FCM setup info](https://firebase.google.com/docs/cloud-messaging) for your platform. -4. Add (and schedule) a notification for the above user by making a `POST` request to the - endpoint `http://localhost:8080/project/test/users/sub-1/notifications` with the following body- +4. Schedule a notification for the user by making a `POST` request to + `http://localhost:8080/projects/radar/users/sub-1/messaging/notifications`: ```json - { - "title" : "Test Title", - "body": "Test Body", - "ttlSeconds": 86400, - "sourceId": "z", - "fcmMessageId": "12864132148", - "type": "ESM", - "sourceType": "aRMT", - "appPackage": "aRMT", - "scheduledTime": "2019-06-29T15:25:58.054Z" - } + { + "title": "Test Title", + "body": "Test Body", + "ttlSeconds": 86400, + "sourceId": "z", + "type": "ESM", + "sourceType": "aRMT", + "appPackage": "aRMT", + "scheduledTime": "2025-06-29T15:25:58.054Z" + } ``` - Please update the `scheduledTime` to the desired time of notification delivery. + Update the `scheduledTime` to the desired time of notification delivery. -5. You will now receive a notification at the `scheduledTime` for the App and device associated with the FCM token for - the user. - There are other features provided via the REST endpoints. These can be explored using swagger-ui. Please refer - to [Documentation](#documentation) section. +5. You will receive a notification at the `scheduledTime` on the device associated with the FCM token. + Explore other features via the OpenAPI spec — see [Documentation](#documentation). ## FCM @@ -193,123 +181,113 @@ Firebase [documentation](https://firebase.google.com/docs/admin/setup#initialize variable (`GOOGLE_APPLICATION_CREDENTIALS`). In the properties file, you would need to set `fcmserver.fcmsender` to `org.radarbase.fcm.downstream.AdminSdkFcmSender`. -## Docker/ Docker Compose +## Docker / Docker Compose -The AppServer is also available as a docker container. Its [Dockerfile](/Dockerfile) is provided with the project. It -can be run as follows - +The AppServer is available as a Docker container. ```shell - docker run -v /logs/:/var/log/radar/appserver/ \ - -v etc/google-credentials.json:/etc/google-credentials.json \ - -e "GOOGLE_APPLICATION_CREDENTIALS=/etc/google-credentials.json" \ - radarbase/radar-appserver:1.1.0 +docker run -v /logs/:/var/log/radar/appserver/ \ + -v etc/google-credentials.json:/etc/google-credentials.json \ + -e "GOOGLE_APPLICATION_CREDENTIALS=/etc/google-credentials.json" \ + radarbase/radar-appserver:2.4.3 ``` -Make sure to have the correct path to the google-credentials.json file. +Make sure to have the correct path to the `google-credentials.json` file. -The same can be achieved by running as a docker-compose service. Just specify the following in `docker-compose.yml` -file - +The same can be achieved by running as a Docker Compose service. Specify the following in your `docker-compose.yml`: ```yml - services: - appserver: - image: radarbase/radar-appserver:1.1.0 - restart: always - ports: - - 8080:8080 - volumes: - - ./radar-is.yml:/resources/radar-is.yml - - ./logs/:/var/log/radar/appserver/ - - ./etc/google-credentials.json:/etc/google-credentials.json - environment: - JDK_JAVA_OPTIONS: -Xmx4G -Djava.security.egd=file:/dev/./urandom - GOOGLE_APPLICATION_CREDENTIALS: /etc/google-credentials.json - RADAR_ADMIN_USER: "radar" - RADAR_ADMIN_PASSWORD: "radar" - SPRING_APPLICATION_JSON: '{"spring":{"boot":{"admin":{"client":{"url":"http://spring-boot-admin:1111","username":"radar","password":"appserver"}}}}}' - RADAR_IS_CONFIG_LOCATION: "/resources/radar-is.yml" - SPRING_BOOT_ADMIN_CLIENT_INSTANCE_NAME: radar-appserver +services: + appserver: + image: radarbase/radar-appserver:2.4.3 + restart: always + ports: + - 8080:8080 + volumes: + - ./logs/:/var/log/radar/appserver/ + - ./etc/google-credentials.json:/etc/google-credentials.json + environment: + GOOGLE_APPLICATION_CREDENTIALS: /etc/google-credentials.json + JDK_JAVA_OPTIONS: -Xmx4G -Djava.security.egd=file:/dev/./urandom ``` -An example `docker-compose` file with all the other components is provided -in [integrationTest resources](/src/integrationTest/resources/docker/appserver_dockerhub/docker-compose.yml). +For microservices deployment with Docker Compose, see [microservices/docker-compose.yml](microservices/docker-compose.yml). ## Architecture Here is a high level architecture and data flow diagram for the AppServer and its example interaction with a Cordova application (hybrid) like the [RADAR-Questionnaire](https://github.com/RADAR-base/RADAR-Questionnaire). -```text - - - - - - - - - - - - ┌───────────────────┐ Downstream - │Device (Google Play│◀─────────────────────────────────Message .───────────. - │ Services/Apple │ │ _.─' `──. - │ IPNS) │ └───────────────────────────────────────,' `. - └────────▲────┬─────┴─────────────────────────────────────┐ ,' `. - │ │ │ ╱ ╲ - │ │ XMPP ; : - │ │ Upstream │ Firebase Cloud Messaging │ - .┴────▼─. Message───────────────────────────────────▶│ Service │ - ,' `. : ; - ; Native Code : ╲ ╱ - :(IOS/Android); ╲ ╱ - ╲ ╱ `. ,' - `▲ ,' `. ,' - │`─────│ ▲`──. _.─' - │ │ │ `────────'▲ - │ │ │ - │ │ │ ┃ - │ │ │ - │ │ │ ┃ - │──────▼. Send - ,─' '─. downstream ┃ - ╱ Cordova FCM ╲ Message at - ; Plugin : Scheduled ┃ - : ; Time - ╲ ╱ │ ┃ - ╲ ╱ │ FCM Admin - '─▲ ,─' │ SDK (Only - │`─────'│ │ downstream - │ │ │ messaging) - │ │ │ - │ │ │ ┃ - │ │ │ - │ │ ┌────┴───────▼──────┻─────── ▼────┐ - │ │ │ │ - ┌─────┴───────▼─────────┐ │ │ - │ │ ┌───────────────────────────────────▶ │ - │ │ Schedule message │ │ - │ │ for future delivery │ New App Server │ - │ │ using HTTP REST │ │ - │ ├───────────────────────────────────┘ │ (HTTP Protocol) │ - │ CORDOVA APPLICATION │ │ (REST API and FCM Admin SDK) │ - │ │ Get confirmation of ─────────────────────────┤ │ - │ ◀───────────────────────success for each request. │ │ - │ │ │ │ - │ │ ┌───────────────────────────────────▶ │ - │ │ │ │ │ - │ │ Get/Set user metrics, │ │ - │ ├───────────────────schedule, notifications, etc │ │ - │ │ │ │ - │ │ More │ │ - │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ functionality ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶ │ - └───────────────────────┘ ..... └─────────────────────────────────┘ -``` +```text + + + + + + + + + + + ┌───────────────────┐ Downstream + │Device (Google Play│◀─────────────────────────────────Message .───────────. + │ Services/Apple │ │ _.─' `──. + │ IPNS) │ └───────────────────────────────────────,' `. + └────────▲────┬─────┴─────────────────────────────────────┐ ,' `. + │ │ │ ╱ ╲ + │ │ XMPP ; : + │ │ Upstream │ Firebase Cloud Messaging │ + .┴────▼─. Message───────────────────────────────────▶│ Service │ + ,' `. : ; + ; Native Code : ╲ ╱ + :(IOS/Android); ╲ ╱ + ╲ ╱ `. ,' + `▲ ,' `. ,' + │`─────│ ▲`──. _.─' + │ │ │ `────────'▲ + │ │ │ + │ │ │ ┃ + │ │ │ + │ │ │ ┃ + │──────▼. Send + ,─' '─. downstream ┃ + ╱ Cordova FCM ╲ Message at + ; Plugin : Scheduled ┃ + : ; Time + ╲ ╱ │ ┃ + ╲ ╱ │ FCM Admin + '─▲ ,─' │ SDK (Only + │`─────'│ │ downstream + │ │ │ messaging) + │ │ │ + │ │ │ ┃ + │ │ │ + │ │ ┌────┴───────▼──────┻─────── ▼────┐ + │ │ │ │ + ┌─────┴───────▼─────────┐ │ │ + │ │ ┌───────────────────────────────────▶ │ + │ │ Schedule message │ │ + │ │ for future delivery │ New App Server │ + │ │ using HTTP REST │ │ + │ ├───────────────────────────────────┘ │ (HTTP Protocol) │ + │ CORDOVA APPLICATION │ │ (REST API and FCM Admin SDK) │ + │ │ Get confirmation of ─────────────────────────┤ │ + │ ◀───────────────────────success for each request. │ │ + │ │ │ │ + │ │ ┌───────────────────────────────────▶ │ + │ │ │ │ │ + │ │ Get/Set user metrics, │ │ + │ ├───────────────────schedule, notifications, etc │ │ + │ │ │ │ + │ │ More │ │ + │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ functionality ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶ │ + └───────────────────────┘ ..... └─────────────────────────────────┘ +``` ## Notification Lifecycle -The Appserver manages the lifecycle of the Notifications through state change events. It uses Pub/Sub paradigm utilising -Spring Events so other subscribers can also hook up to the Events as listeners. Currently, there are 10 possible states +The Appserver manages the lifecycle of the Notifications through state change events. It uses Pub/Sub paradigm so +other subscribers can also hook up to the Events as listeners. Currently, there are 10 possible states as follows - ```text @@ -325,7 +303,7 @@ as follows - // Miscellaneous ERRORED, UNKNOWN -``` +``` REST Endpoints are provided to update and query the STATE. Update can only be made to any of the ones above that can be updated by external entities(i.e. DELIVERED, OPENED, DISMISSED, ERRORED and UNKNOWN ). @@ -366,10 +344,9 @@ Here is a simple flow between the states -- │ │ `. ,' │ │ └───────────────┘ `───. _.──' │ │ │ `───────────────' │ │ - │ ▲ │ │ - │ │ │ ▼ - └──────────────────────────────────┘ │ .─────────────────. - │ _.──' `───. + │ ▲ │ ▼ + │ │ │ .─────────────────. + └──────────────────────────────────┘ │ _.──' `───. │ ╱ ╲ └───────────( DELIVERED ) `. ,' @@ -396,56 +373,61 @@ Here is a simple flow between the states -- The AppServer has support for providing Protocols for the [RADAR-Questionnaire](https://github.com/RADAR-base/RADAR-Questionnaire) application. Currently, one strategy for -getting the protocols from Github(Take a look -at [RADAR-aRMT-protocols](https://github.com/RADAR-base/RADAR-aRMT-protocols/)) is provided. The AppServer also caches -the protocols, so they are still available if there are any issues with GitHub. Later, we intend to extend this -functionality to add protocols directly in the AppServer possibly by a UI. -You can host your own protocols and configure the following properties - +getting the protocols from GitHub (see [RADAR-aRMT-protocols](https://github.com/RADAR-base/RADAR-aRMT-protocols/)) is provided. The AppServer also caches the protocols, so they are still available if there are any issues with GitHub. | Property | Description | Default | Required? | |:---------------------------------------------:|----------------------------------------------------------------|:---------------------------------:|:---------:| -| radar.questionnaire.protocol.github.repo.path | The Github repo where protocols are hosted. | `RADAR-base/RADAR-aRMT-protocols` | No | +| radar.questionnaire.protocol.github.repo.path | The GitHub repo where protocols are hosted. | `RADAR-base/RADAR-aRMT-protocols` | No | | radar.questionnaire.protocol.github.file.name | The filename containing the Protocol for each Project. | `protocol.json` | No | | radar.questionnaire.protocol.github.branch | The Branch of the Repository from which to fetch the protocols | `master` | No | -## Email notifications +## Email Notifications -By default, appserver sends push notifications to mobile devices. Optionally, Notifications can be sent via email in -addition. To enable email notifications, you need to set the following properties in the `application.properties` file: +By default, the appserver sends push notifications to mobile devices. Optionally, notifications can be sent via email +in addition. To enable email notifications, configure the `email` section in `appserver.yml`: -1. set the `radar.notification.email.enabled` property to `true`. -2. set the `radar.notification.email.from` property to the email address from which the notifications will be sent. -3. set [Spring Boot mail properties](https://docs.spring.io/spring-boot/reference/io/email.html) to configure the email - server. +1. Set `enabled` to `true`. +2. Set `fromAddress` to the email address from which notifications will be sent. +3. Configure the SMTP server settings. -Example: +Example `appserver.yml` configuration: -```properties -radar.notification.email.enabled=true -radar.notification.email.from=no-reply@radar.org -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username=my_username -spring.mail.password=my_password -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true +```yaml +email: + enabled: true + smtpHost: smtp.gmail.com + smtpPort: 587 + smtpUser: my_username + smtpPassword: my_password + fromAddress: no-reply@radar.org + enableTls: true ``` +These can also be set via environment variables: + +| Environment Variable | Description | +|---|---| +| `RADAR_APPSERVER_NOTIFICATION_EMAIL_ENABLED` | Enable email notifications (`true`/`false`) | +| `RADAR_APPSERVER_NOTIFICATION_EMAIL_FROM` | Sender email address | +| `RADAR_APPSERVER_EMAIL_SMTP_HOST` | SMTP server host | +| `RADAR_APPSERVER_EMAIL_SMTP_PORT` | SMTP server port | +| `RADAR_APPSERVER_EMAIL_SMTP_USERNAME` | SMTP username | +| `RADAR_APPSERVER_EMAIL_SMTP_PASSWORD` | SMTP password | +| `RADAR_APPSERVER_EMAIL_TLS_ENABLED` | Enable TLS (`true`/`false`) | + In addition, in the notification scheduling request set the following fields: -- _emailEnabled_ with value _true_. -- (optional) _emailTitle_: subject of the email (notification title will be used when not specified) -- (optional) _emailBody_: body of the email (notification text will be used when not specified) +- `emailEnabled` with value `true`. +- (optional) `emailTitle`: subject of the email (notification title will be used when not specified). +- (optional) `emailBody`: body of the email (notification text will be used when not specified). Example of a request body (partial) to the notification scheduling endpoint: ```json { - ... "emailEnabled": true, "emailTitle": "My email title", - "emailBody": "My email body", - ... + "emailBody": "My email body" } ``` @@ -453,239 +435,158 @@ Note: HTML email is not supported at the moment of this writing. ## Documentation -Api docs are available through swagger open api 3 config. -The raw json is present at the ``. By default this should -be `http://localhost:8080/v3/api-docs`. This will provide the specification in JSON format. If `YAML` format is -preferred, you can query `http://localhost:8080/v3/api-docs.yaml` - -The Swagger UI is shown below. -It is present at `` as shown below - - -![java documentation](/images/java-docs.png "Java Docs") +API docs are available through OpenAPI (Swagger): +- **JSON**: `http://localhost:8080/openapi.json` +- **YAML**: `http://localhost:8080/openapi.yaml` -## Client - -You can generate a client in 40 different languages for the api -using [Swagger Codegen](https://swagger.io/tools/swagger-codegen/) tool. There is even -a [javascript library](https://github.com/swagger-api/swagger-codegen#where-is-javascript) that is completely dynamic -and does not require static code generation. +Each microservice also exposes its own OpenAPI spec at its respective base URL. ## Security -By Default, no OAuth 2.0 security is enabled for the endpoints. Only basic Auth is present on Admin endpoints. -To enable security of specific provider, please read the sections below. - ### Management Portal -To enable security via the [RADAR Management Portal](https://github.com/RADAR-base/ManagementPortal), set the following -property - +To enable security via the [RADAR Management Portal](https://github.com/RADAR-base/ManagementPortal), configure the +`auth` section in `appserver.yml`: -```ini -security.radar.managementportal.enabled=true -security.radar.managementportal.url= +```yaml +auth: + managementPortalUrl: http://localhost:8081/managementportal + resourceName: res_AppServer ``` -This will instantiate all the classes needed for security using the management portal. Per endpoint level auth is -controlled using Pre and Post annotations for each permission. -All the classes are located -in [/src/main/java/org/radarbase/appserver/auth/managementportal](/src/main/java/org/radarbase/appserver/auth/managementportal). +This will instantiate all the classes needed for security using the Management Portal. Per-endpoint authorization is +controlled using `@NeedsPermission` annotations on each resource method. + +The Management Portal URL and client credentials can also be configured via environment variables: -You can provide the Management Portal specific config in [radar-is.yml](radar-is.yml) file providing the public key -endpoint and the resource name. The path to this file should be specified in the env -variable `RADAR_IS_CONFIG_LOCATION`. +| Environment Variable | Description | +|---|---| +| `MANAGEMENT_PORTAL_CLIENT_ID` | OAuth client ID | +| `MANAGEMENT_PORTAL_CLIENT_SECRET` | OAuth client secret | ### Management Portal Clients -If security is enabled, please also make sure that the correct resources and scope are set in the OAuth Client -configurations in Management Portal. +If security is enabled, make sure the correct resources and scope are set in the OAuth Client +configuration in Management Portal. The resource `res_AppServer` and scopes `MEASUREMENT.CREATE,SUBJECT.UPDATE,SUBJECT.READ,PROJECT.READ` must be added to -the `aRMT` client. Please check the `/src/integrationTest/resources/docker/etc/config/oauth_client_details.csv` file for +the `aRMT` client. See `appserver-jersey/src/integrationTest/resources/docker/etc/config/oauth_client_details.csv` for an example. ### Other Security Providers -For using other type of security providers, set `managementportal.security.enabled=false` and configure the security -provider in the spring context and add any necessary classes. See [Management Portal Security](#management-portal) -section for an example. - -Then you will need to change the `Pre` and `Post` Authorise annotations for each endpoint method according to the -semantics provided by your provider. Currently, these are configured to work with Management portal. - -## Monitoring +For using other security providers, configure the security provider in the enhancer factory and modify the authorization annotations on each endpoint method. -The App server has built in support for -the [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready) and -can be accessed via the default REST endpoints `/actuator/*`. To see all the features provided please -refer to -the [official docs](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-endpoints) of -Spring boot actuator. - -It also has functionality to register itself as a client to -the [Spring Boot Admin Server](http://codecentric.github.io/spring-boot-admin/2.1.1/) for providing a beautiful UI for -the Actuator and some other useful admin stuff. -To make this work, - -- Run an instance of the Spring Boot Admin server (various examples on the internet and also via Docker) on the machine - and then - -- configure the client to point to the Admin Server for registration by adding the following to - your `application.properties` file - - ```ini - spring.boot.admin.client.url = http://localhost:8888 - ``` - In this case, the Spring Boot admin server was running on `http://localhost:8888`. If http basic auth is enabled on - the server also add the following to the `application.properties` file - - ```ini - spring.boot.admin.client.url = http://localhost:8888 - spring.boot.admin.client".username = admin-server-username - spring.boot.admin.client".password = admin-server-password - ``` - -The same can be achieved when deployed with the components as microservices in docker containers using docker-compose. -The file [docker-compose.yml](/src/integrationTest/resources/docker/appserver_dockerhub/docker-compose.yml) in this -project shows an example of how this is achieved. -Please note how the App server is configured in the container compared to the method of adding properties -in `application.properties` file shown above. - -Just run - +## Database Migrations -```bash -cd src/integrationTest/resources/docker/appserver_dockerhub/ -sudo docker-compose up -d -``` +This project uses [Liquibase](https://www.liquibase.org/) for database schema management. Liquibase tracks which +schema changes have been applied via a `DATABASECHANGELOG` table in the database, so migrations are only run once and +in order. -Then go to the browser to the URL : `https://localhost:8888` and login with credentials `radar:appserver` to see the -Spring Boot Admin Server in action. +Changelogs are located at: +- **appserver-jersey**: `appserver-jersey/src/main/resources/db/changelog/changes/` +- **microservices**: each service has its own changelogs under `/src/main/resources/db/changelog/changes/` -Deploying the Spring Boot Admin server and the client as different components makes sure that the same Server can be -used to register multiple client apps and the server's lifecycle is not associated with the client. This also means that -our client app is lighter and production ready. +A master changelog (`db.changelog-master.yaml`) uses `includeAll` to pick up all changesets in the `changes/` directory, +sorted by filename. Changesets are named with a prefix (`00000000000000_`, `00000000000001_`, etc.) to control ordering. -### Sentry monitoring +Liquibase is enabled by default in the `appserver.yml` config: -To enable Sentry monitoring: +```yaml +db: + liquibase: + enabled: true + changelogs: db/changelog/db.changelog-master.yaml + additionalProperties: + hibernate.hbm2ddl.auto: validate +``` -1. Add the `sentry` profile to active spring profiles. -2. Set a `SENTRY_DSN` environment variable that points to the desired Sentry DSN. -3. (Optional) Set the `SENTRY_LOG_LEVEL` environment variable to control the minimum log level of events sent to Sentry. - The default log level for Sentry is `ERROR`. Possible values are `TRACE`, `DEBUG`, `INFO`, `WARN`, and `ERROR`. +Hibernate runs in `validate` mode — it checks that the schema matches the entity definitions but does not modify the +schema. All schema changes must go through Liquibase changelogs. -For further configuration of Sentry via environmental variables see [here](https://docs.sentry.io/platforms/java/configuration/#configuration-via-the-runtime-environment). For instance: +For H2 development setups, Liquibase can be disabled and Hibernate can manage the schema directly: -``` -SENTRY_LOG_LEVEL: 'ERROR' -SENTRY_DSN: 'https://000000000000.ingest.de.sentry.io/000000000000' -SENTRY_ATTACHSTACKTRACE: true -SENTRY_STACKTRACE_APP_PACKAGES: org.radarbase.appserver +```yaml +db: + jdbcDriver: org.h2.Driver + jdbcUrl: jdbc:h2:mem:dev + hibernateDialect: org.hibernate.dialect.H2Dialect + liquibase: + enabled: false + additionalProperties: + jakarta.persistence.schema-generation.database.action: drop-and-create ``` -## Performance Testing +### Adding new schema changes -The app server supports performance testing using Gatling and Scala. The simulations are located in the -folder `src/gatling/simulations`. +1. Create a new changelog file in the `changes/` directory following the naming convention: + `00000000000003_update_schema-_changelog.yml` +2. Add rollback definitions for reversibility. +3. On startup, Liquibase will automatically detect and apply new changesets. -First run the application and then run the gatling test using the command - +Migration scripts for moving data between deployment modes (jersey to microservices) are available in `scripts/migration/`. -```bash - ./gradlew gatlingRun -``` +## Code Quality and Testing -To run a particular simulation you can run it like follows - +Code quality checks (ktlint) and tests can be run with: ```bash - ./gradlew gatlingRun-org.radarbase.appserver.BatchingApiGatlingSimulationTest -``` - -You can modify the number of iterations of the requests by change the variables the the top of the -file `src/gatling/simulations/org/radarbase/appserver/ApiGatlingSimulationTest.scala` like - - -```scala - val numOfProjects = 5 - val numOfUsersPerProject = 300 - val numOfNotificationsPerUser = 300 - val numOfSimultaneousClients = 2 +./gradlew check ``` -You can also edit the base URL of your deployed instance of the server by changing the value of +This will run linting, unit tests and integration tests. Reports are generated in the `build/reports` folder. -```scala - val baseUrl = "http://localhost:8080" -``` +### Unit Tests -Ideally deploy the server on a remote instance using a persisted database instead of in-memory for real-world testing. -Running on your local machine may not reflect the best results. - -The reports will be generated at the path `build/reports/gatling/` and the folders will be -named `apigatlingsimulationtest-{date-time}`. Open the folder with the correct date time and open the `index.html` file -in a browser to view the results. Example result is shown in the screengrab below- +```bash +# Monolith +./gradlew :appserver-jersey:test -![gatling test](/images/gatling-results.png "Gatling Test Results") +# Microservices +./gradlew :microservices::test +``` -## Code-Style and Quality +### Integration Tests -Various tools are enabled to ensure code quality and styling while also doing static code analysis for bugs. PMD, -CheckStyle is included and all of these can be run with the command - +Integration tests are provided for both the monolith and microservices. They use a running instance of +Management Portal to obtain a valid client token and verify access to resources. ```bash -./gradlew check +# Monolith — starts required services via Docker Compose, then runs tests +./gradlew :appserver-jersey:composeUp +./gradlew :appserver-jersey:integrationTest + +# Microservices +./gradlew :microservices:integration-tests:composeUp +./gradlew :microservices:integration-tests:integrationTest ``` -**Note:** This will also run the test and integrationTest. +The integration tests are located at: +- **appserver-jersey**: `appserver-jersey/src/integrationTest/` +- **microservices**: `microservices/integration-tests/src/integrationTest/` -The reports are generated in the `build/reports` folder. The config files for rules are present in the `config` folder. -A style template following the Google Java style guidelines is also provided for use with IntellJ -Idea ([style plugin](https://plugins.jetbrains.com/plugin/8527-google-java-format)) in `config/codestyles` folder. +The tests use Management Portal as the security provider. The OAuth helper +(`MpOAuthSupport`) retrieves a valid access token from Management Portal for authenticating test requests. -## Unit and Integration Testing +## Monitoring -[Unit Tests](/src/test/java/org/radarbase/appserver) -and [Integration Tests](/src/integrationTest/java/org/radarbase/appserver) are provided with the AppServer. These can be -run as follows- +### Sentry -```bash -# For Unit tests -./gradlew test -``` +To enable Sentry monitoring, set the `SENTRY_DSN` environment variable: -```bash -# For Integration Tests -./gradlew integrationTest ``` - -The integration tests are currently provided for Management Portal as the security provider and uses a running instance -of Management Portal to get a valid client token and provide access to resources. -To change the security provider implement the -interface [OAuthHelper](/src/integrationTest/java/org/radarbase/appserver/auth/common/OAuthHelper.java) and provide a -valid Access Token in `getAccessToken()` using your security provider. -Then just use this instance in static Initialisation of the Tests. For more info take a look -at [MPOAuthHelper](/src/integrationTest/java/org/radarbase/appserver/auth/common/MPOAuthHelper.java). - -You can also run all the checks and tests in a single command using - - -```bash -./gradlew check +SENTRY_DSN: 'https://000000000000.ingest.de.sentry.io/000000000000' +SENTRY_ATTACHSTACKTRACE: true +SENTRY_STACKTRACE_APP_PACKAGES: org.radarbase.appserver ``` -This will run checkstyle, PMD, spot bugs, unit tests and integration tests. - ## Current Features -- Provides a general purpose FCM library with facility to send messages with Admin SDK support. -- Provides functionality of scheduling notifications via FCM. -- Acts as a data store for important user and app related data (like FCM token to subject mapping, notifications, user - metrics, etc). -- Can be easily extended for different apps. -- Uses [Liquibase](https://www.liquibase.org/) for easy evolution of database. -- Contains swagger integration for easy API documentation and generation of Java client. -- Uses [lombok.data](https://projectlombok.org/) in most places to reduce boilerplate code and make it more readable. -- Has support for Auditing of database entities. +- FCM push notification scheduling via Admin SDK +- Data store for user and app data (FCM token mapping, notifications, user metrics) +- Questionnaire protocol management (fetched from GitHub) +- Email notification support +- Database schema management via Liquibase +- OpenAPI/Swagger API documentation +- Sentry monitoring integration +- Management Portal authentication +- Docker and Docker Compose deployment diff --git a/appserver-jersey/README.md b/appserver-jersey/README.md new file mode 100644 index 00000000..83625430 --- /dev/null +++ b/appserver-jersey/README.md @@ -0,0 +1,61 @@ +# RADAR-Appserver Jersey + +Monolith deployment of the RADAR-Appserver using Jersey (JAX-RS) and Hibernate. This replaces the original Spring Boot application with the same functionality and database schema. + +## Running + +```bash +# Build +./gradlew :appserver-jersey:build + +# Run +./gradlew :appserver-jersey:run +``` + +The server starts at `http://localhost:8080/` by default. + +## Configuration + +Configuration is in `src/main/resources/appserver.yml`: + +```yaml +server: + baseUri: http://0.0.0.0:8080/ + +auth: + managementPortalUrl: http://localhost:8081/managementportal + resourceName: res_AppServer + +db: + jdbcUrl: jdbc:postgresql://localhost:5432/appserver + username: radar + password: radar + +email: + enabled: false +``` + +Environment variables can override config values (e.g., `APPSERVER_JDBC_URL`). + +## Database + +Uses a single PostgreSQL database (`appserver`). Schema is managed by Liquibase changelogs in `src/main/resources/db/changelog/`. + +The changelogs include all 20 original changesets from the Spring Boot era, so migrating from the original app to Jersey is an in-place operation (Liquibase only applies new changesets). + +## API Documentation + +OpenAPI spec and Swagger UI are available at runtime: +- Swagger UI: `http://localhost:8080/swagger` +- OpenAPI YAML: `http://localhost:8080/openapi.yaml` +- OpenAPI JSON: `http://localhost:8080/openapi.json` + +## Docker + +```bash +docker build -t radar-appserver . +docker run -p 8080:8080 \ + -e GOOGLE_APPLICATION_CREDENTIALS=/etc/google-credentials.json \ + -v ./google-credentials.json:/etc/google-credentials.json \ + radar-appserver +``` diff --git a/microservices/README.md b/microservices/README.md new file mode 100644 index 00000000..0915c937 --- /dev/null +++ b/microservices/README.md @@ -0,0 +1,81 @@ +# RADAR-Appserver Microservices + +Decomposed deployment of the RADAR-Appserver. Each service runs independently with its own database and communicates via HTTP contracts. + +## Services + +| Service | Port | Database | +|-----------------------------|------|-----------------------------| +| **gateway-service** | 8080 | — | +| **project-service** | 9010 | `appserver_project` | +| **github-service** | 9011 | — | +| **protocol-service** | 9012 | — | +| **user-service** | 9013 | `appserver_user` | +| **task-service** | 9014 | `appserver_task` | +| **cloud-messaging-service** | 9015 | `appserver_cloud_messaging` | + +## Running + +### With Docker Compose + +```bash +cd microservices +docker-compose up -d +``` + +This starts all services and their PostgreSQL instances. + +### Locally (individual service) + +```bash +# Start databases first +docker-compose up -d project-service-db user-service-db task-service-db cloud-messaging-service-db + +# Run a service +./gradlew :microservices:project-service:run +``` + +### Build All + +```bash +./gradlew :microservices:build +``` + +## API Documentation + +The gateway service exposes the public API at `http://localhost:8080`. Each service exposes Swagger UI and OpenAPI specs: + +| Service | Swagger UI | OpenAPI spec | +|-----------------------------|---------------------------------|--------------------------------------| +| **gateway-service** | `http://localhost:8080/swagger` | `http://localhost:8080/openapi.yaml` | +| **project-service** | `http://localhost:9010/swagger` | `http://localhost:9010/openapi.yaml` | +| **github-service** | `http://localhost:9011/swagger` | `http://localhost:9011/openapi.yaml` | +| **protocol-service** | `http://localhost:9012/swagger` | `http://localhost:9012/openapi.yaml` | +| **user-service** | `http://localhost:9013/swagger` | `http://localhost:9013/openapi.yaml` | +| **task-service** | `http://localhost:9014/swagger` | `http://localhost:9014/openapi.yaml` | +| **cloud-messaging-service** | `http://localhost:9015/swagger` | `http://localhost:9015/openapi.yaml` | + +JSON specs are also available at `/openapi.json`. + +## Inter-Service Communication + +Services communicate via HTTP using contracts defined in the `contract` module: + +``` +gateway → project-service +gateway → user-service +gateway → task-service +gateway → cloud-messaging-service +gateway → protocol-service +gateway → github-service +user-service → project-service +protocol-service → github-service +``` + +## Integration Tests + +```bash +./gradlew :microservices:integration-tests:integrationTest +``` + +This spins up the full stack via Docker Compose and runs integration tests. From 4035ba86ef6f0ef8304f793250c3630bb7e85be8 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 16:08:10 +0000 Subject: [PATCH 02/13] Use gradle without daemon in dockerfiles and disable liquibase analytics --- Dockerfile.appserver-jersey | 8 ++++---- .../cloud-messaging-service/Dockerfile | 18 +++++++++++------- microservices/gateway-service/Dockerfile | 18 +++++++++++------- microservices/github-service/Dockerfile | 16 ++++++++++------ microservices/project-service/Dockerfile | 18 ++++++++++-------- microservices/protocol-service/Dockerfile | 16 ++++++++++------ microservices/task-service/Dockerfile | 16 ++++++++++------ microservices/user-service/Dockerfile | 16 ++++++++++------ 8 files changed, 76 insertions(+), 50 deletions(-) diff --git a/Dockerfile.appserver-jersey b/Dockerfile.appserver-jersey index 0d0afc1a..f898df42 100644 --- a/Dockerfile.appserver-jersey +++ b/Dockerfile.appserver-jersey @@ -22,11 +22,11 @@ COPY ./buildSrc /code/buildSrc COPY ./build.gradle.kts ./settings.gradle.kts /code/ COPY appserver-jersey/build.gradle.kts /code/appserver-jersey/ -RUN gradle appserver-jersey:downloadDependencies appserver-jersey:copyDependencies appserver-jersey:startScripts +RUN gradle --no-daemon appserver-jersey:downloadDependencies appserver-jersey:copyDependencies appserver-jersey:startScripts COPY appserver-jersey/src/ /code/appserver-jersey/src -RUN gradle jar +RUN gradle --no-daemon :appserver-jersey:jar FROM eclipse-temurin:17-jre @@ -35,10 +35,10 @@ COPY --from=builder /code/appserver-jersey/build/third-party/* /usr/lib/ COPY --from=builder /code/appserver-jersey/build/libs/*.jar /usr/lib/ COPY --from=builder /code/appserver-jersey/src/main/resources/appserver.yml /etc/appserver-jersey/appserver.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 8080 CMD ["appserver-jersey"] - - diff --git a/microservices/cloud-messaging-service/Dockerfile b/microservices/cloud-messaging-service/Dockerfile index 9d215149..e1951c00 100644 --- a/microservices/cloud-messaging-service/Dockerfile +++ b/microservices/cloud-messaging-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/cloud-messaging-service/build.gradle.kts /code/microservices/cloud-messaging-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:cloud-messaging-service:downloadDependencies \ - microservices:cloud-messaging-service:copyDependencies \ - microservices:cloud-messaging-service:startScripts + :microservices:cloud-messaging-service:downloadDependencies \ + :microservices:cloud-messaging-service:copyDependencies \ + :microservices:cloud-messaging-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/cloud-messaging-service/src/ /code/microservices/cloud-messaging-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:cloud-messaging-service:jar FROM eclipse-temurin:17-jre @@ -28,8 +30,10 @@ COPY --from=builder /code/microservices/cloud-messaging-service/build/third-part COPY --from=builder /code/microservices/cloud-messaging-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/cloud-messaging-service/src/main/resources/cloud-messaging-service.yml /etc/cloud-messaging-service/cloud-messaging-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 -EXPOSE 9014 +EXPOSE 9015 ENTRYPOINT ["cloud-messaging-service"] diff --git a/microservices/gateway-service/Dockerfile b/microservices/gateway-service/Dockerfile index 03c5d9b5..0b5bc2f4 100644 --- a/microservices/gateway-service/Dockerfile +++ b/microservices/gateway-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/gateway-service/build.gradle.kts /code/microservices/gateway-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:gateway-service:downloadDependencies \ - microservices:gateway-service:copyDependencies \ - microservices:gateway-service:startScripts + :microservices:gateway-service:downloadDependencies \ + :microservices:gateway-service:copyDependencies \ + :microservices:gateway-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/gateway-service/src/ /code/microservices/gateway-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:gateway-service:jar FROM eclipse-temurin:17-jre @@ -28,8 +30,10 @@ COPY --from=builder /code/microservices/gateway-service/build/third-party/* /usr COPY --from=builder /code/microservices/gateway-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/gateway-service/src/main/resources/gateway-service.yml /etc/gateway-service/gateway-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 8080 -CMD ["gateway-service"] +ENTRYPOINT ["gateway-service"] diff --git a/microservices/github-service/Dockerfile b/microservices/github-service/Dockerfile index 5f8bc8f6..a0c463ef 100644 --- a/microservices/github-service/Dockerfile +++ b/microservices/github-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/github-service/build.gradle.kts /code/microservices/github-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:github-service:downloadDependencies \ - microservices:github-service:copyDependencies \ - microservices:github-service:startScripts + :microservices:github-service:downloadDependencies \ + :microservices:github-service:copyDependencies \ + :microservices:github-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/github-service/src/ /code/microservices/github-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:github-service:jar FROM eclipse-temurin:17-jre @@ -28,6 +30,8 @@ COPY --from=builder /code/microservices/github-service/build/third-party/* /usr/ COPY --from=builder /code/microservices/github-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/github-service/src/main/resources/github-service.yml /etc/github-service/github-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 9011 diff --git a/microservices/project-service/Dockerfile b/microservices/project-service/Dockerfile index 85f40d3f..f3fa8773 100644 --- a/microservices/project-service/Dockerfile +++ b/microservices/project-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/project-service/build.gradle.kts /code/microservices/project-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:project-service:downloadDependencies \ - microservices:project-service:copyDependencies \ - microservices:project-service:startScripts + :microservices:project-service:downloadDependencies \ + :microservices:project-service:copyDependencies \ + :microservices:project-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/project-service/src/ /code/microservices/project-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:project-service:jar FROM eclipse-temurin:17-jre @@ -28,10 +30,10 @@ COPY --from=builder /code/microservices/project-service/build/third-party/* /usr COPY --from=builder /code/microservices/project-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/project-service/src/main/resources/project-service.yml /etc/project-service/project-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 9010 ENTRYPOINT ["project-service"] - -#CMD ["project-service"] diff --git a/microservices/protocol-service/Dockerfile b/microservices/protocol-service/Dockerfile index 5f88d05a..4ee4b8a7 100644 --- a/microservices/protocol-service/Dockerfile +++ b/microservices/protocol-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/protocol-service/build.gradle.kts /code/microservices/protocol-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:protocol-service:downloadDependencies \ - microservices:protocol-service:copyDependencies \ - microservices:protocol-service:startScripts + :microservices:protocol-service:downloadDependencies \ + :microservices:protocol-service:copyDependencies \ + :microservices:protocol-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/protocol-service/src/ /code/microservices/protocol-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:protocol-service:jar FROM eclipse-temurin:17-jre @@ -28,6 +30,8 @@ COPY --from=builder /code/microservices/protocol-service/build/third-party/* /us COPY --from=builder /code/microservices/protocol-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/protocol-service/src/main/resources/protocol-service.yml /etc/protocol-service/protocol-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 9012 diff --git a/microservices/task-service/Dockerfile b/microservices/task-service/Dockerfile index 411d86be..610368f4 100644 --- a/microservices/task-service/Dockerfile +++ b/microservices/task-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/task-service/build.gradle.kts /code/microservices/task-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:task-service:downloadDependencies \ - microservices:task-service:copyDependencies \ - microservices:task-service:startScripts + :microservices:task-service:downloadDependencies \ + :microservices:task-service:copyDependencies \ + :microservices:task-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/task-service/src/ /code/microservices/task-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:task-service:jar FROM eclipse-temurin:17-jre @@ -28,6 +30,8 @@ COPY --from=builder /code/microservices/task-service/build/third-party/* /usr/li COPY --from=builder /code/microservices/task-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/task-service/src/main/resources/task-service.yml /etc/task-service/task-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 9014 diff --git a/microservices/user-service/Dockerfile b/microservices/user-service/Dockerfile index d6077d94..bc76c5bd 100644 --- a/microservices/user-service/Dockerfile +++ b/microservices/user-service/Dockerfile @@ -8,18 +8,20 @@ ENV GRADLE_USER_HOME=/code/.gradlecache \ COPY buildSrc /code/buildSrc COPY build.gradle.kts settings.gradle.kts /code/ +COPY microservices/core/build.gradle.kts /code/microservices/core/ +COPY microservices/contract/build.gradle.kts /code/microservices/contract/ COPY microservices/user-service/build.gradle.kts /code/microservices/user-service/ -COPY microservices/core/ /code/microservices/core/ -COPY microservices/contract /code/microservices/contract RUN gradle --no-daemon --no-parallel -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs="-Xmx1g" \ - microservices:user-service:downloadDependencies \ - microservices:user-service:copyDependencies \ - microservices:user-service:startScripts + :microservices:user-service:downloadDependencies \ + :microservices:user-service:copyDependencies \ + :microservices:user-service:startScripts +COPY microservices/core/src/ /code/microservices/core/src/ +COPY microservices/contract/src/ /code/microservices/contract/src/ COPY microservices/user-service/src/ /code/microservices/user-service/src/ -RUN gradle jar +RUN gradle --no-daemon :microservices:user-service:jar FROM eclipse-temurin:17-jre @@ -28,6 +30,8 @@ COPY --from=builder /code/microservices/user-service/build/third-party/* /usr/li COPY --from=builder /code/microservices/user-service/build/libs/*.jar /usr/lib/ COPY --from=builder /code/microservices/user-service/src/main/resources/user-service.yml /etc/user-service/user-service.yml +ENV LIQUIBASE_ANALYTICS_ENABLED=false + USER 101 EXPOSE 9013 From e0a6e1aa6032c5eecfd3a5c2cf1b7db323b8d911 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 16:13:09 +0000 Subject: [PATCH 03/13] Add health checks for docker compose services --- microservices/docker-compose.yml | 114 ++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/microservices/docker-compose.yml b/microservices/docker-compose.yml index 8875749f..bc2d069a 100644 --- a/microservices/docker-compose.yml +++ b/microservices/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - networks: public: driver: bridge @@ -30,28 +28,36 @@ services: # ManagementPortal Postgres # #---------------------------------------------------------------------------# managementportal-postgresql: - image: postgres + image: postgres:16 environment: POSTGRES_USER: radarbase POSTGRES_PASSWORD: radarbase POSTGRES_DB: managementportal networks: - mp-db + healthcheck: + test: ["CMD-SHELL", "pg_isready -U radarbase -d managementportal"] + interval: 5s + timeout: 5s + retries: 5 #---------------------------------------------------------------------------# # Management Portal # #---------------------------------------------------------------------------# managementportal: image: radarbase/management-portal:2.1.0 + depends_on: + managementportal-postgresql: + condition: service_healthy environment: SERVER_PORT: 8081 SPRING_PROFILES_ACTIVE: prod SPRING_DATASOURCE_URL: jdbc:postgresql://managementportal-postgresql:5432/managementportal SPRING_DATASOURCE_USERNAME: radarbase SPRING_DATASOURCE_PASSWORD: radarbase - SPRING_LIQUIBASE_CONTEXTS: dev #includes testing_data, remove for production builds - JHIPSTER_SLEEP: 10 # gives time for the database to boot before the application - JAVA_OPTS: -Xmx512m # maximum heap size for the JVM running ManagementPortal, increase this as necessary + SPRING_LIQUIBASE_CONTEXTS: dev + JHIPSTER_SLEEP: 10 + JAVA_OPTS: -Xmx512m MANAGEMENTPORTAL_COMMON_BASE_URL: http://localhost:8081/managementportal MANAGEMENTPORTAL_COMMON_MANAGEMENT_PORTAL_BASE_URL: http://localhost:8081/managementportal MANAGEMENTPORTAL_FRONTEND_CLIENT_SECRET: @@ -65,40 +71,22 @@ services: volumes: - ./integration-tests/src/integrationTest/resources/docker/etc/:/mp-includes/ - #---------------------------------------------------------------------------# - # Gateway Service # - #---------------------------------------------------------------------------# - gateway-service: - build: - context: ../ - dockerfile: microservices/gateway-service/Dockerfile - image: radarbase/appserver-gateway-service:SNAPSHOT - ports: - - "8080:8080" - networks: - - public - - microservice - - mp - environment: - APPSERVER_PROJECT_SERVICE_BASE_URL: http://project-service:9010 - APPSERVER_USER_SERVICE_BASE_URL: http://user-service:9013 - APPSERVER_GITHUB_SERVICE_BASE_URL: http://github-service:9011 - APPSERVER_PROTOCOL_SERVICE_BASE_URL: http://protocol-service:9012 - APPSERVER_TASK_SERVICE_BASE_URL: http://task-service:9014 - APPSERVER_CLOUD_MESSAGING_SERVICE_BASE_URL: http://cloud-messaging-service:9015 - APPSERVER_MANAGEMENTPORTAL_BASE_URL: http://managementportal:8081/managementportal - #---------------------------------------------------------------------------# # Project Service # #---------------------------------------------------------------------------# project-service-db: - image: postgres + image: postgres:16 networks: - project-service-internal environment: POSTGRES_DB: appserver_project POSTGRES_USER: radar POSTGRES_PASSWORD: radar + healthcheck: + test: ["CMD-SHELL", "pg_isready -U radar -d appserver_project"] + interval: 5s + timeout: 5s + retries: 5 project-service: build: @@ -109,7 +97,8 @@ services: - microservice - project-service-internal depends_on: - - project-service-db + project-service-db: + condition: service_healthy environment: APPSERVER_PROJECT_JDBC_URL: jdbc:postgresql://project-service-db:5432/appserver_project APPSERVER_PROJECT_JDBC_USERNAME: radar @@ -121,13 +110,18 @@ services: # User Service # #---------------------------------------------------------------------------# user-service-db: - image: postgres + image: postgres:16 networks: - user-service-internal environment: POSTGRES_DB: appserver_user POSTGRES_USER: radar POSTGRES_PASSWORD: radar + healthcheck: + test: ["CMD-SHELL", "pg_isready -U radar -d appserver_user"] + interval: 5s + timeout: 5s + retries: 5 user-service: build: @@ -138,13 +132,15 @@ services: - microservice - user-service-internal depends_on: - - user-service-db + user-service-db: + condition: service_healthy environment: APPSERVER_USER_JDBC_URL: jdbc:postgresql://user-service-db:5432/appserver_user APPSERVER_USER_JDBC_USERNAME: radar APPSERVER_USER_JDBC_PASSWORD: radar APPSERVER_USER_HIBERNATE_DIALECT: org.hibernate.dialect.PostgreSQLDialect APPSERVER_USER_JDBC_DRIVER: org.postgresql.Driver + APPSERVER_PROJECT_SERVICE_BASE_URL: http://project-service:9010 #---------------------------------------------------------------------------# # Github Service # @@ -168,18 +164,25 @@ services: image: radarbase/appserver-protocol-service:SNAPSHOT networks: - microservice + environment: + APPSERVER_GITHUB_SERVICE_BASE_URL: http://github-service:9011 #---------------------------------------------------------------------------# # Task Service # #---------------------------------------------------------------------------# task-service-db: - image: postgres + image: postgres:16 networks: - task-service-internal environment: POSTGRES_DB: appserver_task POSTGRES_USER: radar POSTGRES_PASSWORD: radar + healthcheck: + test: ["CMD-SHELL", "pg_isready -U radar -d appserver_task"] + interval: 5s + timeout: 5s + retries: 5 task-service: build: @@ -190,7 +193,8 @@ services: - microservice - task-service-internal depends_on: - - task-service-db + task-service-db: + condition: service_healthy environment: APPSERVER_TASK_JDBC_URL: jdbc:postgresql://task-service-db:5432/appserver_task APPSERVER_TASK_JDBC_USERNAME: radar @@ -202,13 +206,18 @@ services: # Cloud Messaging Service # #---------------------------------------------------------------------------# cloud-messaging-service-db: - image: postgres + image: postgres:16 networks: - cloud-messaging-service-internal environment: POSTGRES_DB: appserver_cloud_messaging POSTGRES_USER: radar POSTGRES_PASSWORD: radar + healthcheck: + test: ["CMD-SHELL", "pg_isready -U radar -d appserver_cloud_messaging"] + interval: 5s + timeout: 5s + retries: 5 cloud-messaging-service: build: @@ -219,7 +228,8 @@ services: - microservice - cloud-messaging-service-internal depends_on: - - cloud-messaging-service-db + cloud-messaging-service-db: + condition: service_healthy environment: APPSERVER_CLOUD_MESSAGING_JDBC_URL: jdbc:postgresql://cloud-messaging-service-db:5432/appserver_cloud_messaging APPSERVER_CLOUD_MESSAGING_JDBC_USERNAME: radar @@ -230,4 +240,32 @@ services: volumes: - ./integration-tests/src/integrationTest/resources/docker/fcm:/appserver-includes/ - + #---------------------------------------------------------------------------# + # Gateway Service # + #---------------------------------------------------------------------------# + gateway-service: + build: + context: ../ + dockerfile: microservices/gateway-service/Dockerfile + image: radarbase/appserver-gateway-service:SNAPSHOT + ports: + - "8080:8080" + networks: + - public + - microservice + - mp + depends_on: + - project-service + - user-service + - github-service + - protocol-service + - task-service + - cloud-messaging-service + environment: + APPSERVER_PROJECT_SERVICE_BASE_URL: http://project-service:9010 + APPSERVER_USER_SERVICE_BASE_URL: http://user-service:9013 + APPSERVER_GITHUB_SERVICE_BASE_URL: http://github-service:9011 + APPSERVER_PROTOCOL_SERVICE_BASE_URL: http://protocol-service:9012 + APPSERVER_TASK_SERVICE_BASE_URL: http://task-service:9014 + APPSERVER_CLOUD_MESSAGING_SERVICE_BASE_URL: http://cloud-messaging-service:9015 + APPSERVER_MANAGEMENTPORTAL_BASE_URL: http://managementportal:8081/managementportal From 8930f6e9a25ba1b728f01123428c34f68fa18365 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 16:26:56 +0000 Subject: [PATCH 04/13] Misc changes --- appserver-jersey/build.gradle.kts | 15 +++++++-------- .../cloud-messaging-service/build.gradle.kts | 2 +- microservices/core/build.gradle.kts | 16 ++++++++-------- microservices/gateway-service/build.gradle.kts | 2 +- microservices/integration-tests/build.gradle.kts | 8 ++++---- microservices/protocol-service/build.gradle.kts | 2 +- microservices/task-service/build.gradle.kts | 2 +- microservices/user-service/build.gradle.kts | 2 +- 8 files changed, 24 insertions(+), 25 deletions(-) diff --git a/appserver-jersey/build.gradle.kts b/appserver-jersey/build.gradle.kts index bdf33f2f..1d59d653 100644 --- a/appserver-jersey/build.gradle.kts +++ b/appserver-jersey/build.gradle.kts @@ -73,24 +73,24 @@ dependencies { implementation("com.google.firebase:firebase-admin:${Versions.firebaseAdminVersion}") { constraints { - implementation("com.google.protobuf:protobuf-java:3.25.5") { + implementation("com.google.protobuf:protobuf-java:${Versions.protobufVersion}") { because("Provided version of protobuf has security vulnerabilities") } - implementation("com.google.protobuf:protobuf-java-util:3.25.5") { + implementation("com.google.protobuf:protobuf-java-util:${Versions.protobufVersion}") { because("Provided version of protobuf has security vulnerabilities") } } } - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") implementation("com.sun.mail:jakarta.mail:${Versions.jakartaMailVersion}") implementation("com.google.guava:guava:${Versions.guavaVersion}") implementation("org.quartz-scheduler:quartz:${Versions.quartzVersion}") - testImplementation("io.mockk:mockk:1.14.4") - testImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0") - testImplementation("org.hamcrest:hamcrest:2.1") - testImplementation("org.assertj:assertj-core:3.24.2") + testImplementation("io.mockk:mockk:${Versions.mockkVersion}") + testImplementation("org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlinVersion}") + testImplementation("org.hamcrest:hamcrest:${Versions.hamcrestVersion}") + testImplementation("org.assertj:assertj-core:${Versions.assertjVersion}") integrationTestImplementation(platform("io.ktor:ktor-bom:${Versions.ktorVersion}")) integrationTestImplementation("io.ktor:ktor-client-content-negotiation") @@ -105,7 +105,6 @@ ktlint { radarKotlin { javaVersion.set(Versions.java) kotlinVersion.set(Versions.kotlinVersion) -// kotlinApiVersion.set(Versions.kotlinVersion) junitVersion.set(Versions.junit5Version) log4j2Version.set(Versions.log4j2) } diff --git a/microservices/cloud-messaging-service/build.gradle.kts b/microservices/cloud-messaging-service/build.gradle.kts index a6f7e22d..2bdb3552 100644 --- a/microservices/cloud-messaging-service/build.gradle.kts +++ b/microservices/cloud-messaging-service/build.gradle.kts @@ -11,7 +11,7 @@ description = "AppServer Notification State Event Microservices Implementation" dependencies { implementation("com.h2database:h2:${Versions.h2Version}") implementation("com.google.guava:guava:${Versions.guavaVersion}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") implementation("com.sun.mail:jakarta.mail:${Versions.jakartaMailVersion}") implementation("org.radarbase:radar-jersey:${Versions.radarJerseyVersion}") diff --git a/microservices/core/build.gradle.kts b/microservices/core/build.gradle.kts index 839608db..2141b4a3 100644 --- a/microservices/core/build.gradle.kts +++ b/microservices/core/build.gradle.kts @@ -21,22 +21,22 @@ dependencies { api("org.radarbase:radar-jersey-hibernate:${Versions.radarJerseyVersion}") { runtimeOnly("org.postgresql:postgresql:${Versions.postgresqlVersion}") } - api("org.glassfish.jersey.ext:jersey-bean-validation:3.1.10") - api("com.google.firebase:firebase-admin:9.3.0") { + api("org.glassfish.jersey.ext:jersey-bean-validation:${Versions.jerseyBeanValidationVersion}") + api("com.google.firebase:firebase-admin:${Versions.firebaseAdminVersion}") { constraints { - implementation("com.google.protobuf:protobuf-java:3.25.5") { + implementation("com.google.protobuf:protobuf-java:${Versions.protobufVersion}") { because("Provided version of protobuf has security vulnerabilities") } - implementation("com.google.protobuf:protobuf-java-util:3.25.5") { + implementation("com.google.protobuf:protobuf-java-util:${Versions.protobufVersion}") { because("Provided version of protobuf has security vulnerabilities") } } } - - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") - implementation("com.google.guava:guava:32.1.3-jre") - api("org.quartz-scheduler:quartz:2.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") + + implementation("com.google.guava:guava:${Versions.guavaVersion}") + api("org.quartz-scheduler:quartz:${Versions.quartzVersion}") } diff --git a/microservices/gateway-service/build.gradle.kts b/microservices/gateway-service/build.gradle.kts index 646549d5..40ae127e 100644 --- a/microservices/gateway-service/build.gradle.kts +++ b/microservices/gateway-service/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { implementation(project(":microservices:contract")) implementation("org.radarbase:radar-jersey:${Versions.radarJerseyVersion}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") } ktlint { diff --git a/microservices/integration-tests/build.gradle.kts b/microservices/integration-tests/build.gradle.kts index 258363f1..e77dda05 100644 --- a/microservices/integration-tests/build.gradle.kts +++ b/microservices/integration-tests/build.gradle.kts @@ -47,10 +47,10 @@ dependencies { integrationTestImplementation(project(":microservices:core")) integrationTestImplementation("org.radarbase:radar-jersey:${Versions.radarJerseyVersion}") integrationTestImplementation("org.radarbase:radar-commons-kotlin:${Versions.radarCommonsVersion}") - integrationTestImplementation("io.mockk:mockk:1.14.4") - integrationTestImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0") - integrationTestImplementation("org.hamcrest:hamcrest:2.1") - integrationTestImplementation("org.assertj:assertj-core:3.24.2") + integrationTestImplementation("io.mockk:mockk:${Versions.mockkVersion}") + integrationTestImplementation("org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlinVersion}") + integrationTestImplementation("org.hamcrest:hamcrest:${Versions.hamcrestVersion}") + integrationTestImplementation("org.assertj:assertj-core:${Versions.assertjVersion}") integrationTestImplementation(platform("io.ktor:ktor-bom:${Versions.ktorVersion}")) integrationTestImplementation("io.ktor:ktor-client-core:${Versions.ktorVersion}") integrationTestImplementation("io.ktor:ktor-client-cio:${Versions.ktorVersion}") diff --git a/microservices/protocol-service/build.gradle.kts b/microservices/protocol-service/build.gradle.kts index 9b4446f7..9433c153 100644 --- a/microservices/protocol-service/build.gradle.kts +++ b/microservices/protocol-service/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { implementation(project(":microservices:contract")) implementation("org.radarbase:radar-jersey:${Versions.radarJerseyVersion}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") } ktlint { diff --git a/microservices/task-service/build.gradle.kts b/microservices/task-service/build.gradle.kts index 196c90fe..be0f1af2 100644 --- a/microservices/task-service/build.gradle.kts +++ b/microservices/task-service/build.gradle.kts @@ -11,7 +11,7 @@ description = "AppServer Task State Event Microservices Implementation" dependencies { implementation("com.h2database:h2:${Versions.h2Version}") implementation("com.google.guava:guava:${Versions.guavaVersion}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") implementation("org.radarbase:radar-jersey:${Versions.radarJerseyVersion}") implementation(project(":microservices:core")) diff --git a/microservices/user-service/build.gradle.kts b/microservices/user-service/build.gradle.kts index fe9a0c93..7534b98d 100644 --- a/microservices/user-service/build.gradle.kts +++ b/microservices/user-service/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation(project(":microservices:core")) implementation(project(":microservices:contract")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerializationVersion}") } ktlint { From adba04b42528fd3a43a1dbf24a8190dcd32b6b8d Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:08:37 +0000 Subject: [PATCH 05/13] Update workflows --- .github/workflows/jersey-main.yml | 15 +--- .github/workflows/jersey-release.yml | 1 + .github/workflows/microservices-main.yml | 25 ++----- .github/workflows/scheduled-snyk-docker.yaml | 77 ++++++++++++++++++++ .github/workflows/scheduled-snyk.yaml | 2 +- .github/workflows/snyk.yaml | 2 +- 6 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/scheduled-snyk-docker.yaml diff --git a/.github/workflows/jersey-main.yml b/.github/workflows/jersey-main.yml index 29551da9..125b0828 100644 --- a/.github/workflows/jersey-main.yml +++ b/.github/workflows/jersey-main.yml @@ -31,7 +31,6 @@ jobs: - name: Check run: ./gradlew :appserver-jersey:check - # Check that the docker image builds correctly docker: runs-on: ubuntu-latest permissions: @@ -52,14 +51,12 @@ jobs: run: | echo "DOCKER_IMAGE=${REGISTRY}/${REPOSITORY,,}/${IMAGE_NAME}" >>${GITHUB_ENV} - # Add Docker labels and tags - name: Docker meta id: docker_meta uses: docker/metadata-action@v5 with: images: ${{ env.DOCKER_IMAGE }} - # Setup docker build environment - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -68,12 +65,12 @@ jobs: - name: Cache Docker layers id: cache-buildx - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile.appserver-jersey', 'appserver-jersey/build.gradle.kts', 'buildSrc/src/main/kotlin/Versions.kt', 'settings.gradle.kts', 'build.gradle.kts', 'appserver-jersey/src/main/**') }} + key: ${{ runner.os }}-buildx-jersey-${{ hashFiles('Dockerfile.appserver-jersey', 'appserver-jersey/build.gradle.kts', 'buildSrc/src/main/kotlin/Versions.kt', 'settings.gradle.kts', 'build.gradle.kts') }} restore-keys: | - ${{ runner.os }}-buildx- + ${{ runner.os }}-buildx-jersey- - name: Cache parameters id: cache-parameters @@ -85,7 +82,7 @@ jobs: fi - name: Build docker - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: cache-from: type=local,src=/tmp/.buildx-cache cache-to: ${{ steps.cache-parameters.outputs.cache-to }} @@ -101,10 +98,6 @@ jobs: - name: Inspect docker image run: docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} - - name: Check docker image - run: docker run --rm ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} curl --help - - # Push the image on the dev and master branches - name: Push image if: ${{ github.event_name != 'pull_request' }} run: docker push ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} diff --git a/.github/workflows/jersey-release.yml b/.github/workflows/jersey-release.yml index 962edf17..3c72d19c 100644 --- a/.github/workflows/jersey-release.yml +++ b/.github/workflows/jersey-release.yml @@ -76,6 +76,7 @@ jobs: platforms: linux/amd64,linux/arm64 context: . push: true + file: Dockerfile.appserver-jersey tags: ${{ steps.docker_meta.outputs.tags }} labels: | ${{ steps.docker_meta.outputs.labels }} diff --git a/.github/workflows/microservices-main.yml b/.github/workflows/microservices-main.yml index 329a5663..30889b3d 100644 --- a/.github/workflows/microservices-main.yml +++ b/.github/workflows/microservices-main.yml @@ -41,7 +41,7 @@ jobs: run: ./gradlew :microservices:${{ matrix.service }}:check docker: - name: Docker setup for ${{ matrix.service }} + name: Docker ${{ matrix.service }} runs-on: ubuntu-latest permissions: contents: read @@ -101,19 +101,18 @@ jobs: - name: Cache Docker layers id: cache-buildx - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles( + key: ${{ runner.os }}-buildx-${{ matrix.service }}-${{ hashFiles( format('microservices/{0}/Dockerfile', matrix.service), 'settings.gradle.kts', 'buildSrc/src/main/kotlin/Versions.kt', 'build.gradle.kts', - format('microservices/{0}/build.gradle.kts', matrix.service), - format('microservices/{0}/src/main/**', matrix.service) + format('microservices/{0}/build.gradle.kts', matrix.service) ) }} restore-keys: | - ${{ runner.os }}-buildx- + ${{ runner.os }}-buildx-${{ matrix.service }}- - name: Cache parameters id: cache-parameters @@ -124,11 +123,8 @@ jobs: echo "cache-to=type=local,dest=/tmp/.buildx-cache-new,mode=max" >> $GITHUB_OUTPUT fi - - name: Compile ${{ matrix.service }} code - run: ./gradlew --no-daemon :microservices:${{ matrix.service }}:assemble - - name: Build docker - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: cache-from: type=local,src=/tmp/.buildx-cache cache-to: ${{ steps.cache-parameters.outputs.cache-to }} @@ -140,13 +136,10 @@ jobs: ${{ steps.docker_meta.outputs.labels }} org.opencontainers.image.vendor=RADAR-base org.opencontainers.image.licenses=Apache-2.0 - + - name: Inspect docker image run: docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} - - name: Check docker image - run: docker run --rm ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} curl --help || true - - name: Push image if: ${{ github.event_name != 'pull_request' }} run: docker push ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} @@ -177,7 +170,7 @@ jobs: integration-test: runs-on: ubuntu-latest needs: docker - if: ${{ always() }} # Running this job even if the docker job fails. It will build the image from source, and collect the logs + if: ${{ always() }} steps: - uses: actions/checkout@v5 @@ -215,7 +208,6 @@ jobs: svc_upper=$(echo "$service" | tr '[:lower:]-' '[:upper:]_' ) - # variable names for microservices images echo "RADAR_APPSERVER_${svc_upper}_IMAGE_NAME=${image}" >> $OUTFILE echo "RADAR_APPSERVER_${svc_upper}_IMAGE_TAG=${tag}" >> $OUTFILE @@ -231,7 +223,6 @@ jobs: - name: Decrypt google application credentials run: | - # adjust path to encrypted file if needed gpg --pinentry-mode loopback --local-user "Adi Mishra" --batch --yes \ --passphrase "${{ secrets.GPG_SECRET_KEY_PASSPHRASE }}" \ --output microservices/integration-tests/src/integrationTest/resources/docker/fcm/google-credentials.json \ diff --git a/.github/workflows/scheduled-snyk-docker.yaml b/.github/workflows/scheduled-snyk-docker.yaml new file mode 100644 index 00000000..a8a7646a --- /dev/null +++ b/.github/workflows/scheduled-snyk-docker.yaml @@ -0,0 +1,77 @@ +name: Snyk Docker scheduled test +on: + schedule: + - cron: '0 3 * * 1' + push: + branches: + - master + +env: + REGISTRY: ghcr.io + REPOSITORY: ${{ github.repository }} + +jobs: + jersey: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@v5 + + - name: Lowercase image name + run: | + echo "DOCKER_IMAGE=${REGISTRY}/${REPOSITORY,,}/radar-appserver" >>${GITHUB_ENV} + + - name: Run Snyk to check Docker image for vulnerabilities + uses: snyk/actions/docker@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: ${{ env.DOCKER_IMAGE }}:latest + args: --file=Dockerfile.appserver-jersey --severity-threshold=high + + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: snyk.sarif + + microservices: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + service: + - project-service + - user-service + - gateway-service + - github-service + - protocol-service + - task-service + - cloud-messaging-service + steps: + - uses: actions/checkout@v5 + + - name: Lowercase image name + run: | + echo "DOCKER_IMAGE=${REGISTRY}/${REPOSITORY,,}/${{ matrix.service }}" >>${GITHUB_ENV} + + - name: Run Snyk to check Docker image for vulnerabilities + uses: snyk/actions/docker@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: ${{ env.DOCKER_IMAGE }}:latest + args: --file=microservices/${{ matrix.service }}/Dockerfile --severity-threshold=high + + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/scheduled-snyk.yaml b/.github/workflows/scheduled-snyk.yaml index 47fce4fc..3c37d984 100644 --- a/.github/workflows/scheduled-snyk.yaml +++ b/.github/workflows/scheduled-snyk.yaml @@ -12,7 +12,7 @@ jobs: env: REPORT_FILE: test.json steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Run Snyk to check for vulnerabilities uses: snyk/actions/gradle-jdk17@master diff --git a/.github/workflows/snyk.yaml b/.github/workflows/snyk.yaml index be2d67db..6c968028 100644 --- a/.github/workflows/snyk.yaml +++ b/.github/workflows/snyk.yaml @@ -7,7 +7,7 @@ jobs: security: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Run Snyk to check for JDK vulnerabilities uses: snyk/actions/gradle-jdk17@master From abe7bcf3440c4421ee23760bf4322e3515f26e4e Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:21:16 +0000 Subject: [PATCH 06/13] Support swagger UI, enable liquibase config and add changelogs path --- .../factory/AppserverResourceEnhancerFactory.kt | 12 +++++++++++- .../jersey/resource/FcmDataMessageResource.kt | 2 ++ .../jersey/resource/FcmNotificationResource.kt | 2 ++ .../appserver/jersey/resource/GithubResource.kt | 2 ++ .../resource/NotificationStateEventResource.kt | 2 ++ .../appserver/jersey/resource/ProjectResource.kt | 2 ++ .../appserver/jersey/resource/ProtocolResource.kt | 2 ++ .../jersey/resource/QuestionnaireScheduleResource.kt | 2 ++ .../jersey/resource/TaskStateEventResource.kt | 2 ++ .../appserver/jersey/resource/UserResource.kt | 2 ++ .../microservices/gateway/api/GatewayResource.kt | 8 +++++--- .../factory/GithubServiceResourceEnhancerFactory.kt | 11 +++++++++++ .../factory/ProjectServiceResourceEnhancerFactory.kt | 12 ++++++++++++ .../ProtocolServiceResourceEnhancerFactory.kt | 11 +++++++++++ .../factory/TaskServiceResourceEnhancerFactory.kt | 12 ++++++++++++ .../factory/UserServiceResourceEnhancerFactory.kt | 12 ++++++++++++ 16 files changed, 92 insertions(+), 4 deletions(-) diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/enhancer/factory/AppserverResourceEnhancerFactory.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/enhancer/factory/AppserverResourceEnhancerFactory.kt index 06affaec..bdaa7d77 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/enhancer/factory/AppserverResourceEnhancerFactory.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/enhancer/factory/AppserverResourceEnhancerFactory.kt @@ -16,12 +16,13 @@ package org.radarbase.appserver.jersey.enhancer.factory -import jakarta.mail.Session import org.radarbase.appserver.jersey.config.AppserverConfig import org.radarbase.appserver.jersey.config.EmailConfig import org.radarbase.appserver.jersey.enhancer.AppserverResourceEnhancer import org.radarbase.appserver.jersey.service.transmitter.EmailTransmitter import org.radarbase.appserver.jersey.utils.mail.MailSessionFactory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.jersey.auth.AuthConfig import org.radarbase.jersey.auth.MPConfig import org.radarbase.jersey.enhancer.EnhancerFactory @@ -87,11 +88,20 @@ class AppserverResourceEnhancerFactory(private val config: AppserverConfig) : En ) } + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver" + description = "General purpose application server for the RADAR platform" + version = "2.4.3" + } + } + return listOf( AppserverResourceEnhancer(config, emailTransmitter), Enhancers.radar(authConfig), Enhancers.managementPortal(authConfig), HibernateResourceEnhancer(dbConfig), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmDataMessageResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmDataMessageResource.kt index e412f85d..5561f318 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmDataMessageResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmDataMessageResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.inject.Provider import jakarta.validation.Valid @@ -56,6 +57,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Data Messages") class FcmDataMessageResource @Inject constructor( private val fcmDataMessageService: FcmDataMessageService, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmNotificationResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmNotificationResource.kt index 683ecf74..4ad16a8f 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmNotificationResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/FcmNotificationResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.inject.Provider import jakarta.validation.Valid @@ -60,6 +61,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Notifications") class FcmNotificationResource @Inject constructor( private val asyncService: AsyncCoroutineService, private val authService: AuthService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/GithubResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/GithubResource.kt index 06b47595..e2ac0a69 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/GithubResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/GithubResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.ws.rs.GET import jakarta.ws.rs.Path @@ -38,6 +39,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @Path("/$GITHUB_PATH") +@Tag(name = "GitHub") class GithubResource @Inject constructor( private val githubService: GithubService, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/NotificationStateEventResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/NotificationStateEventResource.kt index 0acafb21..db3da761 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/NotificationStateEventResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/NotificationStateEventResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.ws.rs.GET import jakarta.ws.rs.POST @@ -44,6 +45,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Notification State Events") class NotificationStateEventResource @Inject constructor( private val notificationStateEventService: NotificationStateEventService, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProjectResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProjectResource.kt index a5bb9344..9f555e96 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProjectResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProjectResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.inject.Provider import jakarta.validation.Valid @@ -54,6 +55,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Projects") class ProjectResource @Inject constructor( private val projectService: ProjectService, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProtocolResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProtocolResource.kt index f53230ad..3f616bbf 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProtocolResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/ProtocolResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.validation.Valid import jakarta.ws.rs.GET @@ -41,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Protocols") class ProtocolResource @Inject constructor( private val protocolGenerator: ProtocolGenerator, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/QuestionnaireScheduleResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/QuestionnaireScheduleResource.kt index 37158d6e..8286dc18 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/QuestionnaireScheduleResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/QuestionnaireScheduleResource.kt @@ -18,6 +18,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.validation.Valid import jakarta.ws.rs.DELETE @@ -52,6 +53,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @Path("/") +@Tag(name = "Questionnaire Schedules") class QuestionnaireScheduleResource @Inject constructor( private val scheduleService: QuestionnaireScheduleService, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/TaskStateEventResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/TaskStateEventResource.kt index 19b3c72d..f4bd05b0 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/TaskStateEventResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/TaskStateEventResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.ws.rs.GET import jakarta.ws.rs.POST @@ -44,6 +45,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Task State Events") class TaskStateEventResource @Inject constructor( private val taskStateEventService: TaskStateEventService, private val asyncService: AsyncCoroutineService, diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/UserResource.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/UserResource.kt index bd66ea3a..da9581c5 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/UserResource.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/resource/UserResource.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.jersey.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Inject import jakarta.inject.Provider import jakarta.validation.Valid @@ -58,6 +59,7 @@ import kotlin.time.Duration.Companion.seconds @Suppress("UnresolvedRestParam") @Path("/") +@Tag(name = "Users") class UserResource @Inject constructor( private val userService: UserService, private val asyncService: AsyncCoroutineService, diff --git a/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/api/GatewayResource.kt b/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/api/GatewayResource.kt index 1326d3c5..0d2f2114 100644 --- a/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/api/GatewayResource.kt +++ b/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/api/GatewayResource.kt @@ -826,7 +826,7 @@ class GatewayResource @Inject constructor( ) { asyncService.runAsCoroutine(asyncResponse, requestTimeout) { handleProxyResponse( - NotificationServiceContract.getNotificationUsingId(id, taskServiceRoute.baseUrl), + NotificationServiceContract.getNotificationUsingId(id, cloudMessagingServiceRoute.baseUrl), ) } } @@ -1087,6 +1087,8 @@ class GatewayResource @Inject constructor( @GET @Path("/$MESSAGING_NOTIFICATION_PATH/$NOTIFICATION_ID/$NOTIFICATION_STATE_EVENTS_PATH") @Produces(APPLICATION_JSON) + @Authenticated + @NeedsPermission(Permission.PROJECT_READ) fun getNotificationStateEventsByNotificationId( @PathParam("notificationId") notificationId: Long, @Suspended asyncResponse: AsyncResponse, @@ -1172,14 +1174,14 @@ class GatewayResource @Inject constructor( @Path("$MESSAGING_DATA_PATH/{id}") @Produces(APPLICATION_JSON) @Authenticated - @NeedsPermission(Permission.SUBJECT_READ) + @NeedsPermission(Permission.SUBJECT_READ)G fun getDataMessageUsingId( @PathParam("id") id: Long, @Suspended asyncResponse: AsyncResponse, ) { asyncService.runAsCoroutine(asyncResponse, requestTimeout) { handleProxyResponse( - DataMessageServiceContract.getDataMessageUsingId(id, taskServiceRoute.baseUrl), + DataMessageServiceContract.getDataMessageUsingId(id, cloudMessagingServiceRoute.baseUrl), ) } } diff --git a/microservices/github-service/src/main/kotlin/org/radarbase/appserver/microservices/github/enhancer/factory/GithubServiceResourceEnhancerFactory.kt b/microservices/github-service/src/main/kotlin/org/radarbase/appserver/microservices/github/enhancer/factory/GithubServiceResourceEnhancerFactory.kt index 712d2a4a..d9a42653 100644 --- a/microservices/github-service/src/main/kotlin/org/radarbase/appserver/microservices/github/enhancer/factory/GithubServiceResourceEnhancerFactory.kt +++ b/microservices/github-service/src/main/kotlin/org/radarbase/appserver/microservices/github/enhancer/factory/GithubServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.github.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.github.config.GithubServiceConfig import org.radarbase.appserver.microservices.github.enhancer.GithubServiceResourceEnhancer import org.radarbase.jersey.enhancer.EnhancerFactory @@ -25,8 +27,17 @@ import org.radarbase.jersey.enhancer.JerseyResourceEnhancer class GithubServiceResourceEnhancerFactory(private val config: GithubServiceConfig) : EnhancerFactory { override fun createEnhancers(): List { + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver GitHub Service" + description = "Fetches questionnaire protocols from GitHub repositories" + version = "2.4.3" + } + } + return listOf( GithubServiceResourceEnhancer(config), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) diff --git a/microservices/project-service/src/main/kotlin/org/radarbase/appserver/microservices/project/enhancer/factory/ProjectServiceResourceEnhancerFactory.kt b/microservices/project-service/src/main/kotlin/org/radarbase/appserver/microservices/project/enhancer/factory/ProjectServiceResourceEnhancerFactory.kt index 1152f173..0e189d28 100644 --- a/microservices/project-service/src/main/kotlin/org/radarbase/appserver/microservices/project/enhancer/factory/ProjectServiceResourceEnhancerFactory.kt +++ b/microservices/project-service/src/main/kotlin/org/radarbase/appserver/microservices/project/enhancer/factory/ProjectServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.project.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.project.config.ProjectServiceConfig import org.radarbase.appserver.microservices.project.enhancer.ProjectServiceResourceEnhancer import org.radarbase.jersey.enhancer.EnhancerFactory @@ -36,12 +38,22 @@ class ProjectServiceResourceEnhancerFactory(private val config: ProjectServiceCo properties = config.db.additionalProperties, liquibase = org.radarbase.jersey.hibernate.config.LiquibaseConfig( enable = config.db.liquibase.enabled, + changelogs = config.db.liquibase.changelogs, ), ) + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver Project Service" + description = "Manages projects in the RADAR platform" + version = "2.4.3" + } + } + return listOf( ProjectServiceResourceEnhancer(config), HibernateResourceEnhancer(dbConfig), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) diff --git a/microservices/protocol-service/src/main/kotlin/org/radarbase/appserver/microservices/protocol/enhancer/factory/ProtocolServiceResourceEnhancerFactory.kt b/microservices/protocol-service/src/main/kotlin/org/radarbase/appserver/microservices/protocol/enhancer/factory/ProtocolServiceResourceEnhancerFactory.kt index 0237d36e..8e15357f 100644 --- a/microservices/protocol-service/src/main/kotlin/org/radarbase/appserver/microservices/protocol/enhancer/factory/ProtocolServiceResourceEnhancerFactory.kt +++ b/microservices/protocol-service/src/main/kotlin/org/radarbase/appserver/microservices/protocol/enhancer/factory/ProtocolServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.protocol.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.protocol.config.ProtocolServiceConfig import org.radarbase.appserver.microservices.protocol.enhancer.ProtocolServiceResourceEnhancer import org.radarbase.jersey.enhancer.EnhancerFactory @@ -24,8 +26,17 @@ import org.radarbase.jersey.enhancer.JerseyResourceEnhancer class ProtocolServiceResourceEnhancerFactory(private val config: ProtocolServiceConfig) : EnhancerFactory { override fun createEnhancers(): List { + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver Protocol Service" + description = "Manages questionnaire protocols and scheduling in the RADAR platform" + version = "2.4.3" + } + } + return listOf( ProtocolServiceResourceEnhancer(config), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) diff --git a/microservices/task-service/src/main/kotlin/org/radarbase/appserver/microservices/task/enhancer/factory/TaskServiceResourceEnhancerFactory.kt b/microservices/task-service/src/main/kotlin/org/radarbase/appserver/microservices/task/enhancer/factory/TaskServiceResourceEnhancerFactory.kt index 560b1efd..1fc92c2f 100644 --- a/microservices/task-service/src/main/kotlin/org/radarbase/appserver/microservices/task/enhancer/factory/TaskServiceResourceEnhancerFactory.kt +++ b/microservices/task-service/src/main/kotlin/org/radarbase/appserver/microservices/task/enhancer/factory/TaskServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.task.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.task.config.TaskServiceConfig import org.radarbase.appserver.microservices.task.enhancer.TaskServiceResourceEnhancer import org.radarbase.jersey.enhancer.EnhancerFactory @@ -36,12 +38,22 @@ class TaskServiceResourceEnhancerFactory(private val config: TaskServiceConfig) properties = config.db.additionalProperties, liquibase = org.radarbase.jersey.hibernate.config.LiquibaseConfig( enable = config.db.liquibase.enabled, + changelogs = config.db.liquibase.changelogs, ), ) + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver Task Service" + description = "Manages tasks and task state events in the RADAR platform" + version = "2.4.3" + } + } + return listOf( TaskServiceResourceEnhancer(config), HibernateResourceEnhancer(dbConfig), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) diff --git a/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/enhancer/factory/UserServiceResourceEnhancerFactory.kt b/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/enhancer/factory/UserServiceResourceEnhancerFactory.kt index 38933a35..138ad8fb 100644 --- a/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/enhancer/factory/UserServiceResourceEnhancerFactory.kt +++ b/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/enhancer/factory/UserServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.user.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.user.config.UserServiceConfig import org.radarbase.appserver.microservices.user.enhancer.UserServiceResourceEnhancer import org.radarbase.jersey.enhancer.EnhancerFactory @@ -37,12 +39,22 @@ class UserServiceResourceEnhancerFactory(private val config: UserServiceConfig) properties = config.db.additionalProperties, liquibase = LiquibaseConfig( enable = config.db.liquibase.enabled, + changelogs = config.db.liquibase.changelogs, ), ) + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver User Service" + description = "Manages users, metrics, and attributes in the RADAR platform" + version = "2.4.3" + } + } + return listOf( UserServiceResourceEnhancer(config), HibernateResourceEnhancer(dbConfig), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) From 02aa13a3801e9e64d2bbe7fa4795f86ccd5e72f7 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:27:13 +0000 Subject: [PATCH 07/13] Using @Optional for entities that may be null when injecting --- .../appserver/jersey/service/quartz/MessageJob.kt | 3 ++- .../cloud/messaging/service/quartz/MessageJob.kt | 7 ++++--- .../appserver/microservices/user/config/UserDbConfig.kt | 3 --- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/quartz/MessageJob.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/quartz/MessageJob.kt index fbc72ae5..6ce808b5 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/quartz/MessageJob.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/quartz/MessageJob.kt @@ -17,6 +17,7 @@ package org.radarbase.appserver.jersey.service.quartz import jakarta.inject.Inject +import org.jvnet.hk2.annotations.Optional import org.quartz.Job import org.quartz.JobExecutionContext import org.quartz.JobExecutionException @@ -40,7 +41,7 @@ class MessageJob @Inject constructor( private val notificationService: FcmNotificationService, private val dataMessageService: FcmDataMessageService, private val asyncService: AsyncCoroutineService, - private val emailTransmitter: EmailTransmitter?, + @param:Optional private val emailTransmitter: EmailTransmitter?, config: AppserverConfig, ) : Job { private val emailEnabled = config.email.enabled diff --git a/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/service/quartz/MessageJob.kt b/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/service/quartz/MessageJob.kt index 7ab58c88..3b498267 100644 --- a/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/service/quartz/MessageJob.kt +++ b/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/service/quartz/MessageJob.kt @@ -1,10 +1,11 @@ package org.radarbase.appserver.microservices.cloud.messaging.service.quartz import jakarta.inject.Inject +import org.jvnet.hk2.annotations.Optional import org.quartz.Job import org.quartz.JobExecutionContext import org.quartz.JobExecutionException -import org.radarbase.appserver.jersey.config.AppserverConfig +import org.radarbase.appserver.microservices.cloud.messaging.config.CloudMessagingServiceConfig import org.radarbase.appserver.microservices.cloud.messaging.service.transmitter.DataMessageTransmitter import org.radarbase.appserver.microservices.cloud.messaging.service.transmitter.EmailTransmitter import org.radarbase.appserver.microservices.cloud.messaging.service.transmitter.NotificationTransmitter @@ -25,8 +26,8 @@ class MessageJob @Inject constructor( private val notificationService: FcmNotificationService, private val dataMessageService: FcmDataMessageService, private val asyncService: AsyncCoroutineService, - private val emailTransmitter: EmailTransmitter?, - config: AppserverConfig, + @param:Optional private val emailTransmitter: EmailTransmitter?, + config: CloudMessagingServiceConfig, ) : Job { private val emailEnabled = config.email.enabled /** diff --git a/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/config/UserDbConfig.kt b/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/config/UserDbConfig.kt index 2bcf24ca..6fbcee6a 100644 --- a/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/config/UserDbConfig.kt +++ b/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/config/UserDbConfig.kt @@ -23,7 +23,6 @@ import org.radarbase.appserver.microservices.contract.utils.Env.USER_JDBC_URL import org.radarbase.appserver.microservices.contract.utils.Env.USER_JDBC_USERNAME import org.radarbase.appserver.microservices.core.config.CoreLiquibaseConfig import org.radarbase.appserver.microservices.core.config.Validation -import org.radarbase.appserver.microservices.core.entity.Project import org.radarbase.appserver.microservices.core.entity.User import org.radarbase.appserver.microservices.core.entity.UserMetrics import org.radarbase.appserver.microservices.core.utils.checkInvalidDetails @@ -32,7 +31,6 @@ import org.radarbase.jersey.config.ConfigLoader.copyEnv data class UserDbConfig( val classes: List = listOf( User::class.qualifiedName!!, - Project::class.qualifiedName!!, UserMetrics::class.qualifiedName!!, ), val jdbcDriver: String = "org.postgresql.Driver", @@ -60,7 +58,6 @@ data class UserDbConfig( copy(jdbcDriver = it) } - override fun validate() { checkInvalidDetails( { From 3ef90d1fc74163ba513f1c112762a388910b1d45 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:28:41 +0000 Subject: [PATCH 08/13] Updated the changelogs path in core liquibase config --- .../appserver/microservices/core/config/CoreLiquibaseConfig.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/config/CoreLiquibaseConfig.kt b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/config/CoreLiquibaseConfig.kt index b44a08a6..d1c5e971 100644 --- a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/config/CoreLiquibaseConfig.kt +++ b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/config/CoreLiquibaseConfig.kt @@ -16,6 +16,7 @@ package org.radarbase.appserver.microservices.core.config -data class CoreLiquibaseConfig ( +data class CoreLiquibaseConfig( val enabled: Boolean = false, + val changelogs: String = "db/changelog/db.changelog-master.yaml", ) From 924b74dd28e87a801a33813c094212b3c3a60128 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:30:04 +0000 Subject: [PATCH 09/13] Swagger ui initialized for gateway service --- .../factory/GatewayServiceResourceEnhancerFactory.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/enhancer/factory/GatewayServiceResourceEnhancerFactory.kt b/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/enhancer/factory/GatewayServiceResourceEnhancerFactory.kt index 5f8c25ca..be3195f8 100644 --- a/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/enhancer/factory/GatewayServiceResourceEnhancerFactory.kt +++ b/microservices/gateway-service/src/main/kotlin/org/radarbase/appserver/microservices/gateway/enhancer/factory/GatewayServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.gateway.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.gateway.config.GatewayConfig import org.radarbase.appserver.microservices.gateway.enhancer.GatewayServiceResourceEnhancer import org.radarbase.jersey.auth.AuthConfig @@ -37,10 +39,19 @@ class GatewayServiceResourceEnhancerFactory( jwksUrls = config.auth.publicKeyUrls ?: emptyList(), ) + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver Gateway Service" + description = "API gateway for the RADAR-Appserver microservices" + version = "2.4.3" + } + } + return listOf( GatewayServiceResourceEnhancer(config), Enhancers.radar(authConfig), Enhancers.managementPortal(authConfig), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, ) From 5a9fa9654e9d4ea13076a22c16e26cb31caea993 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:31:03 +0000 Subject: [PATCH 10/13] Update versions --- buildSrc/src/main/kotlin/Versions.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 8adf84f6..2a0cfe8f 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,12 +1,9 @@ @Suppress("ConstPropertyName") object Versions { const val gatlingVersion = "3.9.2.1" - const val shadowVersion = "8.1.1" const val springBootVersion = "3.3.3" const val springDependencyManagementVersion = "1.1.6" - const val benMenesVersion = "0.46.0" const val kotlinVersion = "1.9.25" - const val sentryVersion = "4.11.0" const val dockerCompose = "0.17.6" const val springSecurityVersion = "6.0.5" @@ -18,7 +15,7 @@ object Versions { const val springVersion = "6.0.6" const val radarSpringAuthVersion = "1.2.1" const val guavaVersion = "32.1.3-jre" - const val radarJerseyVersion = "0.12.2" + const val radarJerseyVersion = "0.12.7-SNAPSHOT" const val jacksonKotlinVersion = "2.15.4" const val ktorVersion = "2.3.13" const val coroutinesVersion = "1.10.1" @@ -33,6 +30,11 @@ object Versions { const val quartzVersion = "2.5.0" const val firebaseAdminVersion = "9.3.0" const val jerseyBeanValidationVersion = "3.1.10" + const val protobufVersion = "3.25.5" + const val kotlinxSerializationVersion = "1.6.3" + const val mockkVersion = "1.14.4" + const val hamcrestVersion = "2.1" + const val assertjVersion = "3.24.2" const val project = "2.4.3" const val wrapper = "8.5" From ac85c63c7778242178154165a2d894cdf22c0d56 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 17:47:58 +0000 Subject: [PATCH 11/13] Sync changes from fix/filter-duplicate-notifications Reference: https://github.com/RADAR-base/RADAR-Appserver/pull/546 --- .../org/radarbase/appserver/jersey/entity/Task.kt | 12 ++++++++++++ .../impl/RandomRepeatQuestionnaireHandler.kt | 4 ++-- .../impl/SimpleRepeatQuestionnaireHandler.kt | 2 +- .../appserver/microservices/core/entity/Task.kt | 15 +++++++++++++-- .../impl/RandomRepeatQuestionnaireHandler.kt | 4 ++-- .../impl/SimpleRepeatQuestionnaireHandler.kt | 2 +- 6 files changed, 31 insertions(+), 8 deletions(-) diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/entity/Task.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/entity/Task.kt index ebcdac31..c18d32a4 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/entity/Task.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/entity/Task.kt @@ -35,6 +35,7 @@ import org.hibernate.annotations.OnDeleteAction import org.radarbase.appserver.jersey.dto.protocol.AssessmentType import org.radarbase.appserver.jersey.event.state.TaskState import java.sql.Timestamp +import java.util.Objects @Entity @Table(name = "tasks") @@ -232,4 +233,15 @@ class Task : AuditModel() { "status=$status" + ")" } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Task) return false + return user == other.user + && timestamp == other.timestamp + && name == other.name + } + + override fun hashCode(): Int = Objects.hash(user, timestamp, name) + } diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt index 58ca8f90..30779daa 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt @@ -60,7 +60,7 @@ class RandomRepeatQuestionnaireHandler : ProtocolHandler { val randomUnitsFromZeroBetween = repeatQuestionnaire?.randomUnitsFromZeroBetween ?: return emptyList() val completionWindow = calculateCompletionWindow(assessment.protocol?.completionWindow) - val tasks = mutableListOf() + val tasks = LinkedHashSet() for (referenceTimestamp in referenceTimestamps) { val timePeriod = TimePeriod().apply { unit = repeatQuestionnaire.unit } for (range in randomUnitsFromZeroBetween) { @@ -72,7 +72,7 @@ class RandomRepeatQuestionnaireHandler : ProtocolHandler { tasks.add(task) } } - return tasks + return tasks.toList() } private fun getRandomAmountInRange(range: Array): Int { diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt index 3cc76719..85491e3a 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt @@ -80,7 +80,7 @@ class SimpleRepeatQuestionnaireHandler : ProtocolHandler { } } }.awaitAll() - } + }.toCollection(LinkedHashSet()).toList() } private fun calculateCompletionWindow(completionWindow: TimePeriod?): Long { diff --git a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/entity/Task.kt b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/entity/Task.kt index e390e089..ff9f646f 100644 --- a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/entity/Task.kt +++ b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/entity/Task.kt @@ -30,6 +30,7 @@ import jakarta.validation.constraints.NotNull import org.radarbase.appserver.microservices.core.dto.protocol.AssessmentType import org.radarbase.appserver.microservices.core.event.state.TaskState import java.sql.Timestamp +import java.util.Objects @Entity @Table(name = "tasks") @@ -186,7 +187,7 @@ class Task : AuditModel() { } fun build(): Task { - var task = Task() + val task = Task() task.id = id task.completed = completed task.timestamp = timestamp @@ -207,6 +208,16 @@ class Task : AuditModel() { } override fun toString(): String { - return "Task" + "(id=$id, " + "completed=$completed, " + "timestamp=$timestamp, " + "name=$name, type=$type, " + "estimatedCompletionTime=$estimatedCompletionTime, " + "completionWindow=$completionWindow, " + "warning=$warning, " + "isClinical=$isClinical, " + "timeCompleted=$timeCompleted, " + "showInCalendar=$showInCalendar, " + "isDemo=$isDemo, " + "priority=$priority, " + "nQuestions=$nQuestions, " + "user=$userId, " + "status=$status" + ")" + return "Task(id=$id, completed=$completed, timestamp=$timestamp, name=$name, type=$type, estimatedCompletionTime=$estimatedCompletionTime, completionWindow=$completionWindow, warning=$warning, isClinical=$isClinical, timeCompleted=$timeCompleted, showInCalendar=$showInCalendar, isDemo=$isDemo, priority=$priority, nQuestions=$nQuestions, user=$userId, status=$status)" } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Task) return false + return userId == other.userId + && timestamp == other.timestamp + && name == other.name + } + + override fun hashCode(): Int = Objects.hash(userId, timestamp, name) } diff --git a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt index 93c36ffb..c2e084cc 100644 --- a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt +++ b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/RandomRepeatQuestionnaireHandler.kt @@ -60,7 +60,7 @@ class RandomRepeatQuestionnaireHandler : ProtocolHandler { val randomUnitsFromZeroBetween = repeatQuestionnaire?.randomUnitsFromZeroBetween ?: return emptyList() val completionWindow = calculateCompletionWindow(assessment.protocol?.completionWindow) - val tasks = mutableListOf() + val tasks = LinkedHashSet() for (referenceTimestamp in referenceTimestamps) { val timePeriod = TimePeriod().apply { unit = repeatQuestionnaire.unit } for (range in randomUnitsFromZeroBetween) { @@ -72,7 +72,7 @@ class RandomRepeatQuestionnaireHandler : ProtocolHandler { tasks.add(task) } } - return tasks + return tasks.toList() } private fun getRandomAmountInRange(range: Array): Int { diff --git a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt index 23b353e2..dcb35a05 100644 --- a/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt +++ b/microservices/core/src/main/kotlin/org/radarbase/appserver/microservices/core/service/protocol/handler/impl/SimpleRepeatQuestionnaireHandler.kt @@ -80,7 +80,7 @@ class SimpleRepeatQuestionnaireHandler : ProtocolHandler { } } }.awaitAll() - } + }.toCollection(LinkedHashSet()).toList() } private fun calculateCompletionWindow(completionWindow: TimePeriod?): Long { From e77023aff632f27965628d9994ca215b3cab2781 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 18:05:26 +0000 Subject: [PATCH 12/13] Remove schedule generation on user creation --- .../appserver/jersey/service/UserService.kt | 8 ----- .../schedule/QuestionnaireScheduleService.kt | 23 -------------- .../user/service/UserServiceImpl.kt | 31 ------------------- 3 files changed, 62 deletions(-) diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/UserService.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/UserService.kt index 41ea4fe5..c35df5d0 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/UserService.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/UserService.kt @@ -29,7 +29,6 @@ import org.radarbase.appserver.jersey.mapper.Mapper import org.radarbase.appserver.jersey.mapper.UserMapper import org.radarbase.appserver.jersey.repository.ProjectRepository import org.radarbase.appserver.jersey.repository.UserRepository -import org.radarbase.appserver.jersey.service.questionnaire.schedule.QuestionnaireScheduleService import org.radarbase.appserver.jersey.utils.checkInvalidDetails import org.radarbase.appserver.jersey.utils.checkPresence import org.radarbase.jersey.exception.HttpNotFoundException @@ -42,7 +41,6 @@ class UserService @Inject constructor( @Named(USER_MAPPER) val userMapper: Mapper, val userRepository: UserRepository, val projectRepository: ProjectRepository, - val scheduleService: QuestionnaireScheduleService, config: AppserverConfig, ) { private val sendEmailNotifications: Boolean = config.email.enabled ?: false @@ -219,8 +217,6 @@ class UserService @Inject constructor( userRepository.add(this) } - this.scheduleService.generateScheduleForUser(savedUser) - return userMapper.entityToDto(savedUser) } @@ -274,10 +270,6 @@ class UserService @Inject constructor( "user_not_found", "User with id ${user.id} not found.", ) - // Generate schedule for user - if (user.attributes != userDto.attributes || user.timezone != userDto.timezone || user.enrolmentDate != userDto.enrolmentDate || user.language != userDto.language) { - this.scheduleService.generateScheduleForUser(savedUser) - } return userMapper.entityToDto(savedUser) } diff --git a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/questionnaire/schedule/QuestionnaireScheduleService.kt b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/questionnaire/schedule/QuestionnaireScheduleService.kt index 21380758..9b844a88 100644 --- a/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/questionnaire/schedule/QuestionnaireScheduleService.kt +++ b/appserver-jersey/src/main/kotlin/org/radarbase/appserver/jersey/service/questionnaire/schedule/QuestionnaireScheduleService.kt @@ -31,15 +31,12 @@ import org.radarbase.appserver.jersey.search.TaskSpecificationsBuilder import org.radarbase.appserver.jersey.service.FcmNotificationService import org.radarbase.appserver.jersey.service.TaskService import org.radarbase.appserver.jersey.service.github.protocol.ProtocolGenerator -import org.radarbase.appserver.jersey.service.scheduling.SchedulingService import org.radarbase.appserver.jersey.utils.checkInvalidDetails import org.radarbase.appserver.jersey.utils.checkPresence import org.radarbase.jersey.exception.HttpNotFoundException -import org.radarbase.jersey.service.AsyncCoroutineService import org.slf4j.Logger import org.slf4j.LoggerFactory import java.sql.Timestamp -import java.time.Duration import java.time.Instant @Suppress("unused") @@ -50,20 +47,9 @@ class QuestionnaireScheduleService @Inject constructor( private val projectRepository: ProjectRepository, private val taskService: TaskService, private val notificationService: FcmNotificationService, - schedulingService: SchedulingService, - asyncService: AsyncCoroutineService, ) { private val subjectScheduleMap: HashMap = hashMapOf() - private val cleanScheduleRef: SchedulingService.RepeatReference = schedulingService.repeat( - Duration.ofMillis(3_600_000), - Duration.ofMillis(5_000), - ) { - asyncService.runBlocking { - generateAllSchedules() - } - } - suspend fun getTasksUsingProjectIdAndSubjectId(subjectId: String, projectId: String): List { return getTasksForUser(subjectAndProjectExistsElseThrow(subjectId, projectId)) } @@ -179,15 +165,6 @@ class QuestionnaireScheduleService @Inject constructor( return schedule } - suspend fun generateAllSchedules() { - logger.info("Generating all schedules") - userRepository.findAll().also { users: List -> - users.forEach { - generateScheduleForUser(it) - } - } - } - fun getScheduleForSubject(subjectId: String): Schedule { val schedule: Schedule? = subjectScheduleMap[subjectId] return schedule ?: Schedule() diff --git a/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/service/UserServiceImpl.kt b/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/service/UserServiceImpl.kt index e2928fc3..2000e365 100644 --- a/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/service/UserServiceImpl.kt +++ b/microservices/user-service/src/main/kotlin/org/radarbase/appserver/microservices/user/service/UserServiceImpl.kt @@ -18,11 +18,7 @@ package org.radarbase.appserver.microservices.user.service import jakarta.inject.Inject import jakarta.inject.Named -import jakarta.ws.rs.core.Response -import kotlinx.serialization.json.Json import org.radarbase.appserver.microservices.contract.calls.ProjectServiceContract -import org.radarbase.appserver.microservices.contract.calls.QuestionnaireScheduleContract -import org.radarbase.appserver.microservices.contract.exception.ProxyResponseException import org.radarbase.appserver.microservices.contract.utils.Utils.deserializeDtoFromContract import org.radarbase.appserver.microservices.core.dto.ProjectDto import org.radarbase.appserver.microservices.core.dto.fcm.FcmUserDto @@ -53,9 +49,6 @@ class UserServiceImpl @Inject constructor( ) : UserService { private val sendEmailNotifications: Boolean = config.email.enabled private val projectServiceUrl = config.contract.project - private val taskServiceUrl = config.contract.task - - private val json = Json { ignoreUnknownKeys = true; coerceInputValues = true } /** * Retrieves all users associated with the projects. @@ -244,7 +237,6 @@ class UserServiceImpl @Inject constructor( }.run { userRepository.add(this) } - generateScheduleUsingProjectIdAndSubjectId(savedUser.projectId, savedUser.subjectId) return userMapper.entityToDto(savedUser) } @@ -301,14 +293,6 @@ class UserServiceImpl @Inject constructor( "user_not_found", "User with id ${user.id} not found.", ) - // Generate schedule for user - if (user.attributes != userDto.attributes || - user.timezone != - userDto.timezone || - user.enrolmentDate?.equals(userDto.enrolmentDate) != true || - user.language != userDto.language) { - generateScheduleUsingProjectIdAndSubjectId(updatedUser.projectId, updatedUser.subjectId) - } return userMapper.entityToDto(updatedUser) } @@ -360,21 +344,6 @@ class UserServiceImpl @Inject constructor( this.userRepository.delete(user) } - private suspend fun generateScheduleUsingProjectIdAndSubjectId(projectId: String?, subjectId: String?) { - return QuestionnaireScheduleContract.generateScheduleUsingProjectIdAndSubjectId( - requireNotNull(projectId) { "User's Project id must not be null" }, - requireNotNull(subjectId) { "Subject id must not be null" }, - taskServiceUrl - ).let { - if (it.status !in 200 .. 299) { - throw ProxyResponseException( - Response.Status.fromStatusCode(it.status), - it.body?.decodeToString() ?: "Upstream sent an incorrect response", - ) - } - } - } - companion object { private const val FCM_TOKEN_PREFIX = "unregistered_" From 2005dad7038b5cceb1019b49b62ce0198f132da4 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Tue, 17 Mar 2026 18:06:11 +0000 Subject: [PATCH 13/13] Swagger UI setup for cloud messaging service --- .../CloudMessagingServiceResourceEnhancerFactory.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/enhancer/factory/CloudMessagingServiceResourceEnhancerFactory.kt b/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/enhancer/factory/CloudMessagingServiceResourceEnhancerFactory.kt index bca5030f..f4d6856f 100644 --- a/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/enhancer/factory/CloudMessagingServiceResourceEnhancerFactory.kt +++ b/microservices/cloud-messaging-service/src/main/kotlin/org/radarbase/appserver/microservices/cloud/messaging/enhancer/factory/CloudMessagingServiceResourceEnhancerFactory.kt @@ -16,6 +16,8 @@ package org.radarbase.appserver.microservices.cloud.messaging.enhancer.factory +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info import org.radarbase.appserver.microservices.cloud.messaging.config.CloudMessagingServiceConfig import org.radarbase.appserver.microservices.cloud.messaging.config.EmailConfig import org.radarbase.appserver.microservices.cloud.messaging.enhancer.CloudMessagingServiceResourceEnhancer @@ -39,6 +41,7 @@ class CloudMessagingServiceResourceEnhancerFactory(private val config: CloudMess properties = config.db.additionalProperties, liquibase = org.radarbase.jersey.hibernate.config.LiquibaseConfig( enable = config.db.liquibase.enabled, + changelogs = config.db.liquibase.changelogs, ), ) @@ -74,9 +77,18 @@ class CloudMessagingServiceResourceEnhancerFactory(private val config: CloudMess ) } + val openApi = OpenAPI().apply { + info = Info().apply { + title = "RADAR-Appserver Cloud Messaging Service" + description = "Manages notifications and data messages via FCM in the RADAR platform" + version = "2.4.3" + } + } + return listOf( CloudMessagingServiceResourceEnhancer(config, emailTransmitter), HibernateResourceEnhancer(dbConfig), + Enhancers.swagger(openApi), Enhancers.health, Enhancers.exception, )