-
Notifications
You must be signed in to change notification settings - Fork 67
Ntfy #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ntfy #20
Changes from 12 commits
c9bdb3e
b9fe889
a1c06e9
cca5d36
88f71c1
a00a88a
4a31c56
40c96db
3aeaad1
d3464f7
fc05207
5a6b509
72f9ef3
dbcb3bf
dc6cbd9
03df1f3
d081f82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| target/ | ||
| /.idea/ | ||
| .env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.example; | ||
|
|
||
| import javafx.application.Application; | ||
| import javafx.fxml.FXMLLoader; | ||
| import javafx.scene.Parent; | ||
| import javafx.scene.Scene; | ||
| import javafx.stage.Stage; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| public class ChatApplication extends Application { | ||
|
|
||
| @Override | ||
| public void start(Stage stage) throws IOException { | ||
|
|
||
| NtfyConnection ntfyService = new NtfyConnectionImpl(); | ||
| ChatModel model = new ChatModel(ntfyService); | ||
|
|
||
| model.startReceiving(); | ||
|
|
||
| FXMLLoader fxmlLoader = new FXMLLoader( | ||
| ChatApplication.class.getResource("chat-view.fxml")); | ||
|
|
||
| ChatController controller = new ChatController(model); | ||
| fxmlLoader.setController(controller); | ||
|
|
||
| Scene scene = new Scene(fxmlLoader.load(), 600, 400); | ||
| stage.setTitle("JavaFX Ntfy Chat App"); | ||
| stage.setScene(scene); | ||
| stage.show(); | ||
| } | ||
|
|
||
| public static void main(String[] args) { | ||
| launch(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.example; | ||
|
|
||
| import io.github.cdimascio.dotenv.Dotenv; | ||
| import javafx.fxml.FXML; | ||
| import javafx.scene.control.Button; | ||
| import javafx.scene.control.Label; | ||
| import javafx.scene.control.ListView; | ||
| import javafx.scene.control.TextField; | ||
|
|
||
| import java.time.Instant; | ||
| import java.time.ZoneId; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Objects; | ||
|
|
||
|
|
||
| public class ChatController { | ||
|
|
||
| @FXML private ListView<ChatMessage> messageListView; | ||
| @FXML private TextField inputTextField; | ||
| @FXML private Button sendButton; | ||
|
|
||
| private final ChatModel model; | ||
|
|
||
| private final DateTimeFormatter timeFormatter = | ||
| DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault()); | ||
|
|
||
|
|
||
| public ChatController(ChatModel model) { | ||
| this.model = model; | ||
| } | ||
|
|
||
| @FXML | ||
| public void initialize() { | ||
| messageListView.setItems(model.getMessages()); | ||
|
|
||
| messageListView.setCellFactory(lv -> new javafx.scene.control.ListCell<ChatMessage>() { | ||
| @Override | ||
| protected void updateItem(ChatMessage msg, boolean empty) { | ||
| super.updateItem(msg, empty); | ||
| if (empty || msg == null) { | ||
| setText(null); | ||
| } else { | ||
| String formattedTime = timeFormatter.format(Instant.ofEpochSecond(msg.timestamp())); | ||
| setText("[" + formattedTime + "] " + msg.content()); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
|
|
||
| inputTextField.setOnAction(event -> sendMessageAction()); | ||
| sendButton.setOnAction(event -> sendMessageAction()); | ||
| } | ||
|
|
||
|
|
||
| private void sendMessageAction() { | ||
| String message = inputTextField.getText().trim(); | ||
| if (!message.isEmpty()) { | ||
|
|
||
| model.sendMessage(message); | ||
|
|
||
|
|
||
| inputTextField.clear(); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package com.example; | ||
|
|
||
| public record ChatMessage(String content, long timestamp) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import javafx.application.Platform; | ||||||||||||||||||||||||||||
| import javafx.collections.FXCollections; | ||||||||||||||||||||||||||||
| import javafx.collections.ObservableList; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public class ChatModel { | ||||||||||||||||||||||||||||
| private final ObservableList<ChatMessage> messages = FXCollections.observableArrayList(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private final NtfyConnection ntfyConnection; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public ChatModel(NtfyConnection ntfyConnection) { | ||||||||||||||||||||||||||||
| this.ntfyConnection = ntfyConnection; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| public ObservableList<ChatMessage> getMessages() { | ||||||||||||||||||||||||||||
| return messages; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void sendMessage(String text) { | ||||||||||||||||||||||||||||
| ntfyConnection.send(text); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void startReceiving() { | ||||||||||||||||||||||||||||
| ntfyConnection.receive(ntfyDto -> { | ||||||||||||||||||||||||||||
| ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time()); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Platform.runLater(() -> { | ||||||||||||||||||||||||||||
| messages.add(chatMsg); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: JavaFX threading violation - ObservableList modified from background thread. Line 28 modifies the JavaFX You've imported Apply this diff to fix the threading issue: public void startReceiving() {
ntfyConnection.receive(ntfyDto -> {
ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time());
-
- messages.add(chatMsg);
+ Platform.runLater(() -> messages.add(chatMsg));
});
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example; | ||
|
|
||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.function.Consumer; | ||
|
|
||
| public interface NtfyConnection { | ||
| CompletableFuture<Void> send(String message); | ||
| void receive(Consumer<NtfyMessageDto> messageHandler); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||
| package com.example; | ||||||
|
|
||||||
| import io.github.cdimascio.dotenv.Dotenv; | ||||||
| import tools.jackson.databind.ObjectMapper; | ||||||
|
|
||||||
| import java.net.URI; | ||||||
| import java.net.http.HttpClient; | ||||||
| import java.net.http.HttpRequest; | ||||||
| import java.net.http.HttpResponse; | ||||||
| import java.util.concurrent.CompletableFuture; | ||||||
| import java.util.function.Consumer; | ||||||
|
|
||||||
| public class NtfyConnectionImpl implements NtfyConnection { | ||||||
|
|
||||||
| private final HttpClient http = HttpClient.newHttpClient(); | ||||||
| private final String hostName; | ||||||
| private final ObjectMapper mapper = new ObjectMapper(); | ||||||
|
|
||||||
| public NtfyConnectionImpl() { | ||||||
| String loadedHostName = null; | ||||||
| try { | ||||||
| Dotenv dotenv = Dotenv.load(); | ||||||
| loadedHostName = dotenv.get("HOST_NAME"); | ||||||
| } catch (Exception e) { | ||||||
| System.err.println("WARNING: Could not load .env file for HOST_NAME. Using fallback."); | ||||||
| } | ||||||
|
|
||||||
| this.hostName = (loadedHostName != null) | ||||||
| ? loadedHostName | ||||||
| : "http://localhost:8080"; | ||||||
|
|
||||||
| if (this.hostName.equals("http://localhost:8080")) { | ||||||
| System.out.println("DEBUG: NtfyConnectionImpl running in test/fallback mode."); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public NtfyConnectionImpl(String hostName) { | ||||||
| this.hostName = hostName; | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public CompletableFuture<Void> send(String message) { | ||||||
| HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||
| .POST(HttpRequest.BodyPublishers.ofString(message)) | ||||||
| .uri(URI.create(hostName + "/mytopic")) | ||||||
| .build(); | ||||||
|
|
||||||
| return http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding()) | ||||||
| .thenAccept(response -> { | ||||||
| if (response.statusCode() >= 200 && response.statusCode() < 300) { | ||||||
| System.out.println("Message sent successfully."); | ||||||
| } else { | ||||||
| System.err.println("Error while sending: " + response.statusCode()); | ||||||
| } | ||||||
| }) | ||||||
| .exceptionally(e -> { | ||||||
| System.err.println("Network issue: " + e.getMessage()); | ||||||
| return null; | ||||||
| }); | ||||||
| } | ||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||
|
|
||||||
| @Override | ||||||
| public void receive(Consumer<NtfyMessageDto> messageHandler) { | ||||||
| HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||
| .GET() | ||||||
| .uri(URI.create(hostName + "/mytopic/json")) | ||||||
| .build(); | ||||||
|
|
||||||
| http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines()) | ||||||
| .thenAccept(response -> response.body() | ||||||
| .map(s -> { | ||||||
| try { | ||||||
| return mapper.readValue(s, NtfyMessageDto.class); | ||||||
| } catch (Exception e) { | ||||||
| System.err.println("Failed to parse message: " + e.getMessage()); | ||||||
| return null; | ||||||
| } | ||||||
| }) | ||||||
| .filter(messageDto -> messageDto != null && messageDto.event().equals("message")) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid potential NPE in event comparison. If Apply this diff: - .filter(messageDto -> messageDto != null && messageDto.event().equals("message"))
+ .filter(messageDto -> messageDto != null && "message".equals(messageDto.event()))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| .peek(System.out::println) | ||||||
| .forEach(messageHandler)); | ||||||
| } | ||||||
|
Comment on lines
+62
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix compilation error and add error handling. This code has a critical compilation issue and lacks error handling:
Apply this diff to fix the compilation error and add proper error handling: @Override
public void receive(Consumer<NtfyMessageDto> messageHandler) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(hostName + "/mytopic/json"))
.build();
http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body()
- .map(s ->
- mapper.readValue(s, NtfyMessageDto.class))
+ .map(s -> {
+ try {
+ return mapper.readValue(s, NtfyMessageDto.class);
+ } catch (IOException e) {
+ System.err.println("Error parsing message: " + e.getMessage());
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
.filter(message -> message.event().equals("message"))
.peek(System.out::println)
- .forEach(messageHandler));
+ .forEach(messageHandler))
+ .exceptionally(throwable -> {
+ System.err.println("Error receiving messages: " + throwable.getMessage());
+ return null;
+ });
} |
||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.example; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
|
|
||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public record NtfyMessageDto(String id, long time, String event, String topic, String message) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,10 @@ | ||||||
| module hellofx { | ||||||
| module chatapp { | ||||||
| requires javafx.controls; | ||||||
| requires javafx.fxml; | ||||||
| requires com.fasterxml.jackson.annotation; | ||||||
| requires io.github.cdimascio.dotenv.java; | ||||||
| requires tools.jackson.databind; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainIncorrect Jackson module name. The module name Apply this diff to use the correct module name: - requires tools.jackson.databind;
+ requires com.fasterxml.jackson.databind;Note: This change must be coordinated with fixing the dependency in pom.xml. Run the following script to verify Jackson usage in the codebase: 🏁 Script executed: #!/bin/bash
# Verify Jackson module usage and check for standard Jackson dependencies
echo "=== Checking for Jackson imports in source files ==="
rg -n "import com\.fasterxml\.jackson" --type java
echo -e "\n=== Checking pom.xml for Jackson dependencies ==="
rg -n "jackson" pom.xmlLength of output: 323 Incorrect Jackson module name in module-info.java. The module name Apply this change: - requires tools.jackson.databind;
+ requires com.fasterxml.jackson.databind;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| requires java.net.http; | ||||||
|
|
||||||
| opens com.example to javafx.fxml; | ||||||
| exports com.example; | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Pre-release versions in test dependencies.
Both WireMock (4.0.0-beta.15) and TestFX (4.0.16-alpha) are using pre-release versions. While acceptable for test dependencies, ensure these versions are stable enough for your testing needs.
🌐 Web query:
💡 Result:
Short answer: No — both are pre-release and should be used with caution, not considered “stable” for critical/testing without verification.
WireMock 4.0.0-beta.15 is explicitly a 4.x beta (WireMock v4 is still labelled beta and may contain breaking/unstable changes; Java 17 is the baseline). If you need stability, the WireMock 3.x line is the production release. [1][2]
TestFX 4.0.16-alpha is an alpha (pre‑release) artifact from 2019 and has known compatibility issues with newer Java/JavaFX releases; newer TestFX builds (e.g., 4.0.18) exist. TestFX alphas frequently require careful Java/JavaFX version matching and may need workarounds. [3][4]
If you want actionable guidance: use WireMock 3.x for stable CI tests or accept beta risk and pin Java 17 for v4 beta; for UI tests prefer a maintained TestFX release (or verify your exact JDK/OpenJFX/TestFX combination in CI) before relying on 4.0.16-alpha. References: WireMock v4 beta docs and downloads, TestFX Maven listing and GitHub issue describing JavaFX compatibility problems. [1][2][3][4]
Replace pre-release test dependencies with stable versions.
Verification confirms both are problematic for testing:
Consider pinning stable versions in
pom.xmllines 58–69 to avoid CI failures or unexpected behavior during test execution.🤖 Prompt for AI Agents