Skip to content

Commit

Permalink
feature: add gotify notification (#260)
Browse files Browse the repository at this point in the history
  • Loading branch information
MDeLuise authored Aug 8, 2024
1 parent f9ec886 commit 1e37f51
Show file tree
Hide file tree
Showing 20 changed files with 520 additions and 41 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,14 @@ If you're interested in contributing transactions to enhance the app, you can ge
|----------|----------|-------------|
| English | app_en.arb | 100% |
| Italian | app_it.arb | 100% |
| German | app_de.arb | 100% |
| Dutch Flemish | app_nl.arb | 100% |
| Russian | app_ru.arb | 100% |
| French | app_fr.arb | 99% |
| Danish | app_da.arb | 99% |
| Portuguese | app_pt.arb | 98% |
| Ukrainian | app_uk.arb | 96% |
| Spanish Castilian | app_es.arb | 96% |
| Russian | app_ru.arb | 98% |
| German | app_de.arb | 98% |
| French | app_fr.arb | 98% |
| Dutch Flemish | app_nl.arb | 98% |
| Danish | app_da.arb | 98% |
| Portuguese | app_pt.arb | 97% |
| Ukrainian | app_uk.arb | 95% |
| Spanish Castilian | app_es.arb | 95% |

### Bug Report, Feature Request and Question
You can submit any of this in the [issues](https://github.com/MDeLuise/plant-it/issues/new/choose) section of the repository. Chose the right template and then fill the required info.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

import com.github.mdeluise.plantit.common.MessageResponse;
import com.github.mdeluise.plantit.notification.dispatcher.config.AbstractNotificationDispatcherConfig;
import com.github.mdeluise.plantit.notification.gotify.GotifyNotificationDispatcherConfig;
import com.github.mdeluise.plantit.notification.gotify.GotifyNotificationDispatcherConfigDTO;
import com.github.mdeluise.plantit.notification.gotify.GotifyNotificationDispatcherDTOConverter;
import com.github.mdeluise.plantit.notification.ntfy.NtfyNotificationDispatcherConfig;
import com.github.mdeluise.plantit.notification.ntfy.NtfyNotificationDispatcherConfigDTO;
import com.github.mdeluise.plantit.notification.ntfy.NtfyNotificationDispatcherDTOConverter;
Expand All @@ -24,13 +27,16 @@
public class NotificationDispatcherController {
private final NotificationDispatcherService notificationDispatcherService;
private final NtfyNotificationDispatcherDTOConverter ntfyNotificationDispatcherDTOConverter;
private final GotifyNotificationDispatcherDTOConverter gotifyNotificationDispatcherDTOConverter;


@Autowired
public NotificationDispatcherController(NotificationDispatcherService notificationDispatcherService,
NtfyNotificationDispatcherDTOConverter ntfyNotificationDispatcherDTOConverter) {
NtfyNotificationDispatcherDTOConverter ntfyNotificationDispatcherDTOConverter,
GotifyNotificationDispatcherDTOConverter gotifyNotificationDispatcherDTOConverter) {
this.notificationDispatcherService = notificationDispatcherService;
this.ntfyNotificationDispatcherDTOConverter = ntfyNotificationDispatcherDTOConverter;
this.gotifyNotificationDispatcherDTOConverter = gotifyNotificationDispatcherDTOConverter;
}


Expand All @@ -50,7 +56,7 @@ public ResponseEntity<MessageResponse> setUserEnabled(@RequestBody Set<Notificat


@GetMapping("/config/ntfy")
public NtfyNotificationDispatcherConfigDTO getConfig() {
public NtfyNotificationDispatcherConfigDTO getNtfyConfig() {
final NtfyNotificationDispatcherConfig result =
(NtfyNotificationDispatcherConfig) notificationDispatcherService.getUserConfig(NotificationDispatcherName.NTFY)
.orElse(new NtfyNotificationDispatcherConfig());
Expand All @@ -59,9 +65,26 @@ public NtfyNotificationDispatcherConfigDTO getConfig() {


@PostMapping("/config/ntfy")
public ResponseEntity<MessageResponse> setConfig(@RequestBody NtfyNotificationDispatcherConfigDTO config) {
public ResponseEntity<MessageResponse> setNtfyConfig(@RequestBody NtfyNotificationDispatcherConfigDTO config) {
final AbstractNotificationDispatcherConfig toSave = ntfyNotificationDispatcherDTOConverter.convertFromDTO(config);
notificationDispatcherService.setUserConfig(NotificationDispatcherName.NTFY, toSave);
return ResponseEntity.ok(new MessageResponse("Success"));
}


@GetMapping("/config/gotify")
public GotifyNotificationDispatcherConfigDTO getGotifyConfig() {
final GotifyNotificationDispatcherConfig result =
(GotifyNotificationDispatcherConfig) notificationDispatcherService.getUserConfig(NotificationDispatcherName.GOTIFY)
.orElse(new GotifyNotificationDispatcherConfig());
return gotifyNotificationDispatcherDTOConverter.convertToDTO(result);
}


@PostMapping("/config/gotify")
public ResponseEntity<MessageResponse> setGotifyConfig(@RequestBody GotifyNotificationDispatcherConfigDTO config) {
final AbstractNotificationDispatcherConfig toSave = gotifyNotificationDispatcherDTOConverter.convertFromDTO(config);
notificationDispatcherService.setUserConfig(NotificationDispatcherName.GOTIFY, toSave);
return ResponseEntity.ok(new MessageResponse("Success"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum NotificationDispatcherName {
CONSOLE,
EMAIL,
GOTIFY,
NTFY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.github.mdeluise.plantit.notification.gotify;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

import com.github.mdeluise.plantit.notification.NotifyException;
import com.github.mdeluise.plantit.notification.dispatcher.NotificationDispatcher;
import com.github.mdeluise.plantit.notification.dispatcher.NotificationDispatcherName;
import com.github.mdeluise.plantit.notification.dispatcher.config.NotificationDispatcherConfig;
import com.github.mdeluise.plantit.reminder.Reminder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;

@Component
public class GotifyNotificationDispatcher implements NotificationDispatcher {
private final boolean enabled;
private String url;
private String token;
private final Logger logger = LoggerFactory.getLogger(GotifyNotificationDispatcher.class);


public GotifyNotificationDispatcher(@Value("${server.notification.gotify.enabled}") boolean enabled) {
this.enabled = enabled;
}


public void notifyReminder(Reminder reminder) throws NotifyException {
final HttpClient httpClient = HttpClient.newHttpClient();
final URI uri = URI.create(url).resolve("/message?token=" + token);
final String title = reminder.getTarget().getInfo().getPersonalName();
final String message = "Time to take care of " + title + ", action required: " + reminder.getAction();
final String body = String.format("{\"title\": \"%s\", \"message\": \"%s\"}", title, message);

final HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();

try {
final HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

final int statusCode = response.statusCode();
if (statusCode < 200 || statusCode >= 300) {
throw new NotifyException("Failed to send notification. Response code: " + statusCode +
" Response body: " + response.body());
}
} catch (Exception e) {
throw new NotifyException(e);
}
}


@Override
public NotificationDispatcherName getName() {
return NotificationDispatcherName.GOTIFY;
}


@Override
public boolean isEnabled() {
return enabled;
}


@Override
public void loadConfig(NotificationDispatcherConfig config) {
if (!(config instanceof GotifyNotificationDispatcherConfig gotifyConfig)) {
throw new UnsupportedOperationException(
"Configuration provided must be of type GotifyNotificationDispatcherConfig");
}
checkConfigParameters(gotifyConfig);
url = gotifyConfig.getUrl();
token = gotifyConfig.getToken();
}


@Override
public void initConfig() {
url = null;
token = null;
}


private void checkConfigParameters(GotifyNotificationDispatcherConfig gotifyConfig) {
if (gotifyConfig.getUrl() == null || gotifyConfig.getToken() == null) {
final String errorMsg = "Gotify url and token must be provided.";
logger.error(errorMsg);
throw new IllegalArgumentException(errorMsg);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.mdeluise.plantit.notification.gotify;

import com.github.mdeluise.plantit.notification.dispatcher.config.AbstractNotificationDispatcherConfig;
import com.github.mdeluise.plantit.notification.dispatcher.config.NotificationDispatcherConfig;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;

@Entity
@Table(name = "gotify_notification_configs")
public class GotifyNotificationDispatcherConfig extends AbstractNotificationDispatcherConfig {
private String url;
private String token;


public String getUrl() {
return url;
}


public void setUrl(String url) {
this.url = url;
}


public String getToken() {
return token;
}


public void setToken(String token) {
this.token = token;
}


@Override
public void update(NotificationDispatcherConfig updated) {
if (!(updated instanceof GotifyNotificationDispatcherConfig)) {
throw new UnsupportedOperationException("Updated class must be of type GotifyNotificationDispatcherConfig");
}
final GotifyNotificationDispatcherConfig gotifyUpdated = (GotifyNotificationDispatcherConfig) updated;
this.setUrl(gotifyUpdated.getUrl());
this.setToken(gotifyUpdated.getToken());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.mdeluise.plantit.notification.gotify;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(name = "Gotify notification config", description = "Represents the gotify notifier configuration")
public class GotifyNotificationDispatcherConfigDTO {
@Schema(description = "id of the config", accessMode = Schema.AccessMode.READ_ONLY)
private String id;
@Schema(description = "url of the gotify server")
private String url;
@Schema(description = "token for auth in the gotify server")
private String token;


public String getId() {
return id;
}


public void setId(String id) {
this.id = id;
}


public String getUrl() {
return url;
}


public void setUrl(String url) {
this.url = url;
}


public String getToken() {
return token;
}


public void setToken(String token) {
this.token = token;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.mdeluise.plantit.notification.gotify;

import com.github.mdeluise.plantit.common.AbstractDTOConverter;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Component;

@Component
public class GotifyNotificationDispatcherDTOConverter extends
AbstractDTOConverter<GotifyNotificationDispatcherConfig, GotifyNotificationDispatcherConfigDTO> {
public GotifyNotificationDispatcherDTOConverter(ModelMapper modelMapper) {
super(modelMapper);
}


@Override
public GotifyNotificationDispatcherConfig convertFromDTO(
GotifyNotificationDispatcherConfigDTO dto) {
return modelMapper.map(dto, GotifyNotificationDispatcherConfig.class);
}


@Override
public GotifyNotificationDispatcherConfigDTO convertToDTO(
GotifyNotificationDispatcherConfig data) {
return modelMapper.map(data, GotifyNotificationDispatcherConfigDTO.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,13 @@ public void notifyReminder(Reminder reminder) throws NotifyException {
reminder.getTarget().getInfo().getPersonalName())
.header("X-Tags", "seedling");

// Set request body
final String requestBody =
"Time to take care of " + reminder.getTarget().getInfo().getPersonalName() + ", action required: " +
reminder.getAction();
final HttpRequest.BodyPublisher bodyPublisher =
HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8);
requestBuilder.method("POST", bodyPublisher);

// Set authentication based on provided credentials
if (username != null && password != null) {
final String credentials = username + ":" + password;
final String basicAuth = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes());
Expand All @@ -63,7 +61,6 @@ public void notifyReminder(Reminder reminder) throws NotifyException {
final HttpRequest httpRequest = requestBuilder.build();
final HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

// Handle response
int statusCode = response.statusCode();
if (statusCode < 200 || statusCode >= 300) {
throw new NotifyException("Failed to send notification. Response code: " + statusCode);
Expand Down
7 changes: 4 additions & 3 deletions backend/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ spring.jpa.hibernate.ddl-auto = update


#
# RATE LIMITING
# Rate Limiting
#
#server.rateLimit.requestPerSeconds = ${MAX_REQUESTS_PER_MINUTE:50}
server.rateLimit.requestPerMinute = ${MAX_REQUESTS_PER_MINUTE:100}
Expand Down Expand Up @@ -119,6 +119,7 @@ spring.servlet.multipart.max-file-size = ${MAX_UPLOAD_IMG_SIZE:15MB}


#
# NTFY
# Notification
#
server.notification.ntfy.enabled = ${NTFY_ENABLED:true}
server.notification.ntfy.enabled = ${NTFY_ENABLED:false}
server.notification.gotify.enabled = ${GOTIFY_ENABLED:false}
8 changes: 4 additions & 4 deletions backend/src/main/resources/application-integration.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,12 @@ server.cors.allowed-origins = ${ALLOWED_ORIGINS:*}


#
# RATE LIMITING
# Rate Limiting
#
#server.rateLimit.requestPerSeconds = ${MAX_REQUESTS_PER_MINUTE:999}
server.rateLimit.requestPerMinute = ${MAX_REQUESTS_PER_MINUTE:99999}



#
# Info
#
Expand Down Expand Up @@ -98,6 +97,7 @@ spring.servlet.multipart.max-file-size = ${MAX_UPLOAD_IMG_SIZE:15MB}


#
# NTFY
# Notification
#
server.notification.ntfy.enabled = ${NTFY_ENABLED:false}
server.notification.ntfy.enabled = ${NTFY_ENABLED:false}
server.notification.gotify.enabled = ${GOTIFY_ENABLED:false}
Loading

0 comments on commit 1e37f51

Please sign in to comment.