From bdea0c6d6136c5a73e2bb8de2fff74404734d1c9 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 23 Apr 2017 12:55:37 +0200
Subject: [PATCH] Fixed #81 - database delete functionality

---
 .../budgetmaster/logic/ServerConnection.java  |  14 +++
 .../budgetmaster/ui/Controller.java           |   1 -
 src/de/deadlocker8/budgetmaster/ui/Modal.fxml |  22 ++++
 .../budgetmaster/ui/ModalController.java      |  18 +++
 .../budgetmaster/ui/SettingsController.java   | 114 ++++++++++++++++--
 .../budgetmaster/ui/SettingsTab.fxml          |  41 ++++++-
 .../main/DatabaseHandler.java                 |  40 ++++++
 .../server/SparkServer.java                   |   4 +
 .../server/database/DatabaseDelete.java       |  39 ++++++
 9 files changed, 277 insertions(+), 16 deletions(-)
 create mode 100644 src/de/deadlocker8/budgetmaster/ui/Modal.fxml
 create mode 100644 src/de/deadlocker8/budgetmaster/ui/ModalController.java
 create mode 100644 src/de/deadlocker8/budgetmasterserver/server/database/DatabaseDelete.java

diff --git a/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java b/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java
index c26597f25..103917ecc 100644
--- a/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java
+++ b/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java
@@ -321,4 +321,18 @@ public class ServerConnection
 			return 0;
 		}
 	}
+	
+	/*
+	 * DATABASE
+	 */	
+	public void deleteDatabase() throws Exception
+	{
+		URL url = new URL(settings.getUrl() + "/database?secret=" + Helpers.getURLEncodedString(settings.getSecret()));
+		HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection();
+		httpsCon.setRequestMethod("DELETE");
+		httpsCon.setDoInput(true);
+		InputStream stream = httpsCon.getInputStream();
+		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+		reader.close();
+	}
 }
\ No newline at end of file
diff --git a/src/de/deadlocker8/budgetmaster/ui/Controller.java b/src/de/deadlocker8/budgetmaster/ui/Controller.java
index a9f1c4350..07109ba91 100644
--- a/src/de/deadlocker8/budgetmaster/ui/Controller.java
+++ b/src/de/deadlocker8/budgetmaster/ui/Controller.java
@@ -1,7 +1,6 @@
 package de.deadlocker8.budgetmaster.ui;
 
 import java.io.IOException;
-import java.net.ConnectException;
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.ResourceBundle;
diff --git a/src/de/deadlocker8/budgetmaster/ui/Modal.fxml b/src/de/deadlocker8/budgetmaster/ui/Modal.fxml
new file mode 100644
index 000000000..33759ff9b
--- /dev/null
+++ b/src/de/deadlocker8/budgetmaster/ui/Modal.fxml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.ProgressIndicator?>
+<?import javafx.scene.layout.AnchorPane?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.text.Font?>
+
+<AnchorPane fx:id="anchorPaneMain" prefHeight="100.0" prefWidth="375.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.ModalController">
+   <children>
+      <HBox alignment="CENTER_LEFT" spacing="10.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0">
+         <children>
+            <Label fx:id="labelMessage" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
+               <font>
+                  <Font size="15.0" />
+               </font>
+            </Label>
+            <ProgressIndicator prefHeight="72.0" prefWidth="34.0" />
+         </children>
+      </HBox>
+   </children>
+</AnchorPane>
diff --git a/src/de/deadlocker8/budgetmaster/ui/ModalController.java b/src/de/deadlocker8/budgetmaster/ui/ModalController.java
new file mode 100644
index 000000000..042c7345a
--- /dev/null
+++ b/src/de/deadlocker8/budgetmaster/ui/ModalController.java
@@ -0,0 +1,18 @@
+package de.deadlocker8.budgetmaster.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.stage.Stage;
+
+public class ModalController
+{
+	@FXML private Label labelMessage;	
+	
+	public void init(Stage stage, String message)
+	{
+		labelMessage.setText(message);
+		stage.setOnCloseRequest((e)->{
+			e.consume();
+		});
+	}
+}
\ No newline at end of file
diff --git a/src/de/deadlocker8/budgetmaster/ui/SettingsController.java b/src/de/deadlocker8/budgetmaster/ui/SettingsController.java
index f913b6634..a90283827 100644
--- a/src/de/deadlocker8/budgetmaster/ui/SettingsController.java
+++ b/src/de/deadlocker8/budgetmaster/ui/SettingsController.java
@@ -2,20 +2,33 @@ package de.deadlocker8.budgetmaster.ui;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Optional;
 
