Browse Source

Merge branch 'wip' of aeth/keiji into develop

aeth 2 months ago
parent
commit
ae1a9f1b44

+ 4 - 1
.gitignore

@@ -8,4 +8,7 @@ build/linux/seed/*
 html/assets/images/*
 
 # db seed data
-db_seed/*
+db_seed/*
+
+# ignoring nohup.out file
+nohup.out

+ 1 - 1
Makefile

@@ -23,6 +23,6 @@ build-seed-cmd:
 	go build -o ./build/linux/$(SEED_CMD)/$(SEED_CMD) ./cmd/$(SEED_CMD)/$(SEED_CMD).go
 
 dev-run:
-	go build -ldflags "-X main.WEB_ROOT=/home/aeth/keiji/html" \
+	go build -ldflags "-X main.WEB_ROOT=$(WEB_ROOT)" \
 	-o ./build/linux/$(WEBSERVER)/$(WEBSERVER) ./cmd/$(WEBSERVER)/$(WEBSERVER).go && \
 	./build/linux/$(WEBSERVER)/$(WEBSERVER) .env

+ 16 - 0
cmd/webserver/webserver.go

@@ -84,6 +84,22 @@ func main() {
 		"upload_status",
 		fmt.Sprintf("%s/templates/upload_status.html", WEB_ROOT),
 	)
+	renderer.AddFromFiles(
+		"unhandled_error",
+		fmt.Sprintf("%s/templates/unhandled_error.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/menu.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/link.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/navigation.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/listing.html", WEB_ROOT),
+	)
+	renderer.AddFromFiles(
+		"upload",
+		fmt.Sprintf("%s/templates/upload.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/menu.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/link.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/navigation.html", WEB_ROOT),
+		fmt.Sprintf("%s/templates/listing.html", WEB_ROOT),
+	)
 	e := gin.Default()
 	e.HTMLRender = renderer
 	routes.Register(e, WEB_ROOT, DOMAIN_NAME, REDIS_PORT, REDIS_ADDR)

+ 1 - 0
go.mod

@@ -23,6 +23,7 @@ require (
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.17.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/joho/godotenv v1.5.1 // indirect
 	github.com/josharian/intern v1.0.0 // indirect

+ 2 - 0
go.sum

@@ -55,6 +55,8 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU=
+github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

BIN
html/assets/images/blackandwhitedesign.jpg


BIN
html/assets/images/keepoff_small.jpg


BIN
html/assets/images/someoneatthedoor_small.jpg


BIN
html/assets/images/whyhere.gif


+ 49 - 0
html/css/custom.css

@@ -0,0 +1,49 @@
+ /* The side navigation menu */
+ .sidenav {
+    height: 100%; /* 100% Full-height */
+    width: 0; /* 0 width - change this with JavaScript */
+    position: fixed; /* Stay in place */
+    z-index: 1; /* Stay on top */
+    top: 0; /* Stay at the top */
+    left: 0;
+    background-color: #111; /* Black*/
+    overflow-x: hidden; /* Disable horizontal scroll */
+    padding-top: 60px; /* Place content 60px from the top */
+    transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */
+  }
+  
+  /* The navigation menu links */
+  .sidenav a {
+    padding: 8px 8px 8px 32px;
+    text-decoration: none;
+    font-size: 25px;
+    color: #818181;
+    display: block;
+    transition: 0.3s;
+  }
+  
+  /* When you mouse over the navigation links, change their color */
+  .sidenav a:hover {
+    color: #f1f1f1;
+  }
+  
+  /* Position and style the close button (top right corner) */
+  .sidenav .closebtn {
+    position: absolute;
+    top: 0;
+    right: 25px;
+    font-size: 36px;
+    margin-left: 50px;
+  }
+  
+  /* Style page content - use this if you want to push the page content to the right when you open the side navigation */
+  #main {
+    transition: margin-left .5s;
+    padding: 20px;
+  }
+  
+  /* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */
+  @media screen and (max-height: 450px) {
+    .sidenav {padding-top: 15px;}
+    .sidenav a {font-size: 18px;}
+  } 

File diff suppressed because it is too large
+ 0 - 0
html/htmx/htmx.min.js


+ 12 - 0
html/htmx/json-enc.js

@@ -0,0 +1,12 @@
+htmx.defineExtension('json-enc', {
+    onEvent: function (name, evt) {
+        if (name === "htmx:configRequest") {
+            evt.detail.headers['Content-Type'] = "application/json";
+        }
+    },
+    
+    encodeParameters : function(xhr, parameters, elt) {
+        xhr.overrideMimeType('text/json');
+        return (JSON.stringify(parameters));
+    }
+});

+ 9 - 0
html/js/slide.js

