Quellcode durchsuchen

its a mess but i have the client binary working. adding validation to the endpoints that add headers, table items, assets, etc. will continue later

aeth vor 6 Tagen
Ursprung
Commit
4b4237ce8a

+ 39 - 11
cmd/keiji-ctl/keiji-ctl.go

@@ -7,10 +7,12 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"mime/multipart"
 	"net/http"
 	"net/url"
 	"os"
 	"path"
+	"path/filepath"
 
 	"git.aetherial.dev/aeth/keiji/pkg/auth"
 	"git.aetherial.dev/aeth/keiji/pkg/controller"
@@ -18,6 +20,37 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 )
 
+func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
+	file, err := os.Open(path)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	body := &bytes.Buffer{}
+	writer := multipart.NewWriter(body)
+	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
+	if err != nil {
+		return nil, err
+	}
+	_, err = io.Copy(part, file)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	for key, val := range params {
+		_ = writer.WriteField(key, val)
+	}
+	err = writer.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("POST", uri, body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+	return req, err
+}
+
 // authenticate and get the cookie needed to make updates
 func authenticate(url, username, password string) *http.Cookie {
 	client := http.Client{}
@@ -116,21 +149,16 @@ func main() {
 
 	case "nav":
 		fmt.Println(string(pngFile))
-		b, err := os.ReadFile(pngFile)
+		_, fileName := path.Split(pngFile)
+		extraParams := map[string]string{
+			"link":     fileName,
+			"redirect": redirect,
+		}
+		req, err := newfileUploadRequest(address+"/admin/navbar", extraParams, "file", pngFile)
 		if err != nil {
 			log.Fatal(err)
 		}
-		_, fileName := path.Split(pngFile)
-		fmt.Println(fileName)
-		item := storage.NavBarItem{
-			Link:     fileName,
-			Redirect: redirect,
-			Png:      b,
-		}
-		data, _ := json.Marshal(item)
-		req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/admin/navbar", address), bytes.NewReader(data))
 		req.AddCookie(prepareCookie(address))
-		req.Header.Add("Content-Type", "application/json")
 		resp, err := client.Do(req)
 		if err != nil {
 			fmt.Println("There was an error performing the desired request: ", err.Error())

+ 1 - 0
cmd/keiji/keiji.go

@@ -68,6 +68,7 @@ func main() {
 		"login",
 		"admin",
 		"blogpost_editor",
+		"navbar_editor",
 		"post_options",
 		"unhandled_error",
 		"upload",

+ 72 - 25
pkg/controller/admin_handlers.go

@@ -2,6 +2,7 @@ package controller
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"io"
 	"log"
@@ -128,32 +129,53 @@ func (c *Controller) AddMenuItem(ctx *gin.Context) {
 @Router /admin/navbar
 */
 func (c *Controller) AddNavbarItem(ctx *gin.Context) {
+	file, err := ctx.FormFile("file")
+	if err != nil {
+		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
+		return
+	}
+	fh, err := file.Open()
+	if err != nil {
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
+		return
+	}
+	fb := make([]byte, file.Size)
+	var output bytes.Buffer
+	for {
+		n, err := fh.Read(fb)
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
+			return
+		}
+		output.Write(fb[:n])
+	}
 
 	var item storage.NavBarItem
-	err := ctx.ShouldBind(&item)
+	item = storage.NavBarItem{
+		Link: file.Filename,
+		Png:  fb,
+	}
+	err = ctx.ShouldBind(&item)
 	if err != nil {
-		ctx.JSON(400, map[string]string{
-			"Error": err.Error(),
-		})
+		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
 		return
 	}
 	err = c.database.AddNavbarItem(item)
 	if err != nil {
-		ctx.JSON(400, map[string]string{
-			"Error": err.Error(),
-		})
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
 		return
 	}
 
-	err = c.database.AddAsset(item.Link, item.Png)
+	err = c.database.AddAsset(item.File.Filename, fb)
 	if err != nil {
-		ctx.JSON(400, map[string]string{
-			"Error": err.Error(),
-		})
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
 		return
 	}
 
-	ctx.Data(200, "text", []byte("navbar item added."))
+	ctx.HTML(200, "upload_status", gin.H{"UpdateMessage": "Addition Successful!", "Color": "green"})
 }
 
 /*
@@ -163,20 +185,17 @@ func (c *Controller) AddNavbarItem(ctx *gin.Context) {
 @Router /admin/navbar
 */
 func (c *Controller) RemoveNavbarItem(ctx *gin.Context) {
-	itemName := ctx.Param("item_name")
-	err := c.database.RemoveNavbarItem(storage.Identifier(itemName))
+	itemName, ok := ctx.Params.Get("item_name")
+	if !ok {
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": errors.New("No navbar name passed."), "Color": "red"})
+		return
+	}
+	err := c.database.DeleteNavbarItem(storage.Identifier(itemName))
 	if err != nil {
-		ctx.JSON(400, map[string]string{
-			"name":   itemName,
-			"status": "FAIL",
-			"Error":  err.Error(),
-		})
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
 		return
 	}
-	ctx.JSON(200, map[string]string{
-		"name":   itemName,
-		"status": "SUCCESS",
-	})
+	ctx.HTML(200, "upload_status", gin.H{"UpdateMessage": "Deleted '" + itemName + "' Successfully!", "Color": "green"})
 
 }
 
@@ -184,10 +203,10 @@ func (c *Controller) ServeNavbarModify(ctx *gin.Context) {
 	navbar := c.database.GetNavBarLinks()
 	tableData := storage.AdminPage{Tables: map[string][]storage.TableData{}}
 	for i := range navbar {
-		tableData.Tables[storage.Topics[i]] = append(tableData.Tables[storage.Topics[i]],
+		tableData.Tables["items"] = append(tableData.Tables["items"],
 			storage.TableData{
 				DisplayName: navbar[i].Link,
-				Link:        fmt.Sprintf("/admin/options/%s", navbar[i].Link),
+				Link:        fmt.Sprintf("/admin/navbar/options/%s", navbar[i].Link),
 			},
 		)
 	}
@@ -337,6 +356,20 @@ func (c *Controller) ServeNewBlogPage(ctx *gin.Context) {
 	})
 }
 
+/*
+Serving the new blogpost page. Serves the editor with the method to POST a new document
+*/
+func (c *Controller) ServeNewNavbarItem(ctx *gin.Context) {
+
+	ctx.HTML(200, "navbar_editor", gin.H{
+		"navigation": gin.H{
+			"menu":    c.database.GetDropdownElements(),
+			"headers": c.database.GetNavBarLinks(),
+		},
+		"Post": true,
+	})
+}
+
 /*
 Reciever for the ServeNewBlogPage UI screen. Adds a new document to the database
 */
@@ -409,6 +442,20 @@ func (c *Controller) SaveFile(ctx *gin.Context) {
 	ctx.HTML(200, "upload_status", gin.H{"UpdateMessage": "Update Successful!", "Color": "green"})
 }
 
+// Serve the document deletion template
+func (c *Controller) NavbarOptions(ctx *gin.Context) {
+	id, found := ctx.Params.Get("id")
+	if !found {
+		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": "No ID selected!", "Color": "red"})
+		return
+	}
+
+	ctx.HTML(200, "post_options", gin.H{
+		"Link": fmt.Sprintf("/admin/navbar/%s", id),
+	})
+
+}
+
 // Serve the document deletion template
 func (c *Controller) PostOptions(ctx *gin.Context) {
 	id, found := ctx.Params.Get("id")

+ 2 - 0
pkg/routes/register.go

@@ -43,6 +43,8 @@ func Register(e *gin.Engine, domain string, database storage.DocumentIO, files f
 	priv.PATCH("/posts", c.UpdateBlogPost)
 	priv.DELETE("/posts/:id", c.DeleteDocument)
 	priv.DELETE("/navbar/:item_name", c.RemoveNavbarItem)
+	priv.GET("/navbar/new", c.ServeNewNavbarItem)
+	priv.GET("/navbar/options/:id", c.NavbarOptions)
 	priv.GET("/navbar/all", c.ServeNavbarModify)
 
 }

+ 30 - 4
pkg/storage/storage.go

@@ -57,9 +57,10 @@ type LinkPair struct {
 }
 
 type NavBarItem struct {
-	Png      []byte `json:"png"`
-	Link     string `json:"link"`
-	Redirect string `json:"redirect"`
+	Png      []byte                `json:"png"`
+	File     *multipart.FileHeader `form:"file"`
+	Link     string                `json:"link" form:"link"`
+	Redirect string                `json:"redirect" form:"redirect"`
 }
 
 type Asset struct {
@@ -224,6 +225,26 @@ func (s *SQLiteRepo) GetDropdownElements() []LinkPair {
 
 }
 
+/*
+Retrieve a dropdown element by its text name on the UI
+*/
+func (s *SQLiteRepo) GetDropdownElementByName(text string) (LinkPair, bool) {
+	rows := s.db.QueryRow("SELECT * FROM menu WHERE text = ?", text)
+	var item LinkPair
+	var id int
+	if err := rows.Scan(&id, &item.Link, &item.Text); err != nil {
+		if errors.Is(err, sql.ErrNoRows) {
+			return item, false
+		}
+		log.Fatal(err)
+	}
+	return item, true
+
+}
+
+/*
+Get all nav bar items. Returns a list of NavBarItem structs with the png data, the file name, and the redirect location of the icon
+
 /*
 Get all nav bar items. Returns a list of NavBarItem structs with the png data, the file name, and the redirect location of the icon
 */
@@ -446,6 +467,11 @@ func (s *SQLiteRepo) AddMenuItem(item LinkPair) error {
 	if err != nil {
 		return err
 	}
+	_, found := s.GetDropdownElementByName(item.Text)
+	if found {
+		tx.Rollback()
+		return errors.New("Row exists.")
+	}
 	stmt, _ := tx.Prepare("INSERT INTO menu(link, text) VALUES (?,?)")
 	_, err = stmt.Exec(item.Link, item.Text)
 	if err != nil {
@@ -579,7 +605,7 @@ func (s *SQLiteRepo) DeleteNavbarItem(id Identifier) error {
 	if err != nil {
 		return err
 	}
-	stmt, _ := tx.Prepare("DELETE FROM navbar WHERE redirect=?")
+	stmt, _ := tx.Prepare("DELETE FROM navbar WHERE link=?")
 	_, err = stmt.Exec(id)
 	if err != nil {
 		tx.Rollback()

+ 48 - 0
pkg/webpages/html/navbar_editor.html

@@ -0,0 +1,48 @@
+
+{{ define "patch_navbar_editor" }}
+<form hx-encoding='multipart/form-data' hx-patch='/admin/navbar'
+                 _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
+{{ end }}
+
+{{ define "post_navbar_editor" }}
+<form hx-encoding='multipart/form-data' hx-post='/admin/navbar'
+                 _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
+{{ end }}
+
+
+{{ define "navbar_editor" }}
+<!DOCTYPE html>
+<html lang="en">
+    <div class="container-fluid p-2 position-relative"
+        style="width: 80vw; max-width: 80%; background-color: rgb(22, 22, 22);">
+        <div class="container">
+            <div class="row">
+                <div class="col-sm"></div>
+                <div class="container position-relative">
+                    <a style="color: white; height: fit-content; font-size: xx-large; font-weight: bold; font-family: monospace;">Editing/Making navbar item</a>
+                    <div class="col m-5">
+                        {{ if $.Post }}
+                            {{ template "post_navbar_editor" }}
+                        {{ else }}
+                            {{ template "patch_navbar_editor" }}
+                        {{ end }}
+                            <div class="row"
+                                style="background-color: rgb(22, 22, 22); color: white; height: fit-content; font-size: larger; font-family: monospace;">
+                                <a>Redirect to:</a>
+                                <textarea name="redirect"
+                                    style="background-color: rgb(73, 73, 73); color: white;">{{ .Redirect }}</textarea>
+                            </div>
+                            <div class="row container p-2 m-2">
+                                <a>Add PNG:</a>
+                                <input type='file' name='file'>
+                            </div>
+                            <button type="submit">Send</button><div id="response"></div>
+                        </form>
+                    </div>
+                </div>
+                <div class="col-sm"></div>
+            </div>
+        </div>
+    </div>
+</html>
+{{ end }}

+ 2 - 1
scripts/seed.py

@@ -15,7 +15,8 @@ URL = os.getenv("SITE_URL")
 admin_table = {
         "new": {
             "blog post": "/admin/posts",
-            "digital media": "/admin/upload"
+            "digital media": "/admin/upload",
+            "navbar item": "/admin/navbar/new"
             },
         "modify": {
             "blog post": "/admin/posts/all",