+import de.deadlocker8.budgetmaster.logic.ServerConnection;
 import de.deadlocker8.budgetmaster.logic.Settings;
 import de.deadlocker8.budgetmaster.logic.Utils;
+import javafx.application.Platform;
 import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
 import javafx.scene.control.Alert.AlertType;
 import javafx.scene.control.Button;
 import javafx.scene.control.Label;
 import javafx.scene.control.RadioButton;
 import javafx.scene.control.TextArea;
 import javafx.scene.control.TextField;
+import javafx.scene.control.TextInputDialog;
 import javafx.scene.control.ToggleGroup;
 import javafx.scene.layout.AnchorPane;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
 import logger.Logger;
 import tools.AlertGenerator;
+import tools.BASE58Type;
+import tools.ConvertTo;
+import tools.RandomCreations;
+import tools.Worker;
 
 public class SettingsController
 {
@@ -27,6 +40,8 @@ public class SettingsController
 	@FXML private TextField textFieldCurrency;
 	@FXML private Label labelCurrency;
 	@FXML private Button buttonSave;
+	@FXML private Button buttonBackupDB;
+	@FXML private Button buttonDeleteDB;
 	@FXML private RadioButton radioButtonRestActivated;
 	@FXML private RadioButton radioButtonRestDeactivated;
 	@FXML private TextArea textAreaTrustedHosts;
@@ -51,21 +66,23 @@ public class SettingsController
 			}
 			setTextAreaTrustedHosts(controller.getSettings().getTrustedHosts());
 		}
-		
+
 		anchorPaneMain.setStyle("-fx-background-color: #F4F4F4;");
 		labelSecret.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text"));
 		labelURL.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text"));
 		labelCurrency.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text"));
-		buttonSave.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");	
+		buttonSave.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");
+		buttonBackupDB.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");
+		buttonDeleteDB.setStyle("-fx-background-color: #FF5047; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;");
 		textFieldURL.setPromptText("z.B. https://yourdomain.de");
 		textFieldCurrency.setPromptText("z.B. €, CHF, $");
 		textAreaTrustedHosts.setPromptText("z.B. localhost");
-		
+
 		ToggleGroup toggleGroup = new ToggleGroup();
 		radioButtonRestActivated.setToggleGroup(toggleGroup);
 		radioButtonRestDeactivated.setToggleGroup(toggleGroup);
 	}
-	
+
 	private void setTextAreaTrustedHosts(ArrayList<String> trustedHosts)
 	{
 		StringBuilder trustedHostsString = new StringBuilder();
@@ -83,7 +100,7 @@ public class SettingsController
 			textAreaTrustedHosts.setText("");
 		}
 	}
-	
+
 	public void save()
 	{
 		String url = textFieldURL.getText().trim();
@@ -107,11 +124,11 @@ public class SettingsController
 						}
 					}
 					setTextAreaTrustedHosts(trustedHosts);
-					
+
 					if(controller.getSettings() != null)
 					{
 						controller.getSettings().setUrl(url);
-						controller.getSettings().setSecret(secret);	
+						controller.getSettings().setSecret(secret);
 						controller.getSettings().setCurrency(currency);
 						controller.getSettings().setRestActivated(radioButtonRestActivated.isSelected());
 						controller.getSettings().setTrustedHosts(trustedHosts);
@@ -126,7 +143,7 @@ public class SettingsController
 						settings.setTrustedHosts(trustedHosts);
 						controller.setSettings(settings);
 					}