@@ -0,0 +1,9 @@
+/* Set the width of the side navigation to 250px */
+function openNav() {
+    document.getElementById("mySidenav").style.width = "250px";
+  }
+
+  /* Set the width of the side navigation to 0 */
+  function closeNav() {
+    document.getElementById("mySidenav").style.width = "0";
+  }

+ 2 - 3
html/templates/admin.html

@@ -16,7 +16,6 @@
     <div class="container-fluid row">
         {{ range .Tables }}
             <div class="col">
-            
                 <div class="col container h-2 p-2" style="background-color: rgb(22, 22, 22); color: white; height: fit-content; font-size: larger; font-family: monospace;">
                     {{ .TableName }}
                 </div>
@@ -36,8 +35,8 @@
             {{ end }}
     </div>
     <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-    <script src="https://unpkg.com/htmx.org@1.9.4"></script> 
-    <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+    <script src="/api/v1/htmx/htmx.min.js"></script>
+    <script src="/api/v1/htmx/json-enc.js"></script>
 </body>
 </html>
 {{ end }}

+ 2 - 2
html/templates/blogpost.html

@@ -25,8 +25,8 @@
             <div class="col"></div>
         </div>
         <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-        <script src="https://unpkg.com/htmx.org@1.9.4"></script>
-        <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+        <script src="/api/v1/htmx/htmx.min.js"></script>
+        <script src="/api/v1/htmx/json-enc.js"></script>
     </body>
 
 </html>

+ 2 - 2
html/templates/blogpost_editor.html

@@ -57,8 +57,8 @@
         </div>
     </div>
     <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-    <script src="https://unpkg.com/htmx.org@1.9.4"></script>
-    <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+    <script src="/api/v1/htmx/htmx.min.js"></script>
+    <script src="/api/v1/htmx/json-enc.js"></script>
 </body>
 </html>
 {{ end }}

+ 17 - 1
html/templates/centered_image.html

@@ -3,8 +3,24 @@
 <html lang="en">
     <div class="row container-fluid m-0 p-0">
         <div class="col container-fluid" style="background-color: black;"></div>
-        <img src="{{ . }}" loading="lazy" class="img-fluid m-0 p-0 col-auto" style="background-color: black; max-height: 70vh;">
+        <img src="{{ .ApiPath }}" loading="lazy" class="img-fluid m-0 p-0 col-auto" style="background-color: black; max-height: 70vh;">
         <div class="col container-fluid" style="background-color: black;"></div>
     </div>
+    <a href="#">
+        <div class="mask" style="background-color: hsla(0, 0%, 98%, 0.2)">
+            <div class="row align-items-center" style="font-family: monospace; color: white; min-height: 100%;">
+                <div class="col"></div>
+                <div class="col-auto">
+                    <div class="row col-auto p-2 m-2" style="font-size: xx-large;">
+                        '{{ .Title }}'
+                    </div>
+                    <div class="row col-auto p-2 m-2" style="font-size: x-large;">
+                        {{ .Desc }}
+                    </div>
+                </div>
+                <div class="col"></div>
+            </div>
+        </div>
+    </a>
 </html>
 {{ end }}

+ 2 - 5
html/templates/digital_art.html

@@ -17,17 +17,14 @@
                         <div class="row position-relative shadow-lg p-3 m-3 rounded justify-content-center"
                             style="width: 80vh; max-width: 95%; background-color: rgb(22, 22, 22);">
                                 {{ template "centered_image.html" . }}
-                            <a href="#">
-                                <div class="mask" style="background-color: hsla(0, 0%, 98%, 0.2)"></div>
-                            </a>
                         </div>
                     </div>
                 </div>
             {{ end }}
             </div>
         <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-        <script src="https://unpkg.com/htmx.org@1.9.4"></script>
-        <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+        <script src="/api/v1/htmx/htmx.min.js"></script>
+        <script src="/api/v1/htmx/json-enc.js"></script>
     </body>
 </html>
 {{ end }}

+ 22 - 2
html/templates/home.html

@@ -6,6 +6,7 @@
         <meta name="viewport" content="width=device-width, initial-scale=1">
         <link rel="stylesheet" href="/api/v1/style/bootstrap.min.css">
         <link rel="stylesheet" href="/api/v1/style/mdb/mdb.min.css">
+        <link rel="stylesheet" href="/api/v1/style/custom.css">
     </head>
     <body style="background-color: rgb(56, 56, 56);">
         {{ template "navigation.html" .navigation }}
@@ -18,9 +19,28 @@
             </div>
         {{ end }}
         </div>
