From dca8ac958a96eda7fbf75a996b2426c8dafc3945 Mon Sep 17 00:00:00 2001
From: leafus <nxtsrr@proton.me>
Date: Sun, 29 Sep 2024 19:15:37 +0200
Subject: [PATCH] init

---
 go.mod                |   5 ++
 go.sum                |   2 +
 main.go               | 158 ++++++++++++++++++++++++++++++++++++++++++
 web/assets/global.css |  52 ++++++++++++++
 web/assets/index.js   |  59 ++++++++++++++++
 web/index.html        |  64 +++++++++++++++++
 6 files changed, 340 insertions(+)
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 main.go
 create mode 100644 web/assets/global.css
 create mode 100644 web/assets/index.js
 create mode 100644 web/index.html

diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..09d703f
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module main.go
+
+go 1.23.1
+
+require github.com/joho/godotenv v1.5.1 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d61b19e
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..fff6a22
--- /dev/null
+++ b/main.go
@@ -0,0 +1,158 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"html/template"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"github.com/joho/godotenv"
+)
+
+func main() {
+	err := godotenv.Load()
+	if err != nil {
+		log.Fatal("Error loading .env file")
+	}
+
+	port := os.Getenv("PORT")
+	if port == "" {
+		port = "8080"
+	}
+
+	http.HandleFunc("/", handleIndex)
+	http.HandleFunc("/upload", handleUpload)
+	http.HandleFunc("/delete", handleDelete)
+	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("web/assets"))))
+
+	fmt.Printf("Server starting on port %s\n", port)
+	log.Fatal(http.ListenAndServe(":"+port, nil))
+}
+
+func handleIndex(w http.ResponseWriter, r *http.Request) {
+	tmpl, err := template.ParseFiles("web/index.html")
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	err = tmpl.Execute(w, nil)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func handleUpload(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	if r.Method != http.MethodPost {
+		json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"})
+		return
+	}
+
+	err := r.ParseMultipartForm(10 << 20)
+	if err != nil {
+		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
+		return
+	}
+
+	file, handler, err := r.FormFile("file")
+	if err != nil {
+		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
+		return
+	}
+	defer file.Close()
+
+	directory := r.FormValue("directory")
+	if directory == "" {
+		directory = "/"
+	}
+
+	tempFileName := filepath.Join(os.TempDir(), handler.Filename)
+	tempFile, err := os.Create(tempFileName)
+	if err != nil {
+		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
+		return
+	}
+	defer os.Remove(tempFile.Name())
+	defer tempFile.Close()
+
+	_, err = io.Copy(tempFile, file)
+	if err != nil {
+		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
+		return
+	}
+
+	s3ClientPath := filepath.Join(".", "bin", "s3-client")
+	configFilePath := filepath.Join(".", "bin", "s3config.toml")
+	cmd := exec.Command(s3ClientPath, "-config", configFilePath, "-directory", directory, "-file", tempFile.Name())
+
+	cmd.Args = append(cmd.Args, "-overwrite")
+
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		log.Printf("Error uploading file: %s\n", output)
+		json.NewEncoder(w).Encode(map[string]string{
+			"error": fmt.Sprintf("Error uploading file to S3: %s", output),
+		})
+		return
+	}
+
+	response := struct {
+		Message string `json:"message"`
+		Output  string `json:"output"`
+	}{
+		Message: fmt.Sprintf("File %s uploaded successfully", handler.Filename),
+		Output:  string(output),
+	}
+
+	json.NewEncoder(w).Encode(response)
+}
+
+func handleDelete(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	if r.Method != http.MethodPost {
+		json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"})
+		return
+	}
+
+	err := r.ParseMultipartForm(10 << 20)
+	if err != nil {
+		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
+		return
+	}
+
+	filename := r.FormValue("filename")
+	if filename == "" {
+		json.NewEncoder(w).Encode(map[string]string{"error": "Filename is required"})
+		return
+	}
+
+	s3ClientPath := filepath.Join(".", "bin", "s3-client")
+	configFilePath := filepath.Join(".", "bin", "s3config.toml")
+	cmd := exec.Command(s3ClientPath, "-config", configFilePath, "-delete", filename)
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		log.Printf("Error deleting file: %s\n", output)
+		json.NewEncoder(w).Encode(map[string]string{
+			"error": fmt.Sprintf("Error deleting file from S3: %s", output),
+		})
+		return
+	}
+
+	response := struct {
+		Message string `json:"message"`
+		Output  string `json:"output"`
+	}{
+		Message: fmt.Sprintf("File %s deleted successfully from S3", filename),
+		Output:  string(output),
+	}
+
+	json.NewEncoder(w).Encode(response)
+}
diff --git a/web/assets/global.css b/web/assets/global.css
new file mode 100644
index 0000000..91d6e1f
--- /dev/null
+++ b/web/assets/global.css
@@ -0,0 +1,52 @@
+body {
+    background-color:#121212;
+    color: #fff;
+    font-family: Arial, Helvetica, sans-serif;
+}
+
+.header {
+    margin-top: 10px;
+    margin-bottom: 20px;
+    margin-left: 10px;
+    font-size: 20px;
+}
+
+.button {
+    margin-top: 20px;
+    width: 450px;
+}
+
+input {
+    background-color: #161616;
+    border: none;
+    padding: 10px;
+    outline: none;
+    border-radius: 3px;
+    color: #fff;
+}
+
+button {
+    background-color: #161616;
+    border: none;
+    padding: 10px;
+    outline: none;
+    border-radius: 3px;
+    color: #fff;
+}
+
+button:hover {
+    cursor: pointer;
+}
+
+a {
+    color: #fff;
+}
+
+a:hover {
+    color: violet;
+}
+
+fieldset {
+    border-color: #161616;
+    border-style: solid;
+}
\ No newline at end of file
diff --git a/web/assets/index.js b/web/assets/index.js
new file mode 100644
index 0000000..de858d8
--- /dev/null
+++ b/web/assets/index.js
@@ -0,0 +1,59 @@
+const uploadForm = document.getElementById('uploadForm');
+const uploadStatus = document.getElementById('uploadStatus');
+const commandOutput = document.getElementById('commandOutput');
+
+uploadForm.addEventListener('submit', async (e) => {
+    e.preventDefault();
+    uploadStatus.textContent = 'Uploading...';
+    commandOutput.textContent = '';
+
+    const formData = new FormData(uploadForm);
+
+    try {
+        const response = await fetch('/upload', {
+            method: 'POST',
+            body: formData
+        });
+
+        const result = await response.json();
+
+        if (result.error) {
+            throw new Error(result.error);
+        } else {
+            uploadStatus.textContent = result.message;
+            commandOutput.textContent = result.output;
+        }
+    } catch (error) {
+        uploadStatus.textContent = `Error: ${error.message}`;
+    }
+});
+
+const deleteForm = document.getElementById('deleteForm');
+const deleteStatus = document.getElementById('deleteStatus');
+const deleteOutput = document.getElementById('deleteOutput');
+
+deleteForm.addEventListener('submit', async (e) => {
+    e.preventDefault();
+    deleteStatus.textContent = 'Deleting...';
+    deleteOutput.textContent = '';
+
+    const formData = new FormData(deleteForm);
+
+    try {
+        const response = await fetch('/delete', {
+            method: 'POST',
+            body: formData
+        });
+
+        const result = await response.json();
+
+        if (result.error) {
+            throw new Error(result.error);
+        } else {
+            deleteStatus.textContent = result.message;
+            deleteOutput.textContent = result.output;
+        }
+    } catch (error) {
+        deleteStatus.textContent = `Error: ${error.message}`;
+    }
+});
\ No newline at end of file
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..0b0b248
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <link rel="stylesheet" href="/assets/global.css" />
+    <title>S3-Client Web Wrapper</title>
+    <style>
+      #uploadStatus,
+      #commandOutput {
+        margin-top: 20px;
+      }
+    </style>
+  </head>
+  <body style="max-width: 50%; margin: auto; margin-top: 10vh">
+    <div class="header">S3-Client WebGUI</div>
+
+    <fieldset>
+      <legend>File Selection</legend>
+      <form id="uploadForm">
+        <input type="file" name="file" required />
+        <input
+          type="text"
+          name="directory"
+          placeholder="Directory to upload to [Optional]"
+          style="width: 210px;"
+        />
+        <br />
+        <button type="submit" class="button">Upload</button>
+      </form>
+    </fieldset>
+
+    <fieldset style="margin-top: 10px">
+      <legend>Delete</legend>
+      <form style="display: flex;" id="deleteForm">
+        <input
+          type="text"
+          name="filename"
+          placeholder="Filename to delete"
+          required
+          style="width: 100%;"
+        />
+        <button style="margin-left: 10px;" type="submit">Delete</button>
+      </form>
+    </fieldset>
+
+    <fieldset style="margin-top: 10px">
+      <legend>Terminal</legend>
+      <div id="uploadStatus"></div>
+      <pre id="commandOutput"></pre>
+      <div>
+        <div id="deleteStatus"></div>
+        <pre id="deleteOutput"></pre>
+      </div>
+    </fieldset>
+
+    <div style="margin-top: 10px;">
+        <a href="https://git.fluffy.pw/leafus/s3-client">[ s3-client ]</a> -
+        <a href="https://git.fluffy.pw/leafus/s3-client-web">[ s3-client-web ]</a> - Licensed under MIT
+    </div>
+
+    <script src="/assets/index.js"></script>
+  </body>
+</html>