-					
+
 					try
 					{
 						Utils.saveSettings(controller.getSettings());
@@ -135,7 +152,7 @@ public class SettingsController
 					{
 						Logger.error(e);
 						AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Beim Speichern der Einstellungen ist ein Fehler aufgetreten", controller.getIcon(), controller.getStage(), null, false);
-					}	
+					}
 					controller.refresh(controller.getFilterSettings());
 					controller.showNotification("Erfolgreich gespeichert");
 				}
@@ -154,4 +171,83 @@ public class SettingsController
 			AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Das Feld für die Server URL darf nicht leer sein!", controller.getIcon(), controller.getStage(), null, false);
 		}
 	}
+
+	public void backupDB()
+	{
+
+	}
+
+	public void deleteDB()
+	{
+		String verificationCode = ConvertTo.toBase58(RandomCreations.generateRandomMixedCaseString(4, true), true, BASE58Type.UPPER);
+
+		TextInputDialog dialog = new TextInputDialog();
+		dialog.setTitle("Datenbank löschen");
+		dialog.setHeaderText("Soll die Datenbank wirklich gelöscht werden?");
+		dialog.setContentText("Zur Bestätigung gib folgenden Code ein:\t" + verificationCode);
+		Stage dialogStage = (Stage)dialog.getDialogPane().getScene().getWindow();
+		dialogStage.getIcons().add(controller.getIcon());
+		dialogStage.initOwner(controller.getStage());
+
+		Optional<String> result = dialog.showAndWait();
+		if(result.isPresent())
+		{
+			if(result.get().equals(verificationCode))
+			{
+				Stage modalStage = showModal("Vorgang läuft", "Die Datenbank wird gelöscht, bitte warten...");		
+				
+				Worker.runLater(() -> {
+					try
+					{
+						ServerConnection connection = new ServerConnection(controller.getSettings());
+						connection.deleteDatabase();
+						Platform.runLater(() -> {								
+							if(modalStage != null)
+							{
+								modalStage.close();
+							}
+						});
+					}
+					catch(Exception e)
+					{
+						Logger.error(e);
+						Platform.runLater(() -> {							
+							controller.showConnectionErrorAlert(e.getMessage());
+						});
+					}
+				});
+			}
+			else
+			{
+				AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Die Eingabe stimmt nicht mit dem Bestätigungscode überein.", controller.getIcon(), controller.getStage(), null, false);
+				deleteDB();
+			}
+		}
+	}
+	
+	private Stage showModal(String title, String message)
+	{
+		try
+		{
+			FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/de/deadlocker8/budgetmaster/ui/Modal.fxml"));
+			Parent root = (Parent)fxmlLoader.load();
+			Stage newStage = new Stage();
+			newStage.initOwner(controller.getStage());
+			newStage.initModality(Modality.APPLICATION_MODAL);			
+			newStage.setTitle(title);
+			newStage.setScene(new Scene(root));
+			newStage.getIcons().add(controller.getIcon());
+			newStage.setResizable(false);
+			ModalController newController = fxmlLoader.getController();
+			newController.init(newStage, message);
+			newStage.show();
+			
+			return newStage;
+		}
+		catch(IOException e)
+		{
+			Logger.error(e);
+			return null;
+		}
+	}
 }
\ No newline at end of file
diff --git a/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml b/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml
index 67116a650..7b620aeca 100644
--- a/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml
+++ b/src/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml
@@ -16,7 +16,7 @@
    <children>
       <VBox alignment="TOP_CENTER" layoutY="24.0" prefHeight="562.0" prefWidth="772.0" spacing="25.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="25.0">
          <children>
-            <HBox prefHeight="334.0" prefWidth="722.0">
+            <HBox prefHeight="359.0" prefWidth="722.0">
                <children>
                   <VBox alignment="CENTER_RIGHT" prefHeight="25.0" prefWidth="158.0" spacing="20.0">
                      <children>
