Open-sourced generic, dynamic POC, RESTful alerting API

This commit is contained in:
2024-02-16 17:37:37 -07:00
parent b2ed41c549
commit 0d1d154c7a
70 changed files with 3272 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
id("io.swagger.core.v3.swagger-gradle-plugin") version "2.1.9"
kotlin("plugin.jpa") version "1.5.10"
}
tasks.withType<JavaCompile> {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
}
dependencies {
"implementation"(project(":persistence"))
"implementation"(project(":amqp"))
implementation("org.springframework.boot:spring-boot-starter-aop")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-tomcat")
implementation("javax.validation:validation-api")
}
tasks.getByName<BootJar>("bootJar") {
enabled = true
mainClass.set("com.poc.alerting.api.AlertingApi")
}
@@ -0,0 +1,26 @@
package com.poc.alerting.api;
import java.beans.FeatureDescriptor;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
public final class PropertyCopier {
private PropertyCopier() {
}
public static void copyNonNullProperties(final Object source, final Object target, final List<String> ignoredProperties) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source, ignoredProperties));
}
private static String[] getNullPropertyNames(final Object source, final List<String> ignoredProperties) {
final BeanWrapper wrappedSource = new BeanWrapperImpl(source);
return Stream.of(wrappedSource.getPropertyDescriptors())
.map(FeatureDescriptor::getName)
.filter(propertyName -> wrappedSource.getPropertyValue(propertyName) == null || (ignoredProperties != null && ignoredProperties.contains(propertyName)))
.toArray(String[]::new);
}
}
@@ -0,0 +1,11 @@
package com.poc.alerting.api
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication(scanBasePackages = ["com.poc.alerting"])
open class AlertingApi
fun main(args: Array<String>) {
runApplication<AlertingApi>(*args)
}
@@ -0,0 +1,95 @@
package com.poc.alerting.api.controller
import com.poc.alerting.amqp.AlertingAmqpMessage.Add
import com.poc.alerting.amqp.AlertingAmqpMessage.Delete
import com.poc.alerting.amqp.AlertingAmqpMessage.Pause
import com.poc.alerting.amqp.AlertingAmqpMessage.Resume
import com.poc.alerting.amqp.AlertingAmqpMessage.Update
import com.poc.alerting.amqp.RabbitSender
import com.poc.alerting.api.PropertyCopier
import com.poc.alerting.persistence.dto.Alert
import com.poc.alerting.persistence.repositories.AccountRepository
import com.poc.alerting.persistence.repositories.AlertRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid
@RestController
@Validated
open class AlertController @Autowired constructor(
private val accountRepository: AccountRepository,
private val alertRepository: AlertRepository,
private val rabbitSender: RabbitSender
) {
@PostMapping("/accounts/{account_id}/alerts")
open fun addAlert(@PathVariable("account_id") accountId: String,
@RequestBody body: @Valid Alert
): ResponseEntity<Alert> {
val account = accountRepository.findByExtId(accountId)
body.account = account
val alert = alertRepository.save(body).also {
rabbitSender.send(Add(body.frequency, body.extId, accountId))
}
return ResponseEntity(alert, HttpStatus.CREATED)
}
@DeleteMapping("/accounts/{account_id}/alerts/{alert_id}")
open fun deleteAlert(@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String
): ResponseEntity<Void> {
rabbitSender.send(Delete(alertId, accountId))
return ResponseEntity(HttpStatus.OK)
}
@GetMapping("/accounts/{account_id}/alerts/{alert_id}")
open fun getAlertById(
@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String,
@RequestParam(value = "include_recipients", required = false, defaultValue = "false") includeRecipients: @Valid Boolean?
): ResponseEntity<Alert> {
return ResponseEntity.ok(alertRepository.findByExtIdAndAccount_ExtId(alertId, accountId))
}
@GetMapping("/accounts/{account_id}/alerts")
open fun getListOfAlerts(
@PathVariable("account_id") accountId: String,
@RequestParam(value = "include_recipients", required = false, defaultValue = "false") includeRecipients: @Valid Boolean?
): ResponseEntity<List<Alert>> {
return ResponseEntity.ok(alertRepository.findAllByAccount_ExtId(accountId))
}
@PatchMapping("/accounts/{account_id}/alerts/{alert_id}")
open fun updateAlert(
@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String,
@RequestBody body: @Valid Alert
): ResponseEntity<Alert> {
val existingAlert = alertRepository.findByExtIdAndAccount_ExtId(alertId, accountId)
rabbitSender.send(Update(body.frequency, alertId, accountId))
body.enabled.let {
if (body.enabled.isNotBlank() && body.enabled.toBoolean() != existingAlert.enabled.toBoolean()) {
if (body.enabled.toBoolean()) {
rabbitSender.send(Resume(alertId, accountId))
} else {
rabbitSender.send(Pause(alertId, accountId))
}
}
}
PropertyCopier.copyNonNullProperties(body, existingAlert, null)
return ResponseEntity.ok(alertRepository.save(existingAlert))
}
}
@@ -0,0 +1,77 @@
package com.poc.alerting.api.controller
import com.poc.alerting.api.PropertyCopier
import com.poc.alerting.persistence.dto.Recipient
import com.poc.alerting.persistence.repositories.RecipientRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid
@RestController
@Validated
open class RecipientController @Autowired constructor(
private val recipientRepository: RecipientRepository
) {
@PostMapping("/accounts/{account_id}/alerts/{alert_id}/recipients")
open fun addRecipient(@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String,
@RequestBody body: @Valid MutableList<Recipient>
): ResponseEntity<Iterable<Recipient>> {
return ResponseEntity(recipientRepository.saveAll(body), HttpStatus.CREATED)
}
@DeleteMapping("/accounts/{account_id}/alerts/{alert_id}/recipients/{recipient_id}")
open fun deleteRecipient(
@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String,
@PathVariable("recipient_id") recipientId: String
): ResponseEntity<Void> {
recipientRepository.delete(recipientRepository.findByExtIdAndAlert_ExtIdAndAccount_ExtId(recipientId, alertId, accountId))
return ResponseEntity(HttpStatus.OK)
}
@GetMapping("/accounts/{account_id}/recipients")
open fun getAllRecipients(
@PathVariable("account_id") accountId: String
): ResponseEntity<List<Recipient>> {
return ResponseEntity.ok(recipientRepository.findAllByAccount_ExtId(accountId))
}
@GetMapping("/accounts/{account_id}/alerts/{alert_id}/recipients/{recipient_id}")
open fun getRecipient(
@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String,
@PathVariable("recipient_id") recipientId: String
): ResponseEntity<Recipient> {
return ResponseEntity.ok(recipientRepository.findByExtIdAndAlert_ExtIdAndAccount_ExtId(recipientId, alertId, accountId))
}
@GetMapping("/accounts/{account_id}/alerts/{alert_id}/recipients")
open fun getRecipientList(
@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String
): ResponseEntity<List<Recipient>> {
return ResponseEntity.ok(recipientRepository.findAllByAlert_ExtIdAndAccount_ExtId(alertId, accountId))
}
@PatchMapping("/accounts/{account_id}/alerts/{alert_id}/recipients/{recipient_id}")
open fun updateRecipient(
@PathVariable("account_id") accountId: String,
@PathVariable("alert_id") alertId: String,
@PathVariable("recipient_id") recipientId: String,
@RequestBody body: @Valid Recipient
): ResponseEntity<Recipient> {
val existingRecipient = recipientRepository.findByExtIdAndAlert_ExtIdAndAccount_ExtId(recipientId, alertId, accountId)
PropertyCopier.copyNonNullProperties(body, existingRecipient, null)
return ResponseEntity.ok(recipientRepository.save(existingRecipient))
}
}
+22
View File
@@ -0,0 +1,22 @@
server:
servlet:
contextPath: "/poc/alerting/v1"
port: 8080
spring:
jackson:
time-zone: UTC
default-property-inclusion: non_null
jpa:
hibernate:
ddl-auto: update
show-sql: true
datasource:
url: "jdbc:h2:tcp://localhost:9091/mem:alerting"
username: "defaultUser"
password: "secret"
application:
name: AlertingApi
main:
allow-bean-definition-overriding: true
profiles:
active: "api"