+
+        <div id="mySidenav" class="sidenav">
+            <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a>
+            <a href="#">About</a>
+            <a href="#">Services</a>
+            <a href="#">Clients</a>
+            <a href="#">Contact</a>
+          </div>
+          <!-- Use any element to open the sidenav -->
+          <span onclick="openNav()">open</span>
+
+          <!-- Add all page content inside this div if you want the side nav to push page content to the right (not used if you only want the sidenav to sit on top of the page -->
+          <div id="main">
+            ...
+          </div>
+
+
+
         <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-        <script src="https://unpkg.com/htmx.org@1.9.4"></script> 
-        <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+        <script src="/api/v1/js/slide.js"></script>
+        <script src="/api/v1/htmx/htmx.min.js"></script>
+        <script src="/api/v1/htmx/json-enc.js"></script>
     </body>
 </html>
 {{ end }}

+ 2 - 2
html/templates/login.html

@@ -39,7 +39,7 @@
             </div>
         </div>
     </body>
-    <script src="https://unpkg.com/htmx.org@1.9.4"></script> 
-    <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+    <script src="/api/v1/htmx/htmx.min.js"></script>
+    <script src="/api/v1/htmx/json-enc.js"></script>
 </html>
 {{ end }}

+ 2 - 2
html/templates/new_blogpost.html

@@ -57,8 +57,8 @@
         </div>
     </div>
     <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-    <script src="https://unpkg.com/htmx.org@1.9.4"></script>
-    <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
+    <script src="/api/v1/htmx/htmx.min.js"></script>
+    <script src="/api/v1/htmx/json-enc.js"></script>
 </body>
 </html>
 {{ end }}

+ 19 - 0
html/templates/unhandled_error.html

@@ -0,0 +1,19 @@
+{{ define "unhandled_error.html" }}
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <link rel="stylesheet" href="/api/v1/style/bootstrap.min.css">
+        <link rel="stylesheet" href="/api/v1/style/mdb/mdb.min.css">
+    </head>
+    <body style="background-color: rgb(56, 56, 56);">
+        {{ template "navigation.html" .navigation }}
+        <div class="col container-fluid" style="max-width: 80vw; background-color: rgb(22, 22, 22);">
+            <a style="color: red; height: fit-content; font-size: larger; font-family: monospace;">
+                STATUS: {{ .StatusCode }}. UNHANDLED EXCEPTION RAISED: {{ .Reason }}
+            </a>
+        </div>
+    </body>
+</html>
+{{ end }}

+ 36 - 18
html/templates/upload.html

@@ -1,24 +1,42 @@
 {{ define "upload.html" }}
 <!DOCTYPE html>
 <html lang="en">
-        <form hx-encoding='multipart/form-data' hx-post='/admin/images/upload'
-             _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
-                <div class="row container p-2 m-2">
-                    <input type='file' name='file'>
-                </div>
-                <div class="row container p-2 m-2">
-                    <textarea name="description" wrap="soft" required="required" placeholder="Name of the piece?"></textarea>
-                </div>
-                <div class="row container p-2 m-2">
-                    <textarea name="description" wrap="soft" required="required" placeholder="What would you like to say about it?"></textarea>
-                </div>
-                <div class="row container p-2 m-2">
-                    <button>
-                       Upload
-                    </button>
-                    <progress id='progress' value='0' max='100'></progress>
-                </div>
-        </form>
+<head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="/api/v1/style/bootstrap.min.css">
+    <link rel="stylesheet" href="/api/v1/style/mdb/mdb.min.css">
+</head>
+<body style="background-color: rgb(56, 56, 56);">
+    {{ template "navigation.html" .navigation }}
+    <div class="container-fluid row">
+        <div class="col"></div>
+        <div class="col" style="min-width: 80vw; background-color: rgb(22, 22, 22); font-family: monospace;">
+            <form hx-encoding='multipart/form-data' hx-post='/admin/images/upload'
+                 _='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
+                    <div class="row container p-2 m-2">
+                        <input type='file' name='file'>
+                    </div>
+                    <div class="row container p-2 m-2">
+                        <textarea name="title" wrap="soft" required="required" placeholder="Name of the piece?"></textarea>
+                    </div>
+                    <div class="row container p-2 m-2">
+                        <textarea name="description" wrap="soft" required="required" placeholder="What would you like to say about it?"></textarea>
+                    </div>
+                    <div class="row container p-2 m-2">
+                        <button>
+                           Upload
+                        </button>
+                        <progress id='progress' value='0' max='100'></progress>
+                    </div>
+            </form>
+        </div>
+        <div class="col"></div>
+    </div>
+    <script src="/api/v1/htmx/htmx.min.js"></script>
+    <script src="/api/v1/htmx/json-enc.js"></script>
+    <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+    </body>
 </html>
 
 {{ end }}

+ 8 - 7
pkg/controller/admin_handlers.go

@@ -19,15 +19,16 @@ func (c *Controller) AddDocument(ctx *gin.Context) {
 
 	var upload helpers.DocumentUpload
 
-	err := ctx.BindJSON(&upload); if err != nil {
+	err := ctx.BindJSON(&upload)
+	if err != nil {
 		ctx.JSON(400, map[string]string{
 			"Error": err.Error(),
 		})
 		return
 	}
-
-	doc := helpers.NewDocument(upload.Name, nil, upload.Text, upload.Category)
-	err = helpers.AddDocument(doc, c.RedisConfig); if err != nil {
+	newPost := helpers.NewDocument(upload.Name, nil, upload.Text, upload.Category)
+	err = helpers.AddDocument(newPost, c.RedisConfig)
+	if err != nil {
 		ctx.JSON(400, map[string]string{
 			"Error": err.Error(),
 		})
@@ -60,7 +61,8 @@ func (c *Controller) Auth(ctx *gin.Context) {
 
 	var cred helpers.Credentials
 
-	err := ctx.ShouldBind(&cred); if err != nil {
+	err := ctx.ShouldBind(&cred)
+	if err != nil {
 		ctx.JSON(400, map[string]string{
 			"Error": err.Error(),
 		})
@@ -78,7 +80,6 @@ func (c *Controller) Auth(ctx *gin.Context) {
 
 }
 
-
 // @Name AdminPanel
 // @Summary serve the admin panel page
 // @Tags admin
@@ -88,7 +89,7 @@ func (c *Controller) AdminPanel(ctx *gin.Context) {
 	ctx.HTML(http.StatusOK, "admin", gin.H{
 		"navigation": gin.H{
 			"headers": c.Headers().Elements,
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 		},
 		"Tables": c.AdminTables().Tables,
 	})

+ 62 - 0
pkg/controller/cdn_handlers.go

@@ -2,6 +2,7 @@ package controller
 
 import (
 	"fmt"
+	"strings"
 	"os"
 
 	"git.aetherial.dev/aeth/keiji/pkg/helpers"
@@ -80,6 +81,32 @@ func (c *Controller) ServeMdbCss(ctx *gin.Context) {
 
 }
 
+
+// @Name ServeHtmx
+// @Summary serves some htmx assets
+// @Tags cdn
+// @Param file path string true "The JS file to serve to the client"
+// @Router /api/v1/htmx/{file} [get]
+func (c *Controller) ServeHtmx(ctx *gin.Context) {
+	f, exist := ctx.Params.Get("file")
+	if !exist {
+		ctx.JSON(404, map[string]string{
+			"Error": "the requested file could not be found",
+		})
+	}
+	css := fmt.Sprintf("%s/htmx/%s", c.WebRoot, f)
+	b, err := os.ReadFile(css)
+	if err != nil {
+		ctx.JSON(500, map[string]string{
+			"Error": "Could not serve the requested file",
+			"msg":   err.Error(),
+		})
+	}
+	ctx.Data(200, "text/javascript", b)
+
+}
+
+
 // @Name ServeAsset
 // @Summary serves assets to put in a webpage
 // @Tags cdn
@@ -124,3 +151,38 @@ func (c *Controller) ServeImage(ctx *gin.Context) {
 	ctx.Data(200, "image/jpeg", b)
 }
 
+// @Name ServeGeneric
+// @Summary serves file from the html file
+// @Tags cdn
+// @Router /cdn/{file} [get]
+func (c *Controller) ServeGeneric(ctx *gin.Context) {
+	f, exist := ctx.Params.Get("file")
+	if !exist {
+		ctx.JSON(404, map[string]string{
+			"Error": "the requested file could not be found",
+		})
+		return
+	}
+	fext := strings.Split(f, ".")[len(strings.Split(f, "."))-1]
+	var ctype string
+	switch {
+	case fext == "css":
+		ctype = "text/css"
+	case fext == "js":
+		ctype = "text/javascript"
+	case fext == "json":
+		ctype = "application/json"
+	default:
+		ctype = "text"
+	}
+	b, err := os.ReadFile(f)
+	if err != nil {
+		ctx.JSON(500, map[string]string{
+			"Error": "Could not serve the requested file",
+			"msg":   err.Error(),
+		})
+		return
+	}
+	ctx.Data(200, ctype, b)
+}
+

+ 32 - 8
pkg/controller/content_handlers.go

@@ -1,7 +1,6 @@
 package controller
 
 import (
-	"fmt"
 	"os"
 	"time"
 
@@ -24,7 +23,7 @@ func (c *Controller) ServeBlogDirectory(ctx *gin.Context) {
 
 
 func (c *Controller) GetBlogPostEditor(ctx *gin.Context) {
-	rds := helpers.NewRedisClient(helpers.RedisConf{Addr: os.Getenv("REDIS_ADDR"), Port: os.Getenv("REDIS_PORT")})
+	rds := helpers.NewRedisClient(c.RedisConfig)
 	post, exist := ctx.Params.Get("post-name")
 	if !exist {
 		ctx.JSON(404, map[string]string{
@@ -103,17 +102,42 @@ func (c *Controller) MakeBlogPost(ctx *gin.Context) {
 
 }
 
+
+func (c *Controller) ServeFileUpload(ctx *gin.Context) {
+	ctx.HTML(200, "upload", gin.H{
+		"navigation": gin.H{
+			"menu": c.Menu(),
+			"headers": c.Headers().Elements,
+		},
+	})
+}
+
+
+
 func (c *Controller) SaveFile(ctx *gin.Context) {
-	file, _ := ctx.FormFile("file")
+	file, err := ctx.FormFile("file")
+	if err != nil {
+		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
+		return
+	}
+	var img helpers.ImageStoreItem
+	err = ctx.ShouldBind(&img); if err != nil {
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
+		return
+	}
+	savedImg := helpers.NewImageStoreItem(file.Filename, img.Title, img.Desc)
+	err = c.SaveImage(savedImg); if err != nil {
+		ctx.HTML(500, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
+		return
+	}
+
 
 	// Upload the file to specific dst.
-	err := ctx.SaveUploadedFile(file, fmt.Sprintf("%s/%s", helpers.GetImageStore(), file.Filename))
+	err = ctx.SaveUploadedFile(file, savedImg.AbsolutePath)
 	if err != nil {
-		ctx.JSON(400, map[string]string{
-			"Error": err.Error(),
-		})
+		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
 		return
 	}
 
-	ctx.HTML(200, "upload", nil)
+	ctx.HTML(200, "upload_status", gin.H{"UpdateMessage": "Update Successful!", "Color": "green"})
 }

+ 27 - 0
pkg/controller/controller.go

@@ -14,6 +14,9 @@ type Controller struct{
 	Cache		*helpers.AllCache
 }
 
+/*
+Retrieve the header configuration from redis
+*/
 func (c *Controller) Headers() *helpers.HeaderCollection {
 	headers, err := helpers.GetHeaders(c.RedisConfig)
 	if err != nil {
@@ -22,6 +25,9 @@ func (c *Controller) Headers() *helpers.HeaderCollection {
 	return headers
 }
 
+/*
+Retrieve the menu configuration from redis
+*/
 func (c *Controller) Menu() *helpers.MenuElement {
 	links, err := helpers.GetMenuLinks(c.RedisConfig)
 	if err != nil {
@@ -30,6 +36,9 @@ func (c *Controller) Menu() *helpers.MenuElement {
 	return links
 }
 
+/*
+Retrieve the administrator table configuration from redis
+*/
 func (c *Controller) AdminTables() *helpers.AdminTables {
 	tables, err := helpers.GetAdminTables(c.RedisConfig)
 	if err != nil {
@@ -38,6 +47,10 @@ func (c *Controller) AdminTables() *helpers.AdminTables {
 	return tables
 }
 
+
+/*
+Retrieve the post data and format it for the post management page
+*/
 func (c *Controller) FormatDocTable() *helpers.AdminTables {
 	var postTables helpers.AdminTables
 	for i := range helpers.Topics {
@@ -59,6 +72,20 @@ func (c *Controller) FormatDocTable() *helpers.AdminTables {
 
 }
 
+
+/*
+Save a new image store item
+*/
+func (c *Controller) SaveImage(img *helpers.ImageStoreItem) error {
+	rds := helpers.NewRedisClient(c.RedisConfig)
+	err := rds.AddImage(img)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+
 func NewController(root string, domain string, redisPort string, redisAddr string) *Controller {
 	return &Controller{WebRoot: root, Cache: helpers.NewCache(),
 								Domain: domain, RedisConfig: helpers.RedisConf{

+ 22 - 17
pkg/controller/html_handlers.go

@@ -1,7 +1,7 @@
 package controller
 
 import (
-	"fmt"
+	"html/template"
 	"net/http"
 
 	"git.aetherial.dev/aeth/keiji/pkg/helpers"
@@ -34,14 +34,13 @@ func (c *Controller) ServePost(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "blogpost", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 		},
-		"title": doc.Ident,
-		"Ident": doc.Ident,
+		"title":   doc.Ident,
+		"Ident":   doc.Ident,
 		"Created": doc.Created,
-		"Body": doc.Body,
-
+		"Body":    template.HTML(helpers.MdToHTML([]byte(doc.Body))),
 	})
 
 }
@@ -60,14 +59,13 @@ func (c *Controller) ServeBlogHome(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 		},
 		"listings": docs,
 	})
 }
 
-
 // @Name ServeHtml
 // @Summary serves HTML files out of the HTML directory
 // @Tags webpages
@@ -82,7 +80,7 @@ func (c *Controller) ServeHome(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 		},
 		"listings": docs,
@@ -103,7 +101,7 @@ func (c *Controller) ServeCreativeWriting(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 		},
 		"listings": docs,
@@ -125,7 +123,7 @@ func (c *Controller) ServeTechnicalWriteups(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 		},
 		"listings": docs,
@@ -138,15 +136,22 @@ func (c *Controller) ServeTechnicalWriteups(ctx *gin.Context) {
 // @Tags webpages
 // @Router /digital [get]
 func (c *Controller) ServeDigitalArt(ctx *gin.Context) {
+	rds := helpers.NewRedisClient(c.RedisConfig)
+	fnames, err := helpers.GetImageData(rds)
+	if err != nil {
+		ctx.HTML(http.StatusInternalServerError, "unhandled_error",
+			gin.H{
+				"StatusCode": http.StatusInternalServerError,
+				"Reason":     err.Error(),
+			},
+		)
+		return
+	}
 	ctx.HTML(http.StatusOK, "digital_art", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 		},
-		"images": []string{
-			fmt.Sprintf("/api/v1/images/%s", "keepoff_small.jpg"),
-			fmt.Sprintf("/api/v1/images/%s", "someoneatthedoor_small.jpg"),
-			fmt.Sprintf("/api/v1/images/%s", "whyhere.gif"),
-		},
+		"images": fnames,
 	})
 }

+ 1 - 5
pkg/helpers/auth.go

@@ -43,11 +43,7 @@ func (c *AllCache) update(id string, cookie string) {
 
 func (c *AllCache) Read(id string) bool {
     _, ok := c.AuthCookies.Get(id)
-    if ok {
-
-        return true
-    }
-    return false
+	return ok
 }
 
 

+ 57 - 44
pkg/helpers/helpers.go

@@ -2,11 +2,13 @@ package helpers
 
 import (
 	"encoding/json"
-	"fmt"
-	"os"
+
 	"strings"
 	"time"
 
+	"github.com/gomarkdown/markdown"
+	"github.com/gomarkdown/markdown/html"
+	"github.com/gomarkdown/markdown/parser"
 	"github.com/redis/go-redis/v9"
 )
 
@@ -28,83 +30,81 @@ var Topics = []string{
 
 var TopicMap = map[string]string{
 	TECHNICAL: TECHNICAL,
-	BLOG: BLOG,
-	CREATIVE: CREATIVE,
+	BLOG:      BLOG,
+	CREATIVE:  CREATIVE,
 }
 
 type HeaderCollection struct {
-	Category	string		`json:"category"`
-	Elements	[]HeaderElem	`json:"elements"`
+	Category string       `json:"category"`
+	Elements []HeaderElem `json:"elements"`
 }
 
 type HeaderElem struct {
-	Png		string	`json:"png"`
-	Link	string	`json:"link"`
+	Png  string `json:"png"`
+	Link string `json:"link"`
 }
 
 type ImageElement struct {
-	ImgUrl	string	`json:"img_url"`
+	ImgUrl string `json:"img_url"`
 }
 
 type MenuElement struct {
-	Png		string	`json:"png"`
-	Category	string	`json:"category"`
-	MenuLinks	[]MenuLinkPair	`json:"menu_links"`
+	Png       string         `json:"png"`
+	Category  string         `json:"category"`
+	MenuLinks []MenuLinkPair `json:"menu_links"`
 }
 
 type MenuLinkPair struct {
-	MenuLink	string	`json:"menu_link"`
-	LinkText 	string	`json:"link_text"`
+	MenuLink string `json:"menu_link"`
+	LinkText string `json:"link_text"`
 }
 
 type Document struct {
-	Ident	string	`json:"identifier"`
-	Created	string	`json:"created"`
-	Body	string	`json:"body"`
-	Category	string	`json:"category"`
-	Sample  string
+	Ident    string `json:"identifier"`
+	Created  string `json:"created"`
+	Body     string `json:"body"`
+	Category string `json:"category"`
+	Sample   string
 }
 
 type AdminTables struct {
-	Tables		[]Table	`json:"tables"`
+	Tables []Table `json:"tables"`
 }
 
 type Table struct {
-	TableName	string	`json:"table_name"`
-	TableData	[]TableData	`json:"table_data"`
+	TableName string      `json:"table_name"`
+	TableData []TableData `json:"table_data"`
 }
 
 type TableData struct {
-	DisplayName		string	`json:"display_name"`
-	Link			string	`json:"link"`
+	DisplayName string `json:"display_name"`
+	Link        string `json:"link"`
 }
 
-
 func NewDocument(ident string, created *time.Time, body string, category string) Document {
-	
+
 	var ts time.Time
 	if created == nil {
 		rn := time.Now()
 		ts = time.Date(rn.Year(), rn.Month(), rn.Day(), rn.Hour(), rn.Minute(),
-						rn.Second(), rn.Nanosecond(), rn.Location())
+			rn.Second(), rn.Nanosecond(), rn.Location())
 	} else {
 		ts = *created
 	}
-	
+
 	return Document{Ident: ident, Created: ts.String(), Body: body, Category: category}
 }
 
 type DocumentUpload struct {
-	Name	string	`json:"name"`
-	Category	string	`json:"category"`
-	Text	string	`json:"text"`
+	Name     string `json:"name"`
+	Category string `json:"category"`
+	Text     string `json:"text"`
 }
 
-
 type HeaderIo interface {
 	GetHeaders() (*HeaderCollection, error)
 	AddHeaders(HeaderCollection) error
-	GetMenuLinks()	(*MenuElement, error)
+	GetMenuLinks() (*MenuElement, error)
 }
 
 /*
@@ -120,7 +120,8 @@ func GetHeaders(redisCfg RedisConf) (*HeaderCollection, error) {
 		return nil, err
 	}
 	header := &HeaderCollection{}
-	err = json.Unmarshal([]byte(d), header); if err != nil {
+	err = json.Unmarshal([]byte(d), header)
+	if err != nil {
 		return nil, err
 	}
 	return header, nil
@@ -139,7 +140,8 @@ func GetMenuLinks(redisCfg RedisConf) (*MenuElement, error) {
 		return nil, err
 	}
 	header := &MenuElement{}
-	err = json.Unmarshal([]byte(d), header); if err != nil {
+	err = json.Unmarshal([]byte(d), header)
+	if err != nil {
 		return nil, err
 	}
 	return header, nil
@@ -158,7 +160,8 @@ func GetAdminTables(redisCfg RedisConf) (*AdminTables, error) {
 		return nil, err
 	}
 	tables := &AdminTables{}
-	err = json.Unmarshal([]byte(d), tables); if err != nil {
+	err = json.Unmarshal([]byte(d), tables)
+	if err != nil {
 		return nil, err
 	}
 	return tables, nil
@@ -199,33 +202,29 @@ func (d *Document) MakeSample() string {
 
 /*
 Retrieve all documents from the category specified in the argument category
+
 	:param category: the category to get documents from
 */
 func GetAllDocuments(category string, redisCfg RedisConf) ([]*Document, error) {
 	rdc := NewRedisClient(redisCfg)
-	fmt.Fprintf(os.Stdout, "%+v\n", redisCfg)
 	ids, err := rdc.AllDocIds()
 	if err != nil {
-		fmt.Fprint(os.Stdout, "failed 1")
 		return nil, err
 	}
 	var docs []*Document
 	for idx := range ids {
 		doc, err := rdc.GetItem(ids[idx])
 		if err != nil {
-			fmt.Fprint(os.Stdout, "failed 2")
 			return nil, err
 		}
 		if doc.Category != category {
 			continue
 		}
-		
 		docs = append(docs, &Document{
-			Ident: doc.Ident,
+			Ident:   doc.Ident,
 			Created: doc.Created,
-			Body: doc.Body,
-			Sample: doc.MakeSample(),
-
+			Body:    doc.Body,
+			Sample:  doc.MakeSample(),
 		})
 	}
 	return docs, nil
@@ -240,4 +239,18 @@ func AddDocument(d Document, redisCfg RedisConf) error {
 	return rdc.AddDoc(d)
 }
 
+/*
+	 convert markdown to html
+		:param md: the byte array containing the Markdown to convert
+*/
+func MdToHTML(md []byte) []byte {
+	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
+	p := parser.NewWithExtensions(extensions)
+	doc := p.Parse(md)
+
+	htmlFlags := html.CommonFlags | html.HrefTargetBlank
+	opts := html.RendererOptions{Flags: htmlFlags}
+	renderer := html.NewRenderer(opts)
 
+	return markdown.Render(doc, renderer)
+}

+ 45 - 0
pkg/helpers/redis.go

@@ -92,8 +92,29 @@ func (r *RedisCaller) AddDoc(doc Document) error {
         return err
     }
 	return &DocAlreadyExists{Key: doc.Ident, Value: val}
+}
 
 
+/*
+Add an image to the image store
+	:param img: an ImageStoreItem struct with the appropriate metadata
+*/
+func (r *RedisCaller) AddImage(img *ImageStoreItem) error {
+	val, err := r.Client.Get(r.ctx, img.Identifier).Result()
+	if err == redis.Nil {
+		data, err := json.Marshal(img)
+		if err != nil {
+			return err
+		}
+		err = r.Client.Set(r.ctx, img.Identifier, data, 0).Err()
+		if err != nil {
+			return err
+		}
+		return nil
+    } else if err != nil {
+        return err
+    }
+	return &DocAlreadyExists{Key: img.Identifier, Value: val}
 }
 
 
@@ -118,6 +139,30 @@ func (r *RedisCaller) GetItem(id string) (*Document, error) {
 	return &doc, nil
 }
 
+/*
+Retrieve all redis items by category. Returns all the IDs of items that belong to that category
+	:param category: the category to filter by
+*/
+func (r *RedisCaller) GetByCategory(category string) ([]string, error) {
+	ids, err := r.AllDocIds()
+	if err != nil {
+		return nil, err
+	}
+	var matches []string
+	for i := range ids {
+		item, err := r.GetItem(ids[i])
+		if err != nil {
+			return nil, err
+		}
+		if item.Category == category {
+			matches = append(matches, ids[i])
+		}
+
+	}
+	return matches, nil
+}
+
+
 /*
 Delete the target document in redis
 	:param id: the id to delete from redis

+ 75 - 0
pkg/helpers/storage.go

@@ -1,15 +1,90 @@
 package helpers
 
 import (
+	"encoding/json"
+	"fmt"
 	"os"
+	"time"
 
 	"git.aetherial.dev/aeth/keiji/pkg/env"
+	"github.com/google/uuid"
+	"github.com/redis/go-redis/v9"
 )
 
+type InvalidSkipArg struct {Skip int}
+
+func (i *InvalidSkipArg) Error() string {
+	return fmt.Sprintf("Invalid skip amount was passed: %v", i.Skip)
+}
 
 
+type ImageStoreItem struct {
+	Identifier		string	`json:"identifier"`
+	Filename		string	`json:"filename"`
+	AbsolutePath	string	`json:"absolute_path"`
+	Title			string	`json:"title" form:"title"`
+	Created			string	`json:"created"`
+	Desc			string	`json:"description" form:"description"`
+	Category		string	`json:"category"`
+	ApiPath			string
+}
+
+/*
+Create a new ImageStoreItem
+	:param fname: the name of the file to be saved
+	:param title: the canonical title to give the image
+	:param desc: the description to associate to the image
+*/
+func NewImageStoreItem(fname string, title string, desc string) *ImageStoreItem {
+	id := uuid.New()
+	img := ImageStoreItem{
+		Identifier: id.String(),
+		Filename: fname,
+		Title: title,
+		Category: DIGITAL_ART,
+		AbsolutePath: fmt.Sprintf("%s/%s", GetImageStore(), fname),
+		Created: time.Now().UTC().String(),
+		Desc: desc,
+	}
+	return &img
+}
 
+
+/*
+Function to return the location of the image store. Wrapping the env call in
+a function so that refactoring is easier
+*/
 func GetImageStore() string {
 	return os.Getenv(env.IMAGE_STORE)
 }
 
+/*
+Return database entries of the images that exist in the imagestore
+	:param rds: pointer to a RedisCaller to perform the lookups with
+*/
+func GetImageData(rds *RedisCaller) ([]*ImageStoreItem, error) {
+	ids, err := rds.GetByCategory(DIGITAL_ART)
+	if err != nil {
+		return nil, err
+	}
+
+	var imageEntries []*ImageStoreItem
+	for i := range ids {
+		val, err := rds.Client.Get(rds.ctx, ids[i]).Result()
+		if err == redis.Nil {
+			return nil, err
+		} else if err != nil {
+			return nil, err
+		}
+		data := []byte(val)
+		var imageEntry ImageStoreItem
+		err = json.Unmarshal(data, &imageEntry)
+		if err != nil {
+			return nil, err
+		}
+		imageEntry.ApiPath = fmt.Sprintf("/api/v1/images/%s", imageEntry.Filename)
+		imageEntries = append(imageEntries, &imageEntry)
+	}
+	return imageEntries, err
+}
+

+ 4 - 0
pkg/routes/register.go

@@ -25,11 +25,15 @@ func Register(e *gin.Engine, root string, domain string, redisPort string, redis
 	cdn.GET("/style/mdb/:file", c.ServeMdbCss)
 	cdn.GET("/assets/:file", c.ServeAsset)
 	cdn.GET("/images/:file", c.ServeImage)
+	cdn.GET("/cdn/:file", c.ServeGeneric)
+	cdn.GET("/htmx/:file", c.ServeHtmx)
 
 
 
 	priv := e.Group("/admin")
 	priv.Use(c.IsAuthenticated)
+	priv.GET("/upload", c.ServeFileUpload)
+	priv.POST("/upload", c.SaveFile)
 	priv.GET("/panel", c.AdminPanel)
 	priv.POST("/add-document", c.AddDocument)
 	priv.POST("/images/upload", c.SaveFile)

Some files were not shown because too many files changed in this diff