@@ -47,8 +47,11 @@
                            <font>
                               <Font name="System Bold" size="16.0" />
                            </font>
+                           <VBox.margin>
+                              <Insets top="-5.0" />
+                           </VBox.margin>
                         </Label>
-                        <Label fx:id="labelSecret1111" alignment="CENTER" contentDisplay="CENTER" maxHeight="-Infinity" text="(ein Host pro Zeile)" textAlignment="CENTER" wrapText="true">
+                        <Label fx:id="labelSecret1111" alignment="CENTER" contentDisplay="CENTER" maxHeight="-Infinity" text="(ein Host pro Zeile)" textAlignment="CENTER" wrapText="true" VBox.vgrow="ALWAYS">
                            <font>
                               <Font size="14.0" />
                            </font>
@@ -56,7 +59,15 @@
                               <Insets top="-20.0" />
                            </VBox.margin>
                         </Label>
-                        <Region prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
+                        <Label fx:id="labelSecret1112" alignment="CENTER_RIGHT" contentDisplay="RIGHT" maxHeight="-Infinity" prefWidth="158.0" text="Datenbank:" textAlignment="RIGHT" wrapText="true">
+                           <font>
+                              <Font name="System Bold" size="16.0" />
+                           </font>
+                           <VBox.margin>
+                              <Insets top="35.0" />
+                           </VBox.margin>
+                        </Label>
+                        <Region prefHeight="19.0" prefWidth="158.0" VBox.vgrow="ALWAYS" />
                      </children>
                      <HBox.margin>
                         <Insets right="25.0" />
@@ -67,14 +78,14 @@
                         <TextField fx:id="textFieldURL" />
                         <TextField fx:id="textFieldSecret" />
                         <TextField fx:id="textFieldCurrency" />
-                        <HBox alignment="CENTER" prefHeight="11.0" prefWidth="539.0">
+                        <HBox alignment="CENTER" prefHeight="11.0" prefWidth="539.0" spacing="30.0">
                            <children>
                               <RadioButton fx:id="radioButtonRestActivated" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="aktiviert">
                                  <font>
                                     <Font size="14.0" />
                                  </font>
                                  <HBox.margin>
-                                    <Insets right="30.0" />
+                                    <Insets />
                                  </HBox.margin>
                               </RadioButton>
                               <RadioButton fx:id="radioButtonRestDeactivated" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="deaktiviert" HBox.hgrow="ALWAYS">
@@ -84,7 +95,25 @@
                               </RadioButton>
                            </children>
                         </HBox>
-                        <TextArea fx:id="textAreaTrustedHosts" prefHeight="136.0" prefWidth="539.0" VBox.vgrow="ALWAYS" />
+                        <TextArea fx:id="textAreaTrustedHosts" prefWidth="539.0" VBox.vgrow="ALWAYS">
+                           <VBox.margin>
+                              <Insets />
+                           </VBox.margin></TextArea>
+                        <HBox alignment="CENTER_LEFT" prefHeight="11.0" prefWidth="539.0" spacing="30.0">
+                           <children>
+                              <Button fx:id="buttonBackupDB" mnemonicParsing="false" onAction="#backupDB" text="Backup">
+                                 <font>
+                                    <Font name="System Bold" size="14.0" />
+                                 </font>
+                              </Button>
+                              <Button fx:id="buttonDeleteDB" mnemonicParsing="false" onAction="#deleteDB" text="Löschen">
+                                 <font>
+                                    <Font name="System Bold" size="14.0" />
+                                 </font>
+                              </Button>
+                           </children>
+                        </HBox>
+                        <Region prefHeight="19.0" prefWidth="158.0" VBox.vgrow="ALWAYS" />
                      </children>
                   </VBox>
                </children>
diff --git a/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java b/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java
index 43097810d..bda5b7ff1 100644
--- a/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java
+++ b/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java
@@ -624,6 +624,46 @@ public class DatabaseHandler
 			}
 		}
 	}
