diff --git a/README.md b/README.md index 7b6807c..51e0da2 100644 --- a/README.md +++ b/README.md @@ -1,164 +1,42 @@ -# template-microservice -This serves as a template for the microservices +# Notification Service -## Package structure +The Notification Service is a component designed to fulfill the following functions within the platform: -This package structure is based on multiple sources of best practices in Spring Boot, using roughly the "Package by layer" approach. -- *root* - - *config* - - *controller* - - *dapr* - - *dto* - - *exception* - - *persistence* - - *entity* - - *repository* - - *mapper* - - *service* - - *util* (optional, if needed) - - *validation* +1. **Event ingestion & routing:** Consumes cross-service notification events (e.g., Media, Content) and routes them to target recipients (explicit user list or course-wide broadcast). +2. **Persistence & recipient tracking:** Persists notifications and creates recipient rows per target user with status management (`UNREAD`, `READ`, `DO_NOT_NOTIFY`). +3. **User preference filtering:** Respects per-user notification settings to mute specific categories; if the settings service is unavailable, it fails open to avoid missed notifications. +4. **Delivery interfaces:** Exposes unread counts, listing, mark-as-read (single/all), delete (single/all), and provides a live “notification added” stream. -Detailed description of the packages: +## Notification Service -### Root package +## Environment variables -This should be named after the microservice itself. This is the root package for the microservice. It contains the `Application.java` file (or of similar name), which is the entry point for the microservice. Usually, this is the only class in this package. +### Relevant for deployment -### Config package -This package should contain any classes that are used to configure the application. This includes [Sprint Boot configuration classes](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html) but might also contain anything else related to configuration the microservice. -The classes that are in this package should not be deleted in the actual microservice as they provide useful functionality. +| Name | Description | Default Value in Prod Environment | +|------------------------------|-------------------------------------|---------------------------------------------------------------------------------------| +| `spring.datasource.url` | PostgreSQL database URL | `jdbc:postgresql://notification-service-db-postgresql:5432/notification-service` | +| `spring.datasource.username` | Database username | `postgres` | +| `spring.datasource.password` | Database password | `*secret*` | +| `DAPR_HTTP_PORT` | Dapr HTTP Port | `3500` | +| `DAPR_GRPC_PORT` | Dapr gRPC Port | `50001` | -### Controller package +### Other properties -**Location:src/main/java/de/unistuttgart/iste/meitrex/{service_name}/controller** +| Name | Description | Example / Default | +|------------------------------------|---------------------------------------------------|--------------------------------------| +| `services.course.base-url` | Course Service base URL (member lookup) | `http://course-service:8080` | +| `services.user-settings.base-url` | User Settings Service base URL (preference checks)| `http://user-settings-service:8080` | +| `notifications.cleanup.enabled` | Remove orphan notifications with no recipients | `true` | +| `notifications.stream.buffer-size` | In-memory buffer size for the live publisher | `256` | -This package contains the GraphQL controllers (and other types of controllers if needed). The GraphQL controllers are annotated with the `@Controller` annotation. Controllers contain no business logic, but only delegate the requests to the service layer. They handle the "technical stuff" of the request. +## GraphQL API -In some services, there is also a class called SubscriptionController which handles all dapr event subscriptions. +The API is documented in the [`api.md` file](api.md). -More information can be found in -the [Controller package](src/main/java/de/unistuttgart/iste/meitrex/template/controller/package-info.java). +It can be accessed at `/graphql` and explored via the GraphiQL Playground at `/graphiql`. -### Dapr package +## Get started -**Location**:src/main/java/de/unistuttgart/iste/meitrex/{service_name}/dapr - -This package should contain all classes that are used to communicate with Dapr, e.g. using pub sub. - -### DTO package - -**This package will not be located in the src/main/java folder, but in the build/generated folder.** - -This package contains the generated DTOs (data transfer objects) from the GraphQL schema. The DTOs are generated when building the project with gradle. - -If not necessary, no other files should be added manually to this package. - -#### Why both DTOs and Entities? - -The DTOs are used to transfer data between the GraphQL controller and the service layer. The entities are used to persist data in the database. This is done to separate the data transfer from the data persistence. This is a common approach in Spring Boot applications as it can happen that we want to store more data in the database than we want to transfer to the client or vice versa. - -### Exception package - -**Location**:src/main/java/de/unistuttgart/iste/meitrex/{service_name}/exception - -This package is used for exception handling. Note that with GraphQL, the exceptions are not thrown directly, but are wrapped in a `GraphQLException`, which is different that from the usual Spring Boot approach. - -More information can be found in -the [Exception package](src/main/java/de/unistuttgart/iste/meitrex/template/exception/package-info.java). - -### Persistence package - -**Location**:src/main/java/de/unistuttgart/iste/meitrex/{service_name}/persistence - -This package contains all classes that are used to persist data in the database. This includes the entities, the mapping -logic between entities and DTOs, as well as the repositories. - -This package handles the calls to the database and defines the database entities. It is structured into three sub-packages: - -#### 1. entity -This package contains the database entities. - -#### 2. repository -This package contains the interfaces to the database, also known as Data Access Objects (DAOs), used to perform various database operations. Note that these interfaces may sometimes be empty, especially when the default methods provided by the Spring Framework are sufficient for the required operations. - -#### 3. mapper -The 'mapper' package is responsible for the mapping logic between the database entities and the data types defined in the GraphQL schema. Specifically, it maps the database entity classes to the corresponding classes generated from the GraphQL schema. - -This structure helps organize the database-related components of the project, making it easier to manage and maintain. - -More information can be found in -the [Entity package](src/main/java/de/unistuttgart/iste/meitrex/template/persistence/entity/package-info.java) and -the [Repository package](src/main/java/de/unistuttgart/iste/meitrex/template/persistence/repository/package-info.java). - -### Service package - -**Location**:src/main/java/de/unistuttgart/iste/meitrex/{service_name}/service - -This package contains all classes that are used to handle the business logic of the microservice. Services are annotated with the `@Service` annotation. Services contain only business logic and delegate the data access to the persistence layer (repositories). - -More information can be found in -the [Service package](src/main/java/de/unistuttgart/iste/meitrex/template/service/package-info.java). - -### Validation package - -**Location**:src/main/java/de/unistuttgart/iste/meitrex/{service_name}/validation - -This package should contain the *class-level* validation logic, i.e. the validation logic that is not directly related to a specific field, e.g. validation if an end date is after a start date. - -Field-level validation logic should not be placed in this package, but in the graphql schema, via directives. -If these directives are not sufficient, the validation logic can also be placed in this package. - -## Getting Started - -### Todos - -Follow the guide in the wiki: https://github.com/MEITREX/wiki/blob/main/dev-manuals/backend/new-service.md - -Addtionally, after cloning the repository, you need to do the following steps: -- [ ] Setup the gradle files correctly. This means - - [ ] Change the project name in the `settings.gradle` file - - [ ] Change the sonar project key in the `build.gradle` file (should be MEITREX_repository_name) - - [ ] Add/Remove dependencies in the `build.gradle` file -- [ ] Rename the package in the `src/main/java` folder to a more suitable name (should be the service name) -- [ ] Remove the package-info.java files in the `src/main/java` folder, if the file is present (or update with the microservice specific information) -- [ ] Update the application.properties file in the `src/main/resources` folder (check the TODOS in the file) -- [ ] Change the ports and name of the database in the docker-compose.yml (see wiki on how to) -- [ ] Define the GraphQL schema in the `src/main/resources/schema.graphqls` file - - - -After creating a new service you need to do the following: -- [ ] Add the repository to sonarcloud, follow the instructions for extra configuration, unselect automatic analysis and choose github actions, only the first step needs to be completed -- [ ] Add SONAR_TOKEN to the service repository secrets on Github (this requires you to have admin permissions on sonarcloud) - -### Pull new changes from this template - -If this template changes and you want to pull the changes to the actual microservice, you can run the following commands: -```bash -git remote add template https://github.com/MEITREX/template_microservice # only necessary once -git fetch --all -git checkout [branch] # replace [branch] with the branch name you want the changes to be merged into (preferably not main) -git merge template/main --allow-unrelated-histories -# you will probably need to commit afterwars -``` - -### Guides -The following guides illustrate how to use some features concretely: - -* [Building a GraphQL service](https://spring.io/guides/gs/graphql-server/) -* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) -* [Validation with GraphQL directives](https://github.com/graphql-java/graphql-java-extended-validation/blob/master/README.md) -* [Error handling](https://www.baeldung.com/spring-graphql-error-handling) - -### Reference Documentation -For further reference, please consider the following sections: - -* [Official Gradle documentation](https://docs.gradle.org) -* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.6/gradle-plugin/reference/html/) -* [Spring Configuration Processor](https://docs.spring.io/spring-boot/docs/3.0.6/reference/htmlsingle/#appendix.configuration-metadata.annotation-processor) -* [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/3.0.6/reference/htmlsingle/#using.devtools) -* [Spring for GraphQL](https://docs.spring.io/spring-boot/docs/3.0.6/reference/html/web.html#web.graphql) -* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.0.6/reference/htmlsingle/#data.sql.jpa-and-spring-data) -* [Validation](https://docs.spring.io/spring-boot/docs/3.0.6/reference/htmlsingle/#io.validation) -* [Generating Sonarqube Token](https://docs.sonarqube.org/latest/user-guide/user-account/generating-and-using-tokens/) -* [Adding secrets on Github](https://docs.github.com/en/actions/security-guides/encrypted-secrets) +A guide how to start development can be found in the +[wiki](https://meitrex.readthedocs.io/en/latest/dev-manuals/backend/get-started.html). diff --git a/api.md b/api.md new file mode 100644 index 0000000..f64c57d --- /dev/null +++ b/api.md @@ -0,0 +1,475 @@ +# Notification Service API + +
+ Table of Contents + + * [Query](#query) + * [Mutation](#mutation) + * [Subscription](#subscription) + * [Objects](#objects) + * [NotificationData](#notificationdata) + * [PaginationInfo](#paginationinfo) + * [Inputs](#inputs) + * [DateTimeFilter](#datetimefilter) + * [IntFilter](#intfilter) + * [Pagination](#pagination) + * [StringFilter](#stringfilter) + * [Enums](#enums) + * [SortDirection](#sortdirection) + * [Scalars](#scalars) + * [Boolean](#boolean) + * [Date](#date) + * [DateTime](#datetime) + * [Int](#int) + * [LocalTime](#localtime) + * [String](#string) + * [Time](#time) + * [UUID](#uuid) + * [Url](#url) + +
+ +## Query + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldArgumentTypeDescription
notifications[NotificationData!]!
userIdUUID!
countUnreadInt!
userIdUUID!
+ +## Mutation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldArgumentTypeDescription
markAllReadInt!
userIdUUID!
markOneReadInt!
userIdUUID!
notificationIdUUID!
deleteOneNotificationInt!
userIdUUID!
notificationIdUUID!
deleteAllNotificationsInt!
userIdUUID!
+ +## Subscription + + + + + + + + + + + + + + + + + + + + + +
FieldArgumentTypeDescription
notificationAddedNotificationData!
userIdUUID!
+ +## Objects + +### NotificationData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldArgumentTypeDescription
idUUID!
titleString!
descriptionString!
hrefString!
createdAtDateTime!
readBoolean!
+ +### PaginationInfo + +Return type for information about paginated results. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldArgumentTypeDescription
pageInt! + +The current page number. + +
sizeInt! + +The number of elements per page. + +
totalElementsInt! + +The total number of elements across all pages. + +
totalPagesInt! + +The total number of pages. + +
hasNextBoolean! + +Whether there is a next page. + +
+ +## Inputs + +### DateTimeFilter + +Filter for date values. +If multiple filters are specified, they are combined with AND. + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
afterDateTime + +If specified, filters for dates after the specified value. + +
beforeDateTime + +If specified, filters for dates before the specified value. + +
+ +### IntFilter + +Filter for integer values. +If multiple filters are specified, they are combined with AND. + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
equalsInt + +An integer value to match exactly. + +
greaterThanInt + +If specified, filters for values greater than to the specified value. + +
lessThanInt + +If specified, filters for values less than to the specified value. + +
+ +### Pagination + +Specifies the page size and page number for paginated results. + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
pageInt! + +The page number, starting at 0. +If not specified, the default value is 0. +For values greater than 0, the page size must be specified. +If this value is larger than the number of pages, an empty page is returned. + +
sizeInt! + +The number of elements per page. + +
+ +### StringFilter + +Filter for string values. +If multiple filters are specified, they are combined with AND. + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
equalsString + +A string value to match exactly. + +
containsString + +A string value that must be contained in the field that is being filtered. + +
ignoreCaseBoolean! + +If true, the filter is case-insensitive. + +
+ +## Enums + +### SortDirection + +Specifies the sort direction, either ascending or descending. + + + + + + + + + + + + + + + + + + +
ValueDescription
ASC
DESC
+ +## Scalars + +### Boolean + +The `Boolean` scalar type represents `true` or `false`. + +### Date + +### DateTime + +### Int + +The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. + +### LocalTime + +### String + +The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. + +### Time + +### UUID + +### Url + diff --git a/build.gradle b/build.gradle index 7144f4a..328ab61 100644 --- a/build.gradle +++ b/build.gradle @@ -108,22 +108,28 @@ repositories { } dependencies { - implementation 'de.unistuttgart.iste.meitrex:meitrex-common:1.0.2' + implementation 'de.unistuttgart.iste.meitrex:meitrex-common:1.4.9' + implementation 'de.unistuttgart.iste.meitrex:course_service' + implementation 'de.unistuttgart.iste.meitrex:user_service' + + implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-graphql' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.modelmapper:modelmapper:3.+' + implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'com.graphql-java:graphql-java-extended-scalars:22.0' implementation 'com.graphql-java:graphql-java-extended-validation:22.0' + implementation 'io.dapr:dapr-sdk:1.9.0' // Dapr's core SDK with all features, except Actors. + implementation 'io.dapr:dapr-sdk-springboot:1.9.0' // Dapr's SDK integration with SpringBoot compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'de.unistuttgart.iste.meitrex:meitrex-common-test:1.0.2' + testImplementation 'de.unistuttgart.iste.meitrex:meitrex-common-test:1.4.9' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework:spring-webflux' testImplementation 'org.springframework.graphql:spring-graphql-test' diff --git a/docker-compose.yml b/docker-compose.yml index 153fb7b..4667c48 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,9 @@ version: "3" services: - database-notification: + database: image: pgvector/pgvector:pg16 + command: -c 'max_connections=500' restart: unless-stopped expose: - 5432 @@ -14,7 +15,7 @@ services: environment: - POSTGRES_USER=root - POSTGRES_PASSWORD=root - - POSTGRES_CREATE_DB_GAMIFICATION_SERVICE=notification_service + - POSTGRES_CREATE_DB_NOTIFICATION_SERVICE=notification_service app-notification: build: context: ./../notification_service # necessary because otherwise docker-compose overrides the context path when merging multiple docker-compose.yml files @@ -28,7 +29,7 @@ services: - "3100:3100" - "3101:3101" depends_on: - - database-notification + - database environment: SPRING_DATASOURCE_URL: jdbc:postgresql://database:5432/notification_service SPRING_DATASOURCE_USERNAME: root @@ -53,10 +54,9 @@ services: expose: - "6379" volumes: - notificationdata: dbdata: testdata: networks: default: name: dapr-network - external: true + external: true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..48521e4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,116 @@ +{ + "name": "notification_service", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "graphql-markdown": "^7.3.0" + } + }, + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "license": "MIT" + }, + "node_modules/graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-markdown": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/graphql-markdown/-/graphql-markdown-7.3.0.tgz", + "integrity": "sha512-xTgP+W156DD3k55KpwvNUtV7RTJmGKQhvCzseUXSnJf7KrZozhyyxhq4bv1IiOJgGeRjt5zwHBbEcAEEWCacrg==", + "license": "MIT", + "dependencies": { + "deep-diff": "^1.0.2", + "lodash.isplainobject": "^4.0.6", + "minimist": "^1.2.6", + "node-fetch": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "bin": { + "graphql-markdown": "src/index.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.2 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..421e172 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "graphql-markdown": "^7.3.0" + } +} diff --git a/settings.gradle b/settings.gradle index 6268e2a..c0461ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,10 @@ sourceControl { gitRepository(uri('https://github.com/MEITREX/common_test')) { producesModule('de.unistuttgart.iste.meitrex:meitrex-common-test') } + gitRepository(uri('https://github.com/MEITREX/course_service')) { + producesModule('de.unistuttgart.iste.meitrex:course_service') + } + gitRepository(uri('https://github.com/MEITREX/user_service')) { + producesModule('de.unistuttgart.iste.meitrex:user_service') + } } diff --git a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/DownstreamClientsConfiguration.java b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/DownstreamClientsConfiguration.java new file mode 100644 index 0000000..143a201 --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/DownstreamClientsConfiguration.java @@ -0,0 +1,43 @@ +package de.unistuttgart.iste.meitrex.notification_service.config; + +import de.unistuttgart.iste.meitrex.course_service.client.CourseServiceClient; +import de.unistuttgart.iste.meitrex.user_service.client.SettingsServiceClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.client.GraphQlClient; +import org.springframework.graphql.client.HttpGraphQlClient; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Configures downstream GraphQL clients used by the notification-service. + *

+ * URLs are provided via application.properties or environment variables: + *

+ */ +@Configuration +public class DownstreamClientsConfiguration { + + @Bean + public CourseServiceClient courseServiceClient( + @Value("${course_service.url}") final String courseServiceUrl) { + final WebClient webClient = WebClient.builder() + .baseUrl(courseServiceUrl) + .build(); + final GraphQlClient gql = HttpGraphQlClient.builder(webClient).build(); + return new CourseServiceClient(gql); + } + + @Bean + public SettingsServiceClient settingsServiceClient( + @Value("${user_service.url}") final String userServiceUrl) { + final WebClient webClient = WebClient.builder() + .baseUrl(userServiceUrl) + .build(); + final GraphQlClient gql = HttpGraphQlClient.builder(webClient).build(); + return new SettingsServiceClient(gql); + } +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/GraphQlScalarsAndValidationConfiguration.java b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/GraphQlScalarsAndValidationConfiguration.java deleted file mode 100644 index f834a32..0000000 --- a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/GraphQlScalarsAndValidationConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.unistuttgart.iste.meitrex.notification_service.config; - -import graphql.scalars.ExtendedScalars; -import graphql.validation.rules.OnValidationErrorStrategy; -import graphql.validation.rules.ValidationRules; -import graphql.validation.schemawiring.ValidationSchemaWiring; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.graphql.execution.RuntimeWiringConfigurer; - -/** - * This class sets up the validation rules for the GraphQL schema and the scalar types. - */ -@Configuration -public class GraphQlScalarsAndValidationConfiguration { - - @Bean - public RuntimeWiringConfigurer runtimeWiringConfigurer() { - - ValidationRules validationRules = ValidationRules.newValidationRules() - .onValidationErrorStrategy(OnValidationErrorStrategy.RETURN_NULL) - .build(); - - ValidationSchemaWiring schemaWiring = new ValidationSchemaWiring(validationRules); - - return wiringBuilder -> wiringBuilder - .directiveWiring(schemaWiring) - .scalar(ExtendedScalars.DateTime) - .scalar(ExtendedScalars.Date) - .scalar(ExtendedScalars.Time) - .scalar(ExtendedScalars.LocalTime) - .scalar(ExtendedScalars.UUID) - .scalar(ExtendedScalars.Url); - } -} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/RequestHeaderUserInterceptor.java b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/RequestHeaderUserInterceptor.java new file mode 100644 index 0000000..aa4fcfd --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/RequestHeaderUserInterceptor.java @@ -0,0 +1,24 @@ +package de.unistuttgart.iste.meitrex.notification_service.config; + +import de.unistuttgart.iste.meitrex.common.user_handling.RequestHeaderUserProcessor; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.server.WebGraphQlInterceptor; +import org.springframework.graphql.server.WebGraphQlRequest; +import org.springframework.graphql.server.WebGraphQlResponse; +import reactor.core.publisher.Mono; + +/** + * This class is used to add data from the request headers to the GraphQL context. + */ +@Configuration +public class RequestHeaderUserInterceptor implements WebGraphQlInterceptor { + @NotNull + @Override + @SneakyThrows + public Mono intercept(@NotNull WebGraphQlRequest request, @NotNull Chain chain) { + RequestHeaderUserProcessor.process(request); + return chain.next(request); + } +} \ No newline at end of file diff --git a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/package-info.java b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/package-info.java deleted file mode 100644 index 964846a..0000000 --- a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/config/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package should contain any classes that are used to configure the application. - */ -package de.unistuttgart.iste.meitrex.notification_service.config; \ No newline at end of file diff --git a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/controller/NotificationController.java b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/controller/NotificationController.java index 3f9f304..c575fbc 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/notification_service/controller/NotificationController.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/notification_service/controller/NotificationController.java @@ -1,25 +1,107 @@ package de.unistuttgart.iste.meitrex.notification_service.controller; +import de.unistuttgart.iste.meitrex.common.user_handling.LoggedInUser; +import org.springframework.graphql.data.method.annotation.ContextValue; -import de.unistuttgart.iste.meitrex.generated.dto.Template; -import de.unistuttgart.iste.meitrex.notification_service.service.TemplateService; +import de.unistuttgart.iste.meitrex.generated.dto.NotificationData; +import de.unistuttgart.iste.meitrex.notification_service.service.NotificationService; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.data.method.annotation.SubscriptionMapping; import org.springframework.stereotype.Controller; import java.util.List; +import java.util.UUID; -@Slf4j +/** + * GraphQL controller exposing queries, mutations and subscriptions for notifications. + */ @Controller @RequiredArgsConstructor public class NotificationController { - private final TemplateService notificationService; + private final NotificationService notificationService; - @QueryMapping - public List