Open-sourced generic, dynamic POC, RESTful alerting API
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JpaPluginProjectSettings">
|
||||||
|
<option name="lastSelectedLanguage" value="Kotlin" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.springframework.amqp:spring-amqp")
|
||||||
|
implementation("org.springframework.amqp:spring-rabbit")
|
||||||
|
implementation("com.google.code.gson:gson")
|
||||||
|
implementation("org.projectlombok:lombok")
|
||||||
|
implementation("org.apache.commons:commons-lang3")
|
||||||
|
|
||||||
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<BootJar>("bootJar") {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<Jar>("jar") {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.poc.alerting.amqp;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public interface AmqpMessage extends Serializable {
|
||||||
|
String correlationId = "correlation-id";
|
||||||
|
|
||||||
|
String getRoutingKey();
|
||||||
|
|
||||||
|
String getExchange();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.poc.alerting.amqp;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public abstract class AmqpResponse<T> implements Serializable {
|
||||||
|
private T value;
|
||||||
|
private ExceptionType exceptionType;
|
||||||
|
private String exceptionMessage;
|
||||||
|
private Integer errorCode;
|
||||||
|
private String errorDescription;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.poc.alerting.amqp;
|
||||||
|
|
||||||
|
public enum ExceptionType {
|
||||||
|
INVALID_REQUEST_EXCEPTION(400),
|
||||||
|
INVALID_ACCOUNT_EXCEPTION(403),
|
||||||
|
NOT_FOUND_EXCEPTION(404),
|
||||||
|
REQUEST_TIMEOUT_EXCEPTION(408),
|
||||||
|
CONFLICT_EXCEPTION(409),
|
||||||
|
UNPROCESSABLE_ENTITY_EXCEPTION(422),
|
||||||
|
INTERNAL_SERVER_EXCEPTION(500);
|
||||||
|
|
||||||
|
private final int status;
|
||||||
|
|
||||||
|
ExceptionType(final int status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.poc.alerting.amqp;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.amqp.core.Message;
|
||||||
|
import org.springframework.amqp.core.MessageProperties;
|
||||||
|
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||||
|
import org.springframework.amqp.support.converter.MessageConverter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class GsonMessageConverter implements MessageConverter {
|
||||||
|
private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").create();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message toMessage(final Object object, final MessageProperties messageProperties) throws MessageConversionException {
|
||||||
|
if (!(object instanceof Serializable)) {
|
||||||
|
throw new MessageConversionException("Message object is not serializable");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String json = GSON.toJson(object);
|
||||||
|
if (json == null) {
|
||||||
|
throw new MessageConversionException("Unable to serialize the message to JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("json = {}", json);
|
||||||
|
|
||||||
|
final byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
|
||||||
|
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||||
|
messageProperties.setContentEncoding("UTF-8");
|
||||||
|
messageProperties.setContentLength(bytes.length);
|
||||||
|
messageProperties.setTimestamp(new Date());
|
||||||
|
messageProperties.setType(object.getClass().getName());
|
||||||
|
if (messageProperties.getMessageId() == null) {
|
||||||
|
messageProperties.setMessageId(UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Message(bytes, messageProperties);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new MessageConversionException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object fromMessage(final Message message) throws MessageConversionException {
|
||||||
|
final byte[] messageBody = message.getBody();
|
||||||
|
if (messageBody == null) {
|
||||||
|
LOG.warn("No message body found for message: {}", message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MessageProperties messageProperties = message.getMessageProperties();
|
||||||
|
final String className = StringUtils.trimAllWhitespace(messageProperties.getType());
|
||||||
|
if (StringUtils.isEmpty(className)) {
|
||||||
|
LOG.error("Could not determine class from message: {}", message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String json = new String(messageBody, StandardCharsets.UTF_8);
|
||||||
|
LOG.debug("json = {}", json);
|
||||||
|
return GSON.fromJson(json, Class.forName(className));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Could not deserialize message: " + message, e);
|
||||||
|
throw new MessageConversionException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.poc.alerting.amqp;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.springframework.amqp.core.Message;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class MessageDataTransferObject implements Serializable {
|
||||||
|
private String routingKey;
|
||||||
|
private String exchange;
|
||||||
|
private Message message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.poc.alerting.amqp;
|
||||||
|
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RabbitSender {
|
||||||
|
private final RabbitTemplate rabbitTemplate;
|
||||||
|
|
||||||
|
public void send(final AmqpMessage amqpMessage) {
|
||||||
|
try {
|
||||||
|
LOG.info("Sending message: {}", amqpMessage.toString());
|
||||||
|
rabbitTemplate.convertAndSend(amqpMessage.getExchange(), amqpMessage.getRoutingKey(), amqpMessage);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Error sending message, serializing to disk", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T sendAndReceive(final AmqpMessage amqpMessage) {
|
||||||
|
final AmqpResponse<T> amqpResponse = (AmqpResponse) rabbitTemplate.convertSendAndReceive(amqpMessage.getExchange(), amqpMessage.getRoutingKey(), amqpMessage);
|
||||||
|
final String errorMessage = "Something went wrong";
|
||||||
|
if (amqpResponse == null) {
|
||||||
|
LOG.error(errorMessage);
|
||||||
|
}
|
||||||
|
final ExceptionType exceptionType = amqpResponse.getExceptionType();
|
||||||
|
if (exceptionType != null) {
|
||||||
|
final String exceptionMessage = amqpResponse.getExceptionMessage();
|
||||||
|
final int statusCode = exceptionType.getStatus();
|
||||||
|
if (amqpResponse.getErrorCode() != null) {
|
||||||
|
final Integer errorCode = amqpResponse.getErrorCode();
|
||||||
|
final String errorDescription = amqpResponse.getErrorDescription();
|
||||||
|
LOG.error(errorMessage);
|
||||||
|
} else {
|
||||||
|
LOG.error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amqpResponse.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendWithoutBackup(final AmqpMessage amqpMessage) {
|
||||||
|
LOG.info("Sending message: {}", amqpMessage.toString());
|
||||||
|
rabbitTemplate.convertAndSend(amqpMessage.getExchange(), amqpMessage.getRoutingKey(), amqpMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.poc.alerting.amqp
|
||||||
|
|
||||||
|
sealed class AlertingAmqpMessage(
|
||||||
|
val alertId: String,
|
||||||
|
val accountId: String
|
||||||
|
): AmqpMessage {
|
||||||
|
override fun getRoutingKey(): String {
|
||||||
|
return "account_$accountId"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExchange(): String {
|
||||||
|
return "poc_alerting"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Add(val frequency: String, alertId: String, accountId: String): AlertingAmqpMessage(alertId, accountId)
|
||||||
|
class Update(val frequency: String, alertId: String, accountId: String): AlertingAmqpMessage(alertId, accountId)
|
||||||
|
class Delete(alertId: String, accountId: String): AlertingAmqpMessage(alertId, accountId)
|
||||||
|
class Pause(alertId: String, accountId: String): AlertingAmqpMessage(alertId, accountId)
|
||||||
|
class Resume(alertId: String, accountId: String): AlertingAmqpMessage(alertId, accountId)
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.poc.alerting.amqp
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.time.DateUtils.MILLIS_PER_MINUTE
|
||||||
|
import org.springframework.amqp.core.Binding
|
||||||
|
import org.springframework.amqp.core.BindingBuilder
|
||||||
|
import org.springframework.amqp.core.DirectExchange
|
||||||
|
import org.springframework.amqp.core.Exchange
|
||||||
|
import org.springframework.amqp.core.ExchangeBuilder
|
||||||
|
import org.springframework.amqp.core.FanoutExchange
|
||||||
|
import org.springframework.amqp.core.Queue
|
||||||
|
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory
|
||||||
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(basePackages = ["com.poc.alerting.amqp"])
|
||||||
|
open class AmqpConfiguration {
|
||||||
|
companion object {
|
||||||
|
const val AMQP_NAME = "poc_alerting"
|
||||||
|
const val NEW_ACCOUNT = "new_account"
|
||||||
|
const val FANOUT_NAME = "${AMQP_NAME}_fanout"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun connectionFactory(): ConnectionFactory {
|
||||||
|
return CachingConnectionFactory().apply {
|
||||||
|
virtualHost = "poc"
|
||||||
|
username = "poc_user"
|
||||||
|
setPassword("s!mpleP@ssw0rd")
|
||||||
|
setAddresses("localhost")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun newAccountQueue(): Queue {
|
||||||
|
return Queue(NEW_ACCOUNT, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun newAccountExchange(): FanoutExchange {
|
||||||
|
return FanoutExchange(NEW_ACCOUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun newAccountBinding(newAccountQueue: Queue, newAccountExchange: FanoutExchange): Binding {
|
||||||
|
return BindingBuilder.bind(newAccountQueue).to(newAccountExchange)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun pocAlertingExchange(): FanoutExchange {
|
||||||
|
return FanoutExchange(FANOUT_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun directExchange(): DirectExchange {
|
||||||
|
return ExchangeBuilder.directExchange(AMQP_NAME)
|
||||||
|
.durable(false)
|
||||||
|
.withArgument("alternate-exchange", NEW_ACCOUNT)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun exchangeBinding(directExchange: Exchange, pocAlertingExchange: FanoutExchange): Binding {
|
||||||
|
return BindingBuilder.bind(directExchange).to(pocAlertingExchange)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun rabbitTemplate(connectionFactory: ConnectionFactory, gsonMessageConverter: GsonMessageConverter): RabbitTemplate {
|
||||||
|
return RabbitTemplate(connectionFactory).apply {
|
||||||
|
setExchange(FANOUT_NAME)
|
||||||
|
messageConverter = gsonMessageConverter
|
||||||
|
setReplyTimeout(MILLIS_PER_MINUTE)
|
||||||
|
setMandatory(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"implementation"(project(":persistence"))
|
||||||
|
"implementation"(project(":amqp"))
|
||||||
|
|
||||||
|
implementation("org.slf4j:slf4j-api")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
|
implementation("org.springframework.amqp:spring-amqp")
|
||||||
|
implementation("org.springframework.amqp:spring-rabbit")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-quartz")
|
||||||
|
implementation("org.springframework:spring-jdbc")
|
||||||
|
implementation("org.projectlombok:lombok")
|
||||||
|
implementation("org.apache.commons:commons-lang3")
|
||||||
|
implementation("org.apache.httpcomponents:fluent-hc")
|
||||||
|
implementation("com.google.code.gson:gson")
|
||||||
|
|
||||||
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<BootJar>("bootJar") {
|
||||||
|
enabled = true
|
||||||
|
mainClass.set("com.poc.alerting.batch.BatchWorkerKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
springBoot {
|
||||||
|
mainClass.set("com.poc.alerting.batch.BatchWorkerKt")
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.poc.alerting.batch;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class AccountConsumerManager {
|
||||||
|
private volatile boolean shuttingDown = false;
|
||||||
|
@Getter private final Map<String, SimpleMessageListenerContainer> consumers = new ConcurrentHashMap<>();
|
||||||
|
private final Object lifecycleMonitor = new Object();
|
||||||
|
|
||||||
|
void registerAndStart(final String queueName, final SimpleMessageListenerContainer newListenerContainer) {
|
||||||
|
synchronized (this.lifecycleMonitor) {
|
||||||
|
if (shuttingDown) {
|
||||||
|
LOG.warn("Shutdown process is underway. Not registering consumer for queue {}", queueName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final SimpleMessageListenerContainer oldListenerContainer = consumers.get(queueName);
|
||||||
|
if (oldListenerContainer != null) {
|
||||||
|
oldListenerContainer.stop();
|
||||||
|
}
|
||||||
|
newListenerContainer.start();
|
||||||
|
consumers.put(queueName, newListenerContainer);
|
||||||
|
LOG.info("Registered a new consumer on queue {}", queueName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopConsumers() {
|
||||||
|
synchronized (this.lifecycleMonitor) {
|
||||||
|
shuttingDown = true;
|
||||||
|
LOG.info("Shutting down consumers on queues {}", consumers.keySet());
|
||||||
|
consumers.entrySet().parallelStream().forEach(entry -> {
|
||||||
|
LOG.info("Shutting down consumer on queue {}", entry.getKey());
|
||||||
|
try {
|
||||||
|
entry.getValue().stop();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Encountered error while stopping consumer on queue " + entry.getKey(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
LOG.info("Finished shutting down all consumers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.poc.alerting.batch;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import org.apache.http.client.fluent.Request;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||||
|
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
|
||||||
|
import org.springframework.amqp.support.converter.MessageConverter;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
|
||||||
|
ConnectionFactory connectionFactory;
|
||||||
|
MessageListenerAdapter accountWorkerListenerAdapter;
|
||||||
|
MessageConverter gsonMessageConverter;
|
||||||
|
ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
ConsumerCreator consumerCreator;
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ApplicationStartup.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(@NotNull final ApplicationReadyEvent event) {
|
||||||
|
LOG.info("Creating consumers for existing queues");
|
||||||
|
try {
|
||||||
|
|
||||||
|
final String rabbitMqUrl = String.format("http://%s:15672/api/exchanges/poc/alerting/bindings/source", "localhost");
|
||||||
|
|
||||||
|
//auth is kind of a kluge here. Apparently the HttpClient Fluent API doesn't support
|
||||||
|
//it except by explicitly setting the auth header.
|
||||||
|
final String json = Request.Get(rabbitMqUrl)
|
||||||
|
.connectTimeout(1000)
|
||||||
|
.socketTimeout(1000)
|
||||||
|
.addHeader("Authorization", "Basic " + getAuthToken())
|
||||||
|
.execute().returnContent().asString();
|
||||||
|
|
||||||
|
final JsonParser parser = new JsonParser();
|
||||||
|
final JsonArray array = parser.parse(json).getAsJsonArray();
|
||||||
|
|
||||||
|
StreamSupport.stream(array.spliterator(), false)
|
||||||
|
.map(jsonElement -> jsonElement.getAsJsonObject().get("destination").getAsString())
|
||||||
|
.forEach(queueName -> consumerCreator.createConsumer(queueName));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Error create consumers for existing queues", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAuthToken() {
|
||||||
|
final String basicPlaintext = "poc" + ":" + "s!mpleP@ssw0rd";
|
||||||
|
|
||||||
|
final Base64.Encoder encoder = Base64.getEncoder();
|
||||||
|
return encoder.encodeToString(basicPlaintext.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.poc.alerting.batch;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.springframework.amqp.support.ConditionalExceptionLogger;
|
||||||
|
|
||||||
|
public class ExclusiveConsumerExceptionLogger implements ConditionalExceptionLogger {
|
||||||
|
@Override
|
||||||
|
public void log(final Log logger, final String message, final Throwable t) {
|
||||||
|
//do not log exclusive consumer warnings
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.poc.alerting.batch;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
import org.springframework.amqp.core.BindingBuilder;
|
||||||
|
import org.springframework.amqp.core.DirectExchange;
|
||||||
|
import org.springframework.amqp.core.Queue;
|
||||||
|
import org.springframework.amqp.core.QueueBuilder;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.poc.alerting.amqp.AmqpMessage;
|
||||||
|
import com.poc.alerting.amqp.AmqpResponse;
|
||||||
|
import com.poc.alerting.amqp.ExceptionType;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static com.poc.alerting.amqp.AmqpConfiguration.AMQP_NAME;
|
||||||
|
import static com.poc.alerting.batch.WorkerConfiguration.NEW_CONSUMER;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QueueCreator {
|
||||||
|
private final RabbitAdmin rabbitAdmin;
|
||||||
|
private final RabbitTemplate rabbitTemplate;
|
||||||
|
private final DirectExchange directExchange;
|
||||||
|
|
||||||
|
public <T> AmqpResponse<T> createQueue(final AmqpMessage amqpMessage) throws IOException, ClassNotFoundException {
|
||||||
|
final String routingKey = amqpMessage.getRoutingKey();
|
||||||
|
LOG.info("Attempting to create new queue {}", routingKey);
|
||||||
|
setupQueueForAccount(routingKey);
|
||||||
|
final AmqpResponse response = (AmqpResponse) rabbitTemplate.convertSendAndReceive(AMQP_NAME, routingKey, amqpMessage);
|
||||||
|
if (response != null && ExceptionType.INVALID_ACCOUNT_EXCEPTION.equals(response.getExceptionType())) {
|
||||||
|
LOG.info("Invalid account, removing queue {}", routingKey);
|
||||||
|
CompletableFuture.runAsync(() -> rabbitAdmin.deleteQueue(routingKey, false, true));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupQueueForAccount(final String routingKey) {
|
||||||
|
final Properties properties = rabbitAdmin.getQueueProperties(routingKey);
|
||||||
|
if (properties == null) {
|
||||||
|
final Queue queue = QueueBuilder.nonDurable(routingKey)
|
||||||
|
.withArgument("x-expires", DateUtils.MILLIS_PER_DAY)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
rabbitAdmin.declareQueue(queue);
|
||||||
|
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(directExchange).with(routingKey));
|
||||||
|
sendCreateConsumerMessage(routingKey);
|
||||||
|
} else if ((Integer) properties.get(RabbitAdmin.QUEUE_CONSUMER_COUNT) < 1) {
|
||||||
|
LOG.info("{} queue already exists. Adding consumer.", routingKey);
|
||||||
|
sendCreateConsumerMessage(routingKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCreateConsumerMessage(final String queueName) {
|
||||||
|
rabbitTemplate.convertAndSend(NEW_CONSUMER, "", queueName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.poc.alerting.batch
|
||||||
|
|
||||||
|
import com.poc.alerting.amqp.AlertingAmqpMessage
|
||||||
|
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.batch.jobs.AlertQueryJob
|
||||||
|
import com.poc.alerting.batch.jobs.AlertQueryJob.Companion.ACCOUNT_ID
|
||||||
|
import com.poc.alerting.batch.jobs.AlertQueryJob.Companion.ALERT_ID
|
||||||
|
import com.poc.alerting.batch.jobs.AlertQueryJob.Companion.CRON
|
||||||
|
import com.poc.alerting.persistence.dto.Alert
|
||||||
|
import com.poc.alerting.persistence.repositories.AlertRepository
|
||||||
|
import org.quartz.CronScheduleBuilder
|
||||||
|
import org.quartz.JobBuilder
|
||||||
|
import org.quartz.JobDataMap
|
||||||
|
import org.quartz.JobDetail
|
||||||
|
import org.quartz.JobKey
|
||||||
|
import org.quartz.Scheduler
|
||||||
|
import org.quartz.Trigger
|
||||||
|
import org.quartz.TriggerBuilder
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
@Service
|
||||||
|
open class AccountWorker @Autowired constructor(
|
||||||
|
private val alertRepository: AlertRepository,
|
||||||
|
private val scheduler: Scheduler
|
||||||
|
){
|
||||||
|
fun processMessage(message: AlertingAmqpMessage) {
|
||||||
|
when (message) {
|
||||||
|
is Add -> createJob(message.alertId, message.accountId, message.frequency)
|
||||||
|
is Update -> updateJob(message.alertId, message.accountId, message.frequency)
|
||||||
|
is Delete -> deleteJob(message.alertId, message.accountId)
|
||||||
|
is Pause -> pauseJob(message.alertId, message.accountId)
|
||||||
|
is Resume -> resumeJob(message.alertId, message.accountId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createJob(alertId: String, accountId: String, cron: String): Alert {
|
||||||
|
val jobDetail = buildJob(alertId, accountId, cron)
|
||||||
|
val trigger = createTrigger(alertId, jobDetail, cron)
|
||||||
|
|
||||||
|
with (scheduler) {
|
||||||
|
scheduleJob(jobDetail, trigger)
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
return alertRepository.findByExtIdAndAccount_Id(alertId, accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateJob(alertId: String, accountId: String, cron: String): Alert {
|
||||||
|
scheduler.deleteJob(JobKey.jobKey(alertId, accountId))
|
||||||
|
return createJob(alertId, accountId, cron)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteJob(alertId: String, accountId: String): Alert {
|
||||||
|
val alert = alertRepository.findByExtIdAndAccount_Id(alertId, accountId)
|
||||||
|
scheduler.deleteJob(JobKey.jobKey(alertId, accountId))
|
||||||
|
alertRepository.delete(alert)
|
||||||
|
return alert
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pauseJob(alertId: String, accountId: String): Alert {
|
||||||
|
scheduler.pauseJob(JobKey.jobKey(alertId, accountId))
|
||||||
|
return alertRepository.findByExtIdAndAccount_Id(alertId, accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resumeJob(alertId: String, accountId: String): Alert {
|
||||||
|
scheduler.resumeJob(JobKey.jobKey(alertId, accountId))
|
||||||
|
return alertRepository.findByExtIdAndAccount_Id(alertId, accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildJob(alertId: String, accountId: String, cron: String): JobDetail {
|
||||||
|
val jobDataMap = JobDataMap()
|
||||||
|
jobDataMap[ALERT_ID] = alertId
|
||||||
|
jobDataMap[ACCOUNT_ID] = accountId
|
||||||
|
jobDataMap[CRON] = cron
|
||||||
|
return JobBuilder.newJob().ofType(AlertQueryJob::class.java)
|
||||||
|
.storeDurably()
|
||||||
|
.withIdentity(alertId, accountId)
|
||||||
|
.usingJobData(jobDataMap)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTrigger(alertId: String, jobDetail: JobDetail, cron: String): Trigger {
|
||||||
|
return TriggerBuilder.newTrigger()
|
||||||
|
.forJob(jobDetail)
|
||||||
|
.withIdentity("${alertId}_trigger")
|
||||||
|
.withSchedule(
|
||||||
|
CronScheduleBuilder.cronSchedule(cron)
|
||||||
|
.withMisfireHandlingInstructionFireAndProceed()
|
||||||
|
.inTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()))
|
||||||
|
)
|
||||||
|
.usingJobData("cron", cron)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.poc.alerting.batch
|
||||||
|
|
||||||
|
import org.quartz.Job
|
||||||
|
import org.quartz.SchedulerContext
|
||||||
|
import org.quartz.spi.TriggerFiredBundle
|
||||||
|
import org.springframework.beans.MutablePropertyValues
|
||||||
|
import org.springframework.beans.PropertyAccessorFactory
|
||||||
|
import org.springframework.context.ApplicationContext
|
||||||
|
import org.springframework.context.ApplicationContextAware
|
||||||
|
import org.springframework.scheduling.quartz.SpringBeanJobFactory
|
||||||
|
|
||||||
|
|
||||||
|
class AutowiringSpringBeanJobFactory : SpringBeanJobFactory(), ApplicationContextAware {
|
||||||
|
private var ctx: ApplicationContext? = null
|
||||||
|
private var schedulerContext: SchedulerContext? = null
|
||||||
|
override fun setApplicationContext(context: ApplicationContext) {
|
||||||
|
ctx = context
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createJobInstance(bundle: TriggerFiredBundle): Any {
|
||||||
|
val job: Job = ctx!!.getBean(bundle.jobDetail.jobClass)
|
||||||
|
val bw = PropertyAccessorFactory.forBeanPropertyAccess(job)
|
||||||
|
val pvs = MutablePropertyValues()
|
||||||
|
pvs.addPropertyValues(bundle.jobDetail.jobDataMap)
|
||||||
|
pvs.addPropertyValues(bundle.trigger.jobDataMap)
|
||||||
|
if (this.schedulerContext != null) {
|
||||||
|
pvs.addPropertyValues(this.schedulerContext)
|
||||||
|
}
|
||||||
|
bw.setPropertyValues(pvs, true)
|
||||||
|
return job
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setSchedulerContext(schedulerContext: SchedulerContext) {
|
||||||
|
this.schedulerContext = schedulerContext
|
||||||
|
super.setSchedulerContext(schedulerContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.poc.alerting.batch
|
||||||
|
|
||||||
|
import org.springframework.boot.Banner
|
||||||
|
import org.springframework.boot.WebApplicationType
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder
|
||||||
|
|
||||||
|
@SpringBootApplication(scanBasePackages = ["com.poc.alerting"])
|
||||||
|
open class BatchWorker
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
SpringApplicationBuilder().sources(BatchWorker::class.java)
|
||||||
|
.bannerMode(Banner.Mode.OFF)
|
||||||
|
.web(WebApplicationType.NONE)
|
||||||
|
.run(*args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.poc.alerting.batch
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.time.DateUtils
|
||||||
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory
|
||||||
|
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
|
||||||
|
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
open class ConsumerCreator @Autowired constructor(
|
||||||
|
private val connectionFactory: ConnectionFactory,
|
||||||
|
private val accountWorkerListenerAdapter: MessageListenerAdapter,
|
||||||
|
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||||
|
private val accountConsumerManager: AccountConsumerManager
|
||||||
|
){
|
||||||
|
fun createConsumer(queueName: String) {
|
||||||
|
val consumer = SimpleMessageListenerContainer(connectionFactory)
|
||||||
|
consumer.setExclusive(true)
|
||||||
|
consumer.setExclusiveConsumerExceptionLogger(ExclusiveConsumerExceptionLogger())
|
||||||
|
consumer.setQueueNames(queueName)
|
||||||
|
consumer.setMessageListener(accountWorkerListenerAdapter)
|
||||||
|
consumer.setIdleEventInterval(DateUtils.MILLIS_PER_HOUR)
|
||||||
|
consumer.setApplicationEventPublisher(applicationEventPublisher)
|
||||||
|
consumer.setDefaultRequeueRejected(false)
|
||||||
|
consumer.setAutoDeclare(false)
|
||||||
|
consumer.setShutdownTimeout(DateUtils.MILLIS_PER_SECOND * 10)
|
||||||
|
accountConsumerManager.registerAndStart(queueName, consumer)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.poc.alerting.batch
|
||||||
|
|
||||||
|
import com.poc.alerting.amqp.AmqpConfiguration.Companion.NEW_ACCOUNT
|
||||||
|
import com.poc.alerting.amqp.GsonMessageConverter
|
||||||
|
import org.springframework.amqp.core.Binding
|
||||||
|
import org.springframework.amqp.core.BindingBuilder
|
||||||
|
import org.springframework.amqp.core.FanoutExchange
|
||||||
|
import org.springframework.amqp.core.Queue
|
||||||
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitAdmin
|
||||||
|
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
|
||||||
|
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
open class WorkerConfiguration: AsyncConfigurer {
|
||||||
|
companion object {
|
||||||
|
const val NEW_CONSUMER = "new_consumer"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("asyncExecutor")
|
||||||
|
override fun getAsyncExecutor(): Executor {
|
||||||
|
return ThreadPoolTaskExecutor()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun rabbitAdmin(connectionFactory: ConnectionFactory): RabbitAdmin {
|
||||||
|
return RabbitAdmin(connectionFactory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun queueCreatorListenerAdapter(queueCreator: QueueCreator, gsonMessageConverter: GsonMessageConverter): MessageListenerAdapter {
|
||||||
|
return MessageListenerAdapter(queueCreator, "createQueue").apply {
|
||||||
|
setMessageConverter(gsonMessageConverter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun queueCreatorContainer(connectionFactory: ConnectionFactory, queueCreatorListenerAdapter: MessageListenerAdapter,
|
||||||
|
gsonMessageConverter: GsonMessageConverter): SimpleMessageListenerContainer {
|
||||||
|
return SimpleMessageListenerContainer(connectionFactory).apply {
|
||||||
|
setExclusive(true)
|
||||||
|
setExclusiveConsumerExceptionLogger(ExclusiveConsumerExceptionLogger())
|
||||||
|
setQueueNames(NEW_ACCOUNT)
|
||||||
|
setMessageListener(queueCreatorListenerAdapter)
|
||||||
|
setDefaultRequeueRejected(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun newConsumerExchange(): FanoutExchange {
|
||||||
|
return FanoutExchange(NEW_CONSUMER)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun newConsumerQueue(): Queue {
|
||||||
|
return Queue("${NEW_CONSUMER}_${InetAddress.getLocalHost().hostName}", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun newConsumerBinding(newConsumerQueue: Queue, newConsumerExchange: FanoutExchange): Binding {
|
||||||
|
return BindingBuilder.bind(newConsumerQueue).to(newConsumerExchange)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun consumerCreatorListenerAdapter(consumerCreator: ConsumerCreator, gsonMessageConverter: GsonMessageConverter): MessageListenerAdapter {
|
||||||
|
return MessageListenerAdapter(consumerCreator, "createConsumer").apply {
|
||||||
|
setMessageConverter(gsonMessageConverter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun consumerCreatorContainer(connectionFactory: ConnectionFactory, consumerCreatorListenerAdapter: MessageListenerAdapter,
|
||||||
|
newConsumerQueue: Queue): SimpleMessageListenerContainer {
|
||||||
|
return SimpleMessageListenerContainer(connectionFactory).apply {
|
||||||
|
setExclusive(true)
|
||||||
|
setExclusiveConsumerExceptionLogger(ExclusiveConsumerExceptionLogger())
|
||||||
|
setQueues(newConsumerQueue)
|
||||||
|
setMessageListener(consumerCreatorListenerAdapter)
|
||||||
|
setDefaultRequeueRejected(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun accountWorkerListenerAdapter(accountWorker: AccountWorker, gsonMessageConverter: GsonMessageConverter): MessageListenerAdapter {
|
||||||
|
return MessageListenerAdapter(accountWorker, "processMessage").apply {
|
||||||
|
setMessageConverter(gsonMessageConverter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.poc.alerting.batch.jobs
|
||||||
|
|
||||||
|
import com.poc.alerting.persistence.repositories.AlertRepository
|
||||||
|
import org.quartz.Job
|
||||||
|
import org.quartz.JobExecutionContext
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Component
|
||||||
|
open class AlertQueryJob @Autowired constructor(
|
||||||
|
private val alertRepository: AlertRepository
|
||||||
|
): Job {
|
||||||
|
companion object {
|
||||||
|
const val ALERT_ID = "alertId"
|
||||||
|
const val CRON = "cron"
|
||||||
|
const val ACCOUNT_ID = "accountId"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun execute(context: JobExecutionContext) {
|
||||||
|
val data = context.jobDetail.jobDataMap
|
||||||
|
val alertId = data.getString(ALERT_ID)
|
||||||
|
val cron = data.getString(CRON)
|
||||||
|
val accountId = data.getString(ACCOUNT_ID)
|
||||||
|
|
||||||
|
val alert = alertRepository.findByExtIdAndAccount_Id(alertId, accountId)
|
||||||
|
|
||||||
|
with(alert) {
|
||||||
|
val queryResult = type.query()
|
||||||
|
println("PERFORMING QUERY for $alertId-$accountId. Running with the following CRON expression: $cron")
|
||||||
|
if (queryResult >= threshold.toInt()) {
|
||||||
|
val currentTime = Date()
|
||||||
|
if (!isTriggered) {
|
||||||
|
isTriggered = true
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationSentTimestamp.let {
|
||||||
|
if (Duration.between(notificationSentTimestamp.toInstant(), currentTime.toInstant()).toSeconds() >= 15) {
|
||||||
|
println("Alert Triggered!!!!!!!!!!!!!")
|
||||||
|
notificationSentTimestamp = currentTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTriggerTimestamp = currentTime
|
||||||
|
alertRepository.save(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.poc.alerting.batch.jobs
|
||||||
|
|
||||||
|
import com.poc.alerting.batch.AutowiringSpringBeanJobFactory
|
||||||
|
import org.quartz.spi.JobFactory
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||||
|
import org.springframework.context.ApplicationContext
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.scheduling.quartz.SchedulerFactoryBean
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
open class ScheduleAlertQueryConfiguration {
|
||||||
|
@Bean
|
||||||
|
open fun jobFactory(applicationContext: ApplicationContext): JobFactory {
|
||||||
|
return AutowiringSpringBeanJobFactory().apply {
|
||||||
|
setApplicationContext(applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun schedulerFactory(applicationContext: ApplicationContext, dataSource: DataSource, jobFactory: JobFactory): SchedulerFactoryBean {
|
||||||
|
return SchedulerFactoryBean().apply {
|
||||||
|
setOverwriteExistingJobs(true)
|
||||||
|
isAutoStartup = true
|
||||||
|
setDataSource(dataSource)
|
||||||
|
setJobFactory(jobFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
server:
|
||||||
|
port: 0
|
||||||
|
servlet:
|
||||||
|
encoding:
|
||||||
|
charset: UTF-8
|
||||||
|
enabled: true
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: "batch"
|
||||||
|
datasource:
|
||||||
|
url: "jdbc:h2:tcp://localhost:9091/mem:alerting;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;"
|
||||||
|
username: "defaultUser"
|
||||||
|
password: "secret"
|
||||||
|
application:
|
||||||
|
name: BatchWorker
|
||||||
|
jackson:
|
||||||
|
time-zone: UTC
|
||||||
|
main:
|
||||||
|
allow-bean-definition-overriding: true
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
`java-library`
|
||||||
|
id("org.springframework.boot") version "2.5.0"
|
||||||
|
id("io.spring.dependency-management") version "1.0.11.RELEASE"
|
||||||
|
kotlin("jvm") version "1.5.10"
|
||||||
|
kotlin("plugin.spring") version "1.5.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = "com.poc.alerting"
|
||||||
|
version = "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
sourceCompatibility = "11"
|
||||||
|
targetCompatibility = "11"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile> {
|
||||||
|
kotlinOptions {
|
||||||
|
freeCompilerArgs = listOf("-Xjsr305=strict", "-Xextended-compiler-checks")
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||||
|
apply(plugin = "org.springframework.boot")
|
||||||
|
apply(plugin = "io.spring.dependency-management")
|
||||||
|
apply(plugin = "java")
|
||||||
|
apply(plugin = "java-library")
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
|
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||||
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test> {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy {
|
||||||
|
failOnVersionConflict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java.srcDir("src/main/kotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
springBoot {
|
||||||
|
mainClass.set("com.poc.alerting.persistence.Persistence")
|
||||||
|
}
|
||||||
@@ -0,0 +1,938 @@
|
|||||||
|
openapi: 3.0.1
|
||||||
|
info:
|
||||||
|
title: Alerting API
|
||||||
|
description: 'This is a POC API for a dynamic, reactive alerting system.'
|
||||||
|
version: 0.0.1
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:8080/poc/alerting/v1
|
||||||
|
tags:
|
||||||
|
- name: Alerts
|
||||||
|
- name: Recipients
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- read
|
||||||
|
- write
|
||||||
|
paths:
|
||||||
|
/accounts/{account_id}/alerts:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Alerts
|
||||||
|
description: Get the list of alerts for an account
|
||||||
|
summary: Get the list of alerts for an account
|
||||||
|
operationId: getListOfAlerts
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account to get alerts from.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: include_recipients
|
||||||
|
in: query
|
||||||
|
description: >
|
||||||
|
If set to true, then the list of all recipients belonging to each alert will be included in the response.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
example: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Alert'
|
||||||
|
example:
|
||||||
|
- id: "percentageOfDeliveryFailures"
|
||||||
|
name: "Percentage of Delivery Failures"
|
||||||
|
threshold: "90"
|
||||||
|
type: "PERCENTAGE_OF_DELIVERY_FAILURES"
|
||||||
|
frequency: "15m"
|
||||||
|
enabled: true
|
||||||
|
last_triggered: "2021-04-22T16:33:21.959Z"
|
||||||
|
notification_sent: "2021-04-22T16:33:21.959Z"
|
||||||
|
is_triggered: true,
|
||||||
|
reference_time_period: "1 Week"
|
||||||
|
created: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated_by: "someOtherUser"
|
||||||
|
recipients:
|
||||||
|
- id: "someUsersEmail"
|
||||||
|
recipient: "someUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-02-22T16:09:28.139Z"
|
||||||
|
updated: "2021-04-26T04:01:13.448Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
- id: "someOtherUsersEmail"
|
||||||
|
recipient: "someOtherUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-03-04T07:17:33.244Z"
|
||||||
|
updated: "2021-03-04T08:00:54.562Z"
|
||||||
|
updated_by: "someOtherUser"
|
||||||
|
- id: "failureThresholdAlert"
|
||||||
|
name: "Too Many Errors"
|
||||||
|
threshold: "1820"
|
||||||
|
type: "FAILURE_THRESHOLD_ALERT"
|
||||||
|
frequency: "5m"
|
||||||
|
enabled: false
|
||||||
|
last_triggered: "2021-04-22T16:33:21.959Z"
|
||||||
|
notification_sent: "2021-04-22T16:33:21.959Z"
|
||||||
|
is_triggered: true
|
||||||
|
reference_time_period: "1 Month"
|
||||||
|
created: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated: "2021-04-26T04:01:13.448Z"
|
||||||
|
updated_by: "someOtherUser"
|
||||||
|
recipients:
|
||||||
|
- id: "someUserHttpRecipient"
|
||||||
|
recipient: "https://some.test.callback.com/callback"
|
||||||
|
type: "HTTP"
|
||||||
|
username: "mikeRoweSoft"
|
||||||
|
password: "iSuPpOrTwInDoWs"
|
||||||
|
created: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alert
|
||||||
|
- read:alert
|
||||||
|
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Alerts
|
||||||
|
description: Add an alert to the specified account.
|
||||||
|
summary: Add an alert to the specified account
|
||||||
|
operationId: addAlert
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of an account to add an alert to.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
External ID used by users to refer to alert.
|
||||||
|
If an ID is specified, the ID must be account-unique.
|
||||||
|
If not provided, it will be automatically generated.
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: The name of this alert
|
||||||
|
default: "Default value differs by the alert type"
|
||||||
|
threshold:
|
||||||
|
type: string
|
||||||
|
description: A threshold must be given for a custom alert.
|
||||||
|
default: "Default value differs by the alert type"
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: The type of alert being added to the account
|
||||||
|
enum:
|
||||||
|
- PERCENTAGE_OF_DELIVERY_FAILURES
|
||||||
|
- FAILURE_THRESHOLD_ALERT
|
||||||
|
- HARD_CODED_ALERT_1
|
||||||
|
- HARD_CODED_ALERT_2
|
||||||
|
frequency:
|
||||||
|
type: string
|
||||||
|
description: The frequency of how often the alerting condition is checked.
|
||||||
|
default: "15m"
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
description: Boolean value denoting whether the alert is enabled or not.
|
||||||
|
default: true
|
||||||
|
reference_time_period:
|
||||||
|
type: string
|
||||||
|
description: Time period to run the alerting condition against (lags in time series data).
|
||||||
|
default: "1 Month"
|
||||||
|
example:
|
||||||
|
id: "testAlert1"
|
||||||
|
name: "Too Many Messages Are Being Sent"
|
||||||
|
threshold: "666"
|
||||||
|
type: "FAILURE_THRESHOLD_ALERT"
|
||||||
|
frequency: "5m"
|
||||||
|
enabled: false
|
||||||
|
reference_time_period: "1 Week"
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: Successfully created new alert
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Alert'
|
||||||
|
example:
|
||||||
|
id: "testAlert1"
|
||||||
|
name: "Too Many Messages Are Being Sent"
|
||||||
|
threshold: "666"
|
||||||
|
type: "FAILURE_THRESHOLD_ALERT"
|
||||||
|
frequency: "15m"
|
||||||
|
enabled: false
|
||||||
|
reference_time_period: "1 Week"
|
||||||
|
created: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
409:
|
||||||
|
description: An alert with the specified ID already exists on the given account
|
||||||
|
422:
|
||||||
|
description: >
|
||||||
|
* Invalid alert request
|
||||||
|
|
||||||
|
* The type was unspecified, or does not exist
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alert
|
||||||
|
- read:alert
|
||||||
|
|
||||||
|
/accounts/{account_id}/alerts/{alert_id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Alerts
|
||||||
|
description: Get an alert given an alert ID and account ID.
|
||||||
|
summary: Get an alert given an alert ID and account ID
|
||||||
|
operationId: getAlertById
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account to get alerts from.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: External ID used by users to refer to an alert.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someHardcodedAlert"
|
||||||
|
- name: include_recipients
|
||||||
|
in: query
|
||||||
|
description: >
|
||||||
|
If set to true, then the list of all recipients belonging to the alert will be included in the response.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
example: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Found alert
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Alert'
|
||||||
|
example:
|
||||||
|
id: "someHardcodedAlert"
|
||||||
|
name: "Percentage of Delivery Failures"
|
||||||
|
threshold: "90"
|
||||||
|
type: "PERCENTAGE_OF_DELIVERY_FAILURES"
|
||||||
|
frequency: "15m"
|
||||||
|
enabled: true
|
||||||
|
last_triggered: "2021-04-22T16:33:21.959Z"
|
||||||
|
notification_sent: "2021-04-22T16:33:21.959Z"
|
||||||
|
is_triggered: true
|
||||||
|
reference_time_period: "1 Week"
|
||||||
|
created: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated_by: "someOtherUser"
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFoundAlert'
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
patch:
|
||||||
|
tags:
|
||||||
|
- Alerts
|
||||||
|
description: >
|
||||||
|
Update the attributes of an existing alert. Note that if a user attempts to update the type of an alert, the value will be ignored.
|
||||||
|
summary: Update the attributes of an existing alert
|
||||||
|
operationId: updateAlert
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: >
|
||||||
|
ID of the account that the alert belongs to. This cannot be updated and will be ignored if placed in the payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: External ID used by users to refer to an alert. This cannot be updated and will be ignored if placed in the payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "testAlert1"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: A new name for the specified alert
|
||||||
|
threshold:
|
||||||
|
type: string
|
||||||
|
description: A new threshold for the alert
|
||||||
|
frequency:
|
||||||
|
type: string
|
||||||
|
description: The frequency of how often the alerting condition is checked
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
description: Boolean value denoting whether the alert is enabled or not
|
||||||
|
reference_time_period:
|
||||||
|
type: string
|
||||||
|
description: Time period to run the alerting condition against (lags in time series data)
|
||||||
|
example:
|
||||||
|
name: "Too Many Messages Are Being Sent"
|
||||||
|
threshold: "955"
|
||||||
|
frequency: "15m"
|
||||||
|
enabled: true
|
||||||
|
reference_time_period: "1 Month"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Alert'
|
||||||
|
example:
|
||||||
|
id: "testAlert1"
|
||||||
|
name: "Too Many Messages Are Being Sent"
|
||||||
|
threshold: "955"
|
||||||
|
type: "FAILURE_THRESHOLD_ALERT"
|
||||||
|
frequency: "15m"
|
||||||
|
enabled: true
|
||||||
|
last_triggered: "2021-04-22T16:33:21.959Z"
|
||||||
|
notification_sent: "2021-04-22T16:33:21.959Z"
|
||||||
|
is_triggered: false
|
||||||
|
reference_time_period: "1 Month"
|
||||||
|
created: "2021-04-22T16:33:21.959Z"
|
||||||
|
updated: "2021-05-19T12:45:09.127Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFoundAlert'
|
||||||
|
422:
|
||||||
|
description: >
|
||||||
|
* Invalid update request
|
||||||
|
|
||||||
|
* User was attempting to update alert type
|
||||||
|
|
||||||
|
* User attempted to update name value to an empty value
|
||||||
|
|
||||||
|
* User attempted to update threshold value to empty value
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- Alerts
|
||||||
|
description: >
|
||||||
|
Remove the specified alert from the account. When an alert is deleted, all recipients belonging to the alert are also removed.
|
||||||
|
summary: Remove the specified alert from the account
|
||||||
|
operationId: deleteAlert
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account that the alert exists on.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: External ID used by users to refer to an alert.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "testAlert1"
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: Successfully deleted alert
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFoundAlert'
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alert
|
||||||
|
- read:alert
|
||||||
|
|
||||||
|
/accounts/{account_id}/recipients:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Recipients
|
||||||
|
description: Get the list of all recipients on the specified account
|
||||||
|
summary: Get the list of all recipients on the specified account
|
||||||
|
operationId: getAllRecipients
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account to get recipients from
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successfully returned the list of recipients
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Recipient'
|
||||||
|
example:
|
||||||
|
- id: "someUsersEmail"
|
||||||
|
recipient: "someUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-02-22T16:09:28.139Z"
|
||||||
|
updated: "2021-04-26T04:01:13.448Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
- id: "someOtherUsersEmail"
|
||||||
|
recipient: "someOtherUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-03-04T07:17:33.244Z"
|
||||||
|
updated: "2021-03-04T08:00:54.562Z"
|
||||||
|
updated_by: "someOtherUser"
|
||||||
|
- id: "someUserHttpRecipient"
|
||||||
|
recipient: "https://some.test.callback.com/callback"
|
||||||
|
type: "HTTP"
|
||||||
|
username: "mikeRoweSoft"
|
||||||
|
password: "iSuPpOrTwInDoWs"
|
||||||
|
created: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
/accounts/{account_id}/alerts/{alert_id}/recipients:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Recipients
|
||||||
|
description: Get the list of recipients for an alert
|
||||||
|
summary: Get the list of recipients for an alert
|
||||||
|
operationId: getRecipientList
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account to get recipients from
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: The alert that the recipients belong to
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "percentageOfDeliveryFailures"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successfully returned list of recipients
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Recipient'
|
||||||
|
example:
|
||||||
|
- id: "someUsersEmail"
|
||||||
|
recipient: "someUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-02-22T16:09:28.139Z"
|
||||||
|
updated: "2021-04-26T04:01:13.448Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
- id: "someOtherUsersEmail"
|
||||||
|
recipient: "someOtherUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-03-04T07:17:33.244Z"
|
||||||
|
updated: "2021-03-04T08:00:54.562Z"
|
||||||
|
updated_by: "someOtherUser"
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFoundAlert'
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Recipients
|
||||||
|
description: Add one or more recipients to an alert. Note that no more than 25 recipients can be added to an alert.
|
||||||
|
summary: Add one or more recipients to an alert
|
||||||
|
operationId: addRecipient
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account that the recipient exists on.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: The alert that the recipient(s) will belong to
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "percentageOfDeliveryFailures"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
External ID used by users to refer to a recipient.
|
||||||
|
This must be unique among all recipient IDs given for the specified account. If this field is not provided, an ID is generated.
|
||||||
|
recipient:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Identifier that indicates who is receiving the notification.
|
||||||
|
|
||||||
|
* When the type is SMS, the recipient value must conform to the E.164 format recommendation
|
||||||
|
|
||||||
|
* When the type is EMAIL, the recipient value must conform to RFC-5322
|
||||||
|
|
||||||
|
* When the type is HTTP, as of RFC 3986, URIs should no longer support credentials.
|
||||||
|
As a result, all URIs must conform to “http[s]://host[:port]/path?querystring” format.
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- EMAIL
|
||||||
|
- SMS
|
||||||
|
- HTTP
|
||||||
|
description: The type of the recipient.
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Username for recipient HTTP webhook authentication. Only applicable to HTTP recipient types.
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Password for recipient HTTP webhook authentication. Only applicable to HTTP recipient types.
|
||||||
|
required:
|
||||||
|
- recipient
|
||||||
|
- type
|
||||||
|
example:
|
||||||
|
- id: "someUserHttpRecipient"
|
||||||
|
recipient: "https://some.test.callback.com/callback"
|
||||||
|
type: "HTTP"
|
||||||
|
username: "mikeRoweSoft"
|
||||||
|
password: "iSuPpOrTwInDoWs"
|
||||||
|
- id: "someUsersEmail"
|
||||||
|
recipient: "someUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: Successfully created new recipient(s)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Recipient'
|
||||||
|
example:
|
||||||
|
- id: "someUserHttpRecipient"
|
||||||
|
recipient: "https://some.test.callback.com/callback"
|
||||||
|
type: "HTTP"
|
||||||
|
username: "mikeRoweSoft"
|
||||||
|
password: "iSuPpOrTwInDoWs"
|
||||||
|
created: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
- id: "someUsersEmail"
|
||||||
|
recipient: "someUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-02-22T16:09:28.139Z"
|
||||||
|
updated: "2021-02-22T16:09:28.139Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/NotFoundAlert'
|
||||||
|
409:
|
||||||
|
description: A recipient with the specified ID already exists on the given account
|
||||||
|
422:
|
||||||
|
description: >
|
||||||
|
* Invalid recipient request
|
||||||
|
|
||||||
|
* User provided an unsupported type for one or more of the recipients
|
||||||
|
|
||||||
|
* User attempted to add more than 25 recipients to the alert
|
||||||
|
|
||||||
|
* Alert already has the maximum number of recipients
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
/accounts/{account_id}/alerts/{alert_id}/recipients/{recipient_id}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Recipients
|
||||||
|
description: Get a recipient given the account ID, alert ID, and recipient ID.
|
||||||
|
summary: Get a recipient given the account ID, alert ID, and recipient ID
|
||||||
|
operationId: getRecipient
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account to get recipients from
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: The alert that the recipient belongs to
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someHardCodedAlert"
|
||||||
|
- name: recipient_id
|
||||||
|
in: path
|
||||||
|
description: External ID used by users to refer to a recipient
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someUsersEmail"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Found Recipient
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Recipient'
|
||||||
|
example:
|
||||||
|
id: "someUsersEmail"
|
||||||
|
recipient: "someUser@somedomain.com"
|
||||||
|
type: "EMAIL"
|
||||||
|
created: "2021-02-22T16:09:28.139Z"
|
||||||
|
updated: "2021-04-26T04:01:13.448Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
description: >
|
||||||
|
* Recipient ID is unknown
|
||||||
|
|
||||||
|
* Alert ID is unknown
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
patch:
|
||||||
|
tags:
|
||||||
|
- Recipients
|
||||||
|
description: Update the attributes of a recipient.
|
||||||
|
summary: Update the attributes of a recipient
|
||||||
|
operationId: updateRecipient
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account that the recipient exists on.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: The alert that the recipient belongs to
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someHardCodedAlert"
|
||||||
|
- name: recipient_id
|
||||||
|
in: path
|
||||||
|
description: External ID used by users to refer to a recipient
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someUserHttpRecipient"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
recipient:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Identifier that indicates who is receiving the notification.
|
||||||
|
|
||||||
|
* When the type is SMS, the recipient value must conform to the E.164 format recommendation
|
||||||
|
|
||||||
|
* When the type is EMAIL, the recipient value must conform to RFC-5322
|
||||||
|
|
||||||
|
* When the type is HTTP, as of RFC 3986, URIs should no longer support credentials.
|
||||||
|
As a result, all URIs must conform to “http[s]://host[:port]/path?querystring” format.
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- EMAIL
|
||||||
|
- SMS
|
||||||
|
- HTTP
|
||||||
|
description: The type of the recipient
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Username for recipient HTTP webhook authentication. Only applicable to HTTP recipient types.
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Password for recipient HTTP webhook authentication. Only applicable to HTTP recipient types.
|
||||||
|
example:
|
||||||
|
password: "AppleHQLooksLikeAHalo.Suspicious!"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successfully updated recipient
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Recipient'
|
||||||
|
example:
|
||||||
|
id: "someUserHttpRecipient"
|
||||||
|
recipient: "https://some.test.callback.com/callback"
|
||||||
|
type: "HTTP"
|
||||||
|
username: "mikeRoweSoft"
|
||||||
|
password: "AppleHQLooksLikeAHalo.Suspicious!"
|
||||||
|
created: "2021-05-19T02:39:12.922Z"
|
||||||
|
updated: "2021-05-19T03:13:59.164Z"
|
||||||
|
updated_by: "someUser"
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
description: >
|
||||||
|
* Recipient ID is unknown
|
||||||
|
|
||||||
|
* Alert ID is unknown
|
||||||
|
422:
|
||||||
|
description: >
|
||||||
|
* Invalid update recipient request
|
||||||
|
|
||||||
|
* Invalid type provided
|
||||||
|
|
||||||
|
* Invalid or unsupported recipient URI given
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- Recipients
|
||||||
|
description: Delete a recipient.
|
||||||
|
summary: Delete a recipient
|
||||||
|
operationId: deleteRecipient
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
in: path
|
||||||
|
description: ID of the account that the recipient exists on
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1234567
|
||||||
|
- name: alert_id
|
||||||
|
in: path
|
||||||
|
description: The alert that the recipient(s) belong to
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someHardCodedAlert"
|
||||||
|
- name: recipient_id
|
||||||
|
in: path
|
||||||
|
description: >
|
||||||
|
External ID used by users to refer to a recipient.
|
||||||
|
Multiple recipients can be deleted by specify a comma-separated list of recipient ID's.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "someUserHttpRecipient,someUsersEmail"
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: Successfully removed recipient(s)
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
404:
|
||||||
|
description: >
|
||||||
|
* One or more recipient IDs are unknown
|
||||||
|
|
||||||
|
* Alert ID is unknown
|
||||||
|
security:
|
||||||
|
- alerting_auth:
|
||||||
|
- write:alerts
|
||||||
|
- read:alerts
|
||||||
|
|
||||||
|
components:
|
||||||
|
responses:
|
||||||
|
Forbidden:
|
||||||
|
description: >
|
||||||
|
* Unknown Account
|
||||||
|
|
||||||
|
* User is not allowed to represent the given account
|
||||||
|
|
||||||
|
BadRequest:
|
||||||
|
description: Request is invalid
|
||||||
|
|
||||||
|
NotFoundAlert:
|
||||||
|
description: Alert ID is unknown
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
Alert:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: Hardcoded or account-unique identifier for the alert
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: The name of this alert
|
||||||
|
threshold:
|
||||||
|
type: string
|
||||||
|
description: The threshold value associated with this alert
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
description: The type of alert. The type will be an enum value matching one of the hard coded alerts.
|
||||||
|
enum:
|
||||||
|
- PERCENTAGE_OF_DELIVERY_FAILURES
|
||||||
|
- FAILURE_THRESHOLD_ALERT
|
||||||
|
- HARD_CODED_ALERT_1
|
||||||
|
- HARD_CODED_ALERT_2
|
||||||
|
frequency:
|
||||||
|
type: string
|
||||||
|
description: The frequency of how often the alerting condition is checked
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
description: Boolean value denoting whether the alert is enabled or not
|
||||||
|
last_triggered:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Timestamp of when the alert was last triggered, conforming to RFC 3339 format. The time zone is always UTC
|
||||||
|
notification_sent:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Timestamp of when the last notification was sent, conforming to RFC 3339 format. The time zone is always UTC
|
||||||
|
is_triggered:
|
||||||
|
type: boolean
|
||||||
|
description: Boolean value denoting whether the alert is still currently being triggered
|
||||||
|
reference_time_period:
|
||||||
|
type: string
|
||||||
|
description: Time period to run the alerting condition against (lags in time series data)
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Timestamp of when the alert was created, conforming to RFC 3339 format.
|
||||||
|
The time zone is always UTC. For the create operation, the updated time will be the same as the created time.
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Timestamp of when the alert was last updated, conforming to RFC 3339 format. The time zone is always UTC
|
||||||
|
updated_by:
|
||||||
|
type: string
|
||||||
|
description: The last user to update the alert
|
||||||
|
recipients:
|
||||||
|
type: array
|
||||||
|
description: >
|
||||||
|
The list of full recipient objects is only returned when the include_recipients parameter is set to true
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Recipient'
|
||||||
|
|
||||||
|
Recipient:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: Account-unique identifier for the recipient
|
||||||
|
recipient:
|
||||||
|
type: string
|
||||||
|
description: Identifier that indicates who is receiving the notification
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- EMAIL
|
||||||
|
- SMS
|
||||||
|
- HTTP
|
||||||
|
description: Type of recipient.
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Username for recipient HTTP webhook authentication. Only applicable to HTTP recipient types.
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Password for recipient HTTP webhook authentication. Only applicable to HTTP recipient types.
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Timestamp of when the recipient was created, conforming to RFC 3339 format. The time zone is always UTC.
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: >
|
||||||
|
Timestamp of when the recipient was last updated, conforming to RFC 3339 format. The time zone is always UTC.
|
||||||
|
For the create operation, the updated time will be the same as the created time.
|
||||||
|
updated_by:
|
||||||
|
type: string
|
||||||
|
description: The last user to update the recipient.
|
||||||
|
|
||||||
|
securitySchemes:
|
||||||
|
alerting_auth:
|
||||||
|
type: oauth2
|
||||||
|
flows:
|
||||||
|
authorizationCode:
|
||||||
|
authorizationUrl: https://login.alexjclarke.com/oauth/authorize
|
||||||
|
tokenUrl: https://login.alexjclarke.com/oauth/token
|
||||||
|
scopes:
|
||||||
|
read:alerts: read your alerts
|
||||||
|
write:alerts: modify alerts in your account
|
||||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 628 B |
Vendored
+75
@@ -0,0 +1,75 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<title>Swagger UI: OAuth2 Redirect</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
function run () {
|
||||||
|
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||||
|
var sentState = oauth2.state;
|
||||||
|
var redirectUrl = oauth2.redirectUrl;
|
||||||
|
var isValid, qp, arr;
|
||||||
|
|
||||||
|
if (/code|token|error/.test(window.location.hash)) {
|
||||||
|
qp = window.location.hash.substring(1);
|
||||||
|
} else {
|
||||||
|
qp = location.search.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr = qp.split("&");
|
||||||
|
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||||
|
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||||
|
function (key, value) {
|
||||||
|
return key === "" ? value : decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
) : {};
|
||||||
|
|
||||||
|
isValid = qp.state === sentState;
|
||||||
|
|
||||||
|
if ((
|
||||||
|
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||||
|
) && !oauth2.auth.code) {
|
||||||
|
if (!isValid) {
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "warning",
|
||||||
|
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qp.code) {
|
||||||
|
delete oauth2.state;
|
||||||
|
oauth2.auth.code = qp.code;
|
||||||
|
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||||
|
} else {
|
||||||
|
let oauthErrorMsg;
|
||||||
|
if (qp.error) {
|
||||||
|
oauthErrorMsg = "["+qp.error+"]: " +
|
||||||
|
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||||
|
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "error",
|
||||||
|
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function () {
|
||||||
|
run();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Vendored
+3
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+3
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+4
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+3
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
|
|||||||
|
<!-- HTML for static distribution bundle build -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Swagger UI</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="dist/swagger-ui.css" />
|
||||||
|
<link rel="icon" type="image/png" href="dist/favicon-32x32.png" sizes="32x32" />
|
||||||
|
<link rel="icon" type="image/png" href="dist/favicon-16x16.png" sizes="16x16" />
|
||||||
|
<style>
|
||||||
|
html
|
||||||
|
{
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after
|
||||||
|
{
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
{
|
||||||
|
margin:0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="dist/swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||||
|
<script src="dist/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
// Begin Swagger UI call region
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "AlertingApi.yaml",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout"
|
||||||
|
});
|
||||||
|
// End Swagger UI call region
|
||||||
|
|
||||||
|
window.ui = ui;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Vendored
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+89
@@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
config.stopBubbling = true
|
||||||
|
lombok.accessors.chain = true
|
||||||
|
lombok.accessors.fluent = false
|
||||||
|
lombok.addLombokGeneratedAnnotation = true
|
||||||
|
lombok.data.flagUsage = error
|
||||||
|
lombok.LOG.fieldName = LOG
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("org.flywaydb.flyway") version "7.9.1"
|
||||||
|
kotlin("plugin.jpa") version "1.5.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"implementation"("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
|
implementation("org.flywaydb:flyway-core")
|
||||||
|
implementation("org.apache.tomcat:tomcat-jdbc")
|
||||||
|
implementation("com.h2database:h2:1.4.200")
|
||||||
|
}
|
||||||
|
|
||||||
|
flyway {
|
||||||
|
url = "jdbc:h2:mem:alerting"
|
||||||
|
user = "defaultUser"
|
||||||
|
password = "secret"
|
||||||
|
locations = arrayOf("classpath:resources/db/migration")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<BootJar>("bootJar") {
|
||||||
|
enabled = true
|
||||||
|
mainClass.set("com.poc.alerting.persistence.Persistence")
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.poc.alerting.persistence
|
||||||
|
|
||||||
|
import org.h2.tools.Server
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.context.annotation.Profile
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaRepositories("com.poc.alerting.persistence.repositories")
|
||||||
|
@EntityScan("com.poc.alerting.persistence.dto")
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
open class H2Config {
|
||||||
|
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||||
|
@Profile("persistence")
|
||||||
|
open fun inMemoryH2DatabaseServer(): Server {
|
||||||
|
return Server.createTcpServer(
|
||||||
|
"-tcp", "-tcpAllowOthers", "-tcpPort", "9091"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.poc.alerting.persistence
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.boot.runApplication
|
||||||
|
|
||||||
|
|
||||||
|
@SpringBootApplication(scanBasePackages = ["com.poc.alerting.persistence"])
|
||||||
|
open class Persistence
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
runApplication<Persistence>(*args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.poc.alerting.persistence.dto
|
||||||
|
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.GeneratedValue
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
import javax.validation.constraints.Size
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Account(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 255)
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 255)
|
||||||
|
val extId: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.poc.alerting.persistence.dto
|
||||||
|
|
||||||
|
import org.hibernate.annotations.CreationTimestamp
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp
|
||||||
|
import java.util.Date
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.EnumType
|
||||||
|
import javax.persistence.Enumerated
|
||||||
|
import javax.persistence.GeneratedValue
|
||||||
|
import javax.persistence.GenerationType
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.ManyToOne
|
||||||
|
import javax.persistence.Temporal
|
||||||
|
import javax.persistence.TemporalType
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
import javax.validation.constraints.NotNull
|
||||||
|
import javax.validation.constraints.Size
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Alert(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 255)
|
||||||
|
val extId: String,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
var threshold: String,
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
val type: AlertTypeEnum,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
var frequency: String = "",
|
||||||
|
|
||||||
|
var enabled: String = "",
|
||||||
|
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
var lastTriggerTimestamp: Date,
|
||||||
|
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
var notificationSentTimestamp: Date,
|
||||||
|
|
||||||
|
var isTriggered: Boolean,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
var referenceTimePeriod: String,
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
val created: Date,
|
||||||
|
|
||||||
|
@UpdateTimestamp
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
var updated: Date,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
var updatedBy: String,
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
var account: Account
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.poc.alerting.persistence.dto
|
||||||
|
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
enum class AlertTypeEnum(val query: () -> Int) {
|
||||||
|
HARDCODED_ALERT_1({ Random.nextInt(0,1000) }),
|
||||||
|
HARDCODED_ALERT_2({ Random.nextInt(0,1000) })
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.poc.alerting.persistence.dto
|
||||||
|
|
||||||
|
import org.hibernate.annotations.CreationTimestamp
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp
|
||||||
|
import java.util.Date
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.EnumType
|
||||||
|
import javax.persistence.Enumerated
|
||||||
|
import javax.persistence.GeneratedValue
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.ManyToOne
|
||||||
|
import javax.persistence.Temporal
|
||||||
|
import javax.persistence.TemporalType
|
||||||
|
import javax.validation.constraints.NotBlank
|
||||||
|
import javax.validation.constraints.NotNull
|
||||||
|
import javax.validation.constraints.Size
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Recipient(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 255)
|
||||||
|
val extId: String,
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 255)
|
||||||
|
val recipient: String,
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
val type: RecipientTypeEnum,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
val username: String,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
val password: String,
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
val created: Date,
|
||||||
|
|
||||||
|
@UpdateTimestamp
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
val updated: Date,
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
val updatedBy: String,
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
val alert: Alert,
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
val account: Account
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.poc.alerting.persistence.dto
|
||||||
|
|
||||||
|
enum class RecipientTypeEnum {
|
||||||
|
EMAIL,
|
||||||
|
SMS,
|
||||||
|
HTTP
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package com.poc.alerting.persistence.repositories
|
||||||
|
|
||||||
|
import com.poc.alerting.persistence.dto.Account
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface AccountRepository : JpaRepository<Account, Long> {
|
||||||
|
fun findByExtId(accountExtId: String): Account
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.poc.alerting.persistence.repositories
|
||||||
|
|
||||||
|
import com.poc.alerting.persistence.dto.Alert
|
||||||
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import org.springframework.data.repository.PagingAndSortingRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface AlertRepository : CrudRepository<Alert, Long>, PagingAndSortingRepository<Alert, Long> {
|
||||||
|
fun findByExtIdAndAccount_ExtId(alertId: String, accountExtId: String): Alert
|
||||||
|
|
||||||
|
fun findAllByAccount_ExtId(accountExtId: String): List<Alert>
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package com.poc.alerting.persistence.repositories
|
||||||
|
|
||||||
|
import com.poc.alerting.persistence.dto.Recipient
|
||||||
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface RecipientRepository : CrudRepository<Recipient, Long> {
|
||||||
|
fun findByExtIdAndAlert_ExtIdAndAccount_ExtId(recipientId: String, alertId: String, accountExtId: String): Recipient
|
||||||
|
|
||||||
|
fun findAllByAccount_ExtId(accountExtId: String): List<Recipient>
|
||||||
|
|
||||||
|
fun findAllByAlert_ExtIdAndAccount_ExtId(alertId: String, accountExtId: String): List<Recipient>
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: "persistence"
|
||||||
|
datasource:
|
||||||
|
url: "jdbc:h2:mem:alerting;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;"
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
username: defaultUser
|
||||||
|
password: secret
|
||||||
|
jpa:
|
||||||
|
database-platform: org.hibernate.dialect.H2Dialect
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
naming:
|
||||||
|
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
|
||||||
|
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
path: /h2-console
|
||||||
|
server:
|
||||||
|
port: 8081
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
DROP TABLE qrtz_calendars IF EXISTS;
|
||||||
|
DROP TABLE qrtz_cron_triggers IF EXISTS;
|
||||||
|
DROP TABLE qrtz_fired_triggeres IF EXISTS;
|
||||||
|
DROP TABLE qrtz_paused_trigger_grps IF EXISTS;
|
||||||
|
DROP TABLE qrtz_scheduler_state IF EXISTS;
|
||||||
|
DROP TABLE qrtz_locks IF EXISTS;
|
||||||
|
DROP TABLE qrtz_job_details IF EXISTS;
|
||||||
|
DROP TABLE qrtz_simple_triggers IF EXISTS;
|
||||||
|
DROP TABLE qrtz_simprop_triggers IF EXISTS;
|
||||||
|
DROP TABLE qrtz_blob_triggers IF EXISTS;
|
||||||
|
DROP TABLE qrtz_triggers IF EXISTS;
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
CREATE TABLE QRTZ_CALENDARS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
CALENDAR_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
CALENDAR IMAGE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_CRON_TRIGGERS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
CRON_EXPRESSION VARCHAR (120) NOT NULL ,
|
||||||
|
TIME_ZONE_ID VARCHAR (80)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_FIRED_TRIGGERS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
ENTRY_ID VARCHAR (95) NOT NULL ,
|
||||||
|
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
INSTANCE_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
FIRED_TIME BIGINT NOT NULL ,
|
||||||
|
SCHED_TIME BIGINT NOT NULL ,
|
||||||
|
PRIORITY INTEGER NOT NULL ,
|
||||||
|
STATE VARCHAR (16) NOT NULL,
|
||||||
|
JOB_NAME VARCHAR (200) NULL ,
|
||||||
|
JOB_GROUP VARCHAR (200) NULL ,
|
||||||
|
IS_NONCONCURRENT BOOLEAN NULL ,
|
||||||
|
REQUESTS_RECOVERY BOOLEAN NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_GROUP VARCHAR (200) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_SCHEDULER_STATE (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
INSTANCE_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
LAST_CHECKIN_TIME BIGINT NOT NULL ,
|
||||||
|
CHECKIN_INTERVAL BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_LOCKS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
LOCK_NAME VARCHAR (40) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_JOB_DETAILS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
JOB_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
JOB_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
DESCRIPTION VARCHAR (250) NULL ,
|
||||||
|
JOB_CLASS_NAME VARCHAR (250) NOT NULL ,
|
||||||
|
IS_DURABLE BOOLEAN NOT NULL ,
|
||||||
|
IS_NONCONCURRENT BOOLEAN NOT NULL ,
|
||||||
|
IS_UPDATE_DATA BOOLEAN NOT NULL ,
|
||||||
|
REQUESTS_RECOVERY BOOLEAN NOT NULL ,
|
||||||
|
JOB_DATA IMAGE NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
REPEAT_COUNT BIGINT NOT NULL ,
|
||||||
|
REPEAT_INTERVAL BIGINT NOT NULL ,
|
||||||
|
TIMES_TRIGGERED BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_SIMPROP_TRIGGERS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
|
STR_PROP_1 VARCHAR(512) NULL,
|
||||||
|
STR_PROP_2 VARCHAR(512) NULL,
|
||||||
|
STR_PROP_3 VARCHAR(512) NULL,
|
||||||
|
INT_PROP_1 INTEGER NULL,
|
||||||
|
INT_PROP_2 INTEGER NULL,
|
||||||
|
LONG_PROP_1 BIGINT NULL,
|
||||||
|
LONG_PROP_2 BIGINT NULL,
|
||||||
|
DEC_PROP_1 NUMERIC(13,4) NULL,
|
||||||
|
DEC_PROP_2 NUMERIC(13,4) NULL,
|
||||||
|
BOOL_PROP_1 BOOLEAN NULL,
|
||||||
|
BOOL_PROP_2 BOOLEAN NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_BLOB_TRIGGERS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
BLOB_DATA IMAGE NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_TRIGGERS (
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
TRIGGER_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
JOB_NAME VARCHAR (200) NOT NULL ,
|
||||||
|
JOB_GROUP VARCHAR (200) NOT NULL ,
|
||||||
|
DESCRIPTION VARCHAR (250) NULL ,
|
||||||
|
NEXT_FIRE_TIME BIGINT NULL ,
|
||||||
|
PREV_FIRE_TIME BIGINT NULL ,
|
||||||
|
PRIORITY INTEGER NULL ,
|
||||||
|
TRIGGER_STATE VARCHAR (16) NOT NULL ,
|
||||||
|
TRIGGER_TYPE VARCHAR (8) NOT NULL ,
|
||||||
|
START_TIME BIGINT NOT NULL ,
|
||||||
|
END_TIME BIGINT NULL ,
|
||||||
|
CALENDAR_NAME VARCHAR (200) NULL ,
|
||||||
|
MISFIRE_INSTR SMALLINT NULL ,
|
||||||
|
JOB_DATA IMAGE NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_CALENDARS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
CALENDAR_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_CRON_TRIGGERS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_FIRED_TRIGGERS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
ENTRY_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_SCHEDULER_STATE ADD
|
||||||
|
CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
INSTANCE_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_LOCKS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
LOCK_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_JOB_DETAILS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
JOB_NAME,
|
||||||
|
JOB_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_TRIGGERS ADD
|
||||||
|
CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_CRON_TRIGGERS ADD
|
||||||
|
CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
) REFERENCES QRTZ_TRIGGERS (
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD
|
||||||
|
CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
) REFERENCES QRTZ_TRIGGERS (
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD
|
||||||
|
CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
) REFERENCES QRTZ_TRIGGERS (
|
||||||
|
SCHED_NAME,
|
||||||
|
TRIGGER_NAME,
|
||||||
|
TRIGGER_GROUP
|
||||||
|
) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE QRTZ_TRIGGERS ADD
|
||||||
|
CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY
|
||||||
|
(
|
||||||
|
SCHED_NAME,
|
||||||
|
JOB_NAME,
|
||||||
|
JOB_GROUP
|
||||||
|
) REFERENCES QRTZ_JOB_DETAILS (
|
||||||
|
SCHED_NAME,
|
||||||
|
JOB_NAME,
|
||||||
|
JOB_GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
CREATE TABLE `account` (
|
||||||
|
`id` long PRIMARY KEY,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`ext_id` varchar(255) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `account`
|
||||||
|
VALUES (1, 'Test Account 1', '1111'),
|
||||||
|
(2, 'Test Account 2', '2222');
|
||||||
|
|
||||||
|
CREATE TABLE `alert` (
|
||||||
|
`id` long PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`ext_id` varchar(255) UNIQUE NOT NULL,
|
||||||
|
`name` varchar(255),
|
||||||
|
`threshold` varchar(255),
|
||||||
|
`type` ENUM ('HARDCODED_ALERT_1', 'HARDCODED_ALERT_2'),
|
||||||
|
`frequency` varchar(255),
|
||||||
|
`enabled` boolean default false,
|
||||||
|
`last_trigger_timestamp` timestamp,
|
||||||
|
`notification_sent_timestamp` timestamp,
|
||||||
|
`is_triggered` boolean default false,
|
||||||
|
`reference_time_period` varchar(255),
|
||||||
|
`created` timestamp,
|
||||||
|
`updated` timestamp,
|
||||||
|
`updated_by` varchar(255),
|
||||||
|
`account_id` long NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `alert`
|
||||||
|
VALUES (1, '1111-alert-1', 'Some Test Alert for Account 1111', '90', 'HARDCODED_ALERT_1', '15m', true, '2021-04-22 16:33:21.959', '2021-04-22 16:33:21.959', false, '1 Month', '2021-04-22 16:33:21.959', '2021-04-22 16:33:21.959', 'someUser1', 1),
|
||||||
|
(2, '1111-alert-2', 'Some Other Test Alert for Account 1111', '666', 'HARDCODED_ALERT_2', '5m', true, null, null, false, '1 Week', '2021-04-22 16:33:21.959', '2021-04-26 04:01:13.448', 'someUser1', 1),
|
||||||
|
(3, '2222-alert-1', 'Some Test Alert for Account 2222', '90', 'HARDCODED_ALERT_1', '15m', true, '2021-04-22 16:33:21.959', '2021-04-22 16:33:21.959', false, '1 Month', '2021-04-22 16:33:21.959', '2021-04-22 16:33:21.959', 'someOtherUser2', 2),
|
||||||
|
(4, '2222-alert-2', 'Some Other Test Alert for Account 2222', '666', 'HARDCODED_ALERT_2', '5m', true, null, null, false, '1 Week', '2021-04-22 16:33:21.959', '2021-04-26 04:01:13.448', 'someOtherUser2', 2);
|
||||||
|
|
||||||
|
ALTER TABLE `alert` ADD FOREIGN KEY (`account_id`) REFERENCES `account` (`id`);
|
||||||
|
|
||||||
|
CREATE TABLE `recipient` (
|
||||||
|
`id` long PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`ext_id` varchar(255) UNIQUE NOT NULL,
|
||||||
|
`recipient` varchar(255) NOT NULL,
|
||||||
|
`type` ENUM ('EMAIL', 'SMS', 'HTTP'),
|
||||||
|
`username` varchar(255),
|
||||||
|
`password` varchar(255),
|
||||||
|
`created` timestamp NOT NULL,
|
||||||
|
`updated` timestamp NOT NULL,
|
||||||
|
`updated_by` varchar(255) NOT NULL,
|
||||||
|
`alert_id` long,
|
||||||
|
`account_id` long
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `recipient`
|
||||||
|
VALUES (1, 'someUsersEmail-account-1111', 'someUser1@somedomain.com', 'EMAIL', null, null, '2021-02-22 16:09:28.139', '2021-02-22 16:09:28.139', 'someUser1', 1, 1),
|
||||||
|
(2, 'someOtherUsersEmail-account-1111', 'someOtherUser1@somedomain.com', 'EMAIL', null, null, '2021-03-04 07:17:33.244', '2021-03-04 08:00:54.562', 'someOtherUser1', 1, 1),
|
||||||
|
(3, 'someUserHttpRecipient-account-1111', 'https://some.test.callback.com/callback1', 'HTTP', 'mikeRoweSoft', 'iSuPpOrTwInDoWs', '2021-05-19 02:39:12.922', '2021-05-19 02:39:12.922', 'someUser1', 2, 1),
|
||||||
|
(4, 'someUserHttpRecipient-account-2222', 'https://some.test.callback.com/callback2', 'HTTP', 'teriDactyl', 'L1f3F1nd$AWay', '2021-05-19 02:39:12.922', '2021-05-19 02:39:12.922', 'someUser2', 2, 2),
|
||||||
|
(5, 'someUsersEmail-account-2222', 'someUser2@somedomain.com', 'EMAIL', null, null, '2021-02-22 16:09:28.139', '2021-02-22 16:09:28.139', 'someUser2', 1, 2),
|
||||||
|
(6, 'someOtherUsersSms-account-2222', '13035552222', 'SMS', null, null, '2021-02-22 16:09:28.139', '2021-02-22 16:09:28.139', 'someOtherUser2', 1, 2);
|
||||||
|
|
||||||
|
ALTER TABLE `recipient` ADD FOREIGN KEY (`alert_id`) REFERENCES `alert` (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `recipient` ADD FOREIGN KEY (`account_id`) REFERENCES `account` (`id`);
|
||||||
|
|
||||||
|
CREATE SEQUENCE `hibernate_sequence` START WITH 5 INCREMENT BY 1;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = "alerting-poc"
|
||||||
|
include(":persistence", ":amqp", ":api", ":batch")
|
||||||
Reference in New Issue
Block a user