+	
+	public void deleteDatabase()
+	{
+		Statement stmt = null;
+		String tableCategory = "DROP TABLE IF EXISTS category;";
+		String tablePayment = "DROP TABLE IF EXISTS payment;";
+		String tableRepeatingPayment = "DROP TABLE IF EXISTS repeating_payment;";
+		String tableRepeatingEntry= "DROP TABLE IF EXISTS repeating_entry;";
+		try
+		{
+			stmt = connection.createStatement();
+			stmt.execute("SET FOREIGN_KEY_CHECKS = 0;");
+			stmt.execute(tableCategory);
+			Logger.info("Deleted table: category");
+			stmt.execute(tablePayment);
+			Logger.info("Deleted table: payment");
+			stmt.execute(tableRepeatingPayment);
+			Logger.info("Deleted table: repeating_payment");
+			stmt.execute(tableRepeatingEntry);
+			Logger.info("Deleted table: repeating_entry");
+			stmt.execute("SET FOREIGN_KEY_CHECKS = 1;");
+		}
+		catch(SQLException e)
+		{
+			Logger.error(e);
+		}
+		finally
+		{
+			if(stmt != null)
+			{
+				try
+				{
+					stmt.close();
+				}
+				catch(SQLException e)
+				{
+				}
+			}
+		}
+	}
 
 	/*
 	 * ADD
diff --git a/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java b/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java
index 760e6fb74..62aea0bf8 100644
--- a/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java
+++ b/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java
@@ -25,6 +25,7 @@ import de.deadlocker8.budgetmasterserver.server.category.CategoryGet;
 import de.deadlocker8.budgetmasterserver.server.category.CategoryGetAll;
 import de.deadlocker8.budgetmasterserver.server.category.CategoryUpdate;
 import de.deadlocker8.budgetmasterserver.server.categorybudget.CategoryBudgetGet;
+import de.deadlocker8.budgetmasterserver.server.database.DatabaseDelete;
 import de.deadlocker8.budgetmasterserver.server.payment.normal.PaymentAdd;
 import de.deadlocker8.budgetmasterserver.server.payment.normal.PaymentDelete;
 import de.deadlocker8.budgetmasterserver.server.payment.normal.PaymentGet;
@@ -110,6 +111,9 @@ public class SparkServer
 		
 		// Rest
 		get("/rest", new RestGet(handler, gson));
+		
+		// Database
+		delete("/database", new DatabaseDelete(handler, settings));
 
 		after((request, response) -> {
 			new RepeatingPaymentUpdater(handler).updateRepeatingPayments(DateTime.now());
diff --git a/src/de/deadlocker8/budgetmasterserver/server/database/DatabaseDelete.java b/src/de/deadlocker8/budgetmasterserver/server/database/DatabaseDelete.java
new file mode 100644
index 000000000..24e50ef9c
--- /dev/null
+++ b/src/de/deadlocker8/budgetmasterserver/server/database/DatabaseDelete.java
@@ -0,0 +1,39 @@
+package de.deadlocker8.budgetmasterserver.server.database;
+
+import static spark.Spark.halt;
+
+import de.deadlocker8.budgetmasterserver.main.DatabaseHandler;
+import de.deadlocker8.budgetmasterserver.main.Settings;
+import spark.Request;
+import spark.Response;
+import spark.Route;
+
+public class DatabaseDelete implements Route
+{
+	private DatabaseHandler handler;
+	private Settings settings;
+	
+	public DatabaseDelete(DatabaseHandler handler, Settings settings)
+	{	
+		this.handler = handler;
+		this.settings = settings;
+	}
+
+	@Override
+	public Object handle(Request req, Response res) throws Exception
+	{		
+		try
+		{							
+			handler.deleteDatabase();
+			handler = new DatabaseHandler(settings);
+	
+			return "";
+		}
+		catch(IllegalStateException ex)
+		{
+			halt(500, "Internal Server Error");
+		}
+		
+		return "EIMER";
+	}
+}
\ No newline at end of file
-- 
GitLab