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/*
 html/assets/images/*
 
 
 # db seed data
 # 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
 	go build -o ./build/linux/$(SEED_CMD)/$(SEED_CMD) ./cmd/$(SEED_CMD)/$(SEED_CMD).go
 
 
 dev-run:
 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 && \
 	-o ./build/linux/$(WEBSERVER)/$(WEBSERVER) ./cmd/$(WEBSERVER)/$(WEBSERVER).go && \
 	./build/linux/$(WEBSERVER)/$(WEBSERVER) .env
 	./build/linux/$(WEBSERVER)/$(WEBSERVER) .env

+ 16 - 0
cmd/webserver/webserver.go

@@ -84,6 +84,22 @@ func main() {
 		"upload_status",
 		"upload_status",
 		fmt.Sprintf("%s/templates/upload_status.html", WEB_ROOT),
 		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 := gin.Default()
 	e.HTMLRender = renderer
 	e.HTMLRender = renderer
 	routes.Register(e, WEB_ROOT, DOMAIN_NAME, REDIS_PORT, REDIS_ADDR)
 	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/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.17.0 // indirect
 	github.com/go-playground/validator/v10 v10.17.0 // indirect
 	github.com/goccy/go-json v0.10.2 // 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/google/uuid v1.6.0 // indirect
 	github.com/joho/godotenv v1.5.1 // indirect
 	github.com/joho/godotenv v1.5.1 // indirect
 	github.com/josharian/intern v1.0.0 // 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 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 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/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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 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">
     <div class="container-fluid row">
         {{ range .Tables }}
         {{ range .Tables }}
             <div class="col">
             <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;">
                 <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 }}
                     {{ .TableName }}
                 </div>
                 </div>
@@ -36,8 +35,8 @@
             {{ end }}
             {{ end }}
     </div>
     </div>
     <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
     <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>
 </body>
 </html>
 </html>
 {{ end }}
 {{ end }}

+ 2 - 2
html/templates/blogpost.html

@@ -25,8 +25,8 @@
             <div class="col"></div>
             <div class="col"></div>
         </div>
         </div>
         <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
         <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>
     </body>
 
 
 </html>
 </html>

+ 2 - 2
html/templates/blogpost_editor.html

@@ -57,8 +57,8 @@
         </div>
         </div>
     </div>
     </div>
     <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
     <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>
 </body>
 </html>
 </html>
 {{ end }}
 {{ end }}

+ 17 - 1
html/templates/centered_image.html

@@ -3,8 +3,24 @@
 <html lang="en">
 <html lang="en">
     <div class="row container-fluid m-0 p-0">
     <div class="row container-fluid m-0 p-0">
         <div class="col container-fluid" style="background-color: black;"></div>
         <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 class="col container-fluid" style="background-color: black;"></div>
     </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>
 </html>
 {{ end }}
 {{ 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"
                         <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);">
                             style="width: 80vh; max-width: 95%; background-color: rgb(22, 22, 22);">
                                 {{ template "centered_image.html" . }}
                                 {{ template "centered_image.html" . }}
-                            <a href="#">
-                                <div class="mask" style="background-color: hsla(0, 0%, 98%, 0.2)"></div>
-                            </a>
                         </div>
                         </div>
                     </div>
                     </div>
                 </div>
                 </div>
             {{ end }}
             {{ end }}
             </div>
             </div>
         <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
         <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>
     </body>
 </html>
 </html>
 {{ end }}
 {{ end }}

+ 22 - 2
html/templates/home.html

@@ -6,6 +6,7 @@
         <meta name="viewport" content="width=device-width, initial-scale=1">
         <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/bootstrap.min.css">
         <link rel="stylesheet" href="/api/v1/style/mdb/mdb.min.css">
         <link rel="stylesheet" href="/api/v1/style/mdb/mdb.min.css">
+        <link rel="stylesheet" href="/api/v1/style/custom.css">
     </head>
     </head>
     <body style="background-color: rgb(56, 56, 56);">
     <body style="background-color: rgb(56, 56, 56);">
         {{ template "navigation.html" .navigation }}
         {{ template "navigation.html" .navigation }}
@@ -18,9 +19,28 @@
             </div>
             </div>
         {{ end }}
         {{ end }}
         </div>
         </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 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>
     </body>
 </html>
 </html>
 {{ end }}
 {{ end }}

+ 2 - 2
html/templates/login.html

@@ -39,7 +39,7 @@
             </div>
             </div>
         </div>
         </div>
     </body>
     </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>
 </html>
 {{ end }}
 {{ end }}

+ 2 - 2
html/templates/new_blogpost.html

@@ -57,8 +57,8 @@
         </div>
         </div>
     </div>
     </div>
     <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
     <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>
 </body>
 </html>
 </html>
 {{ end }}
 {{ 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" }}
 {{ define "upload.html" }}
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <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>
 </html>
 
 
 {{ end }}
 {{ end }}

+ 8 - 7
pkg/controller/admin_handlers.go

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

+ 62 - 0
pkg/controller/cdn_handlers.go

@@ -2,6 +2,7 @@ package controller
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"strings"
 	"os"
 	"os"
 
 
 	"git.aetherial.dev/aeth/keiji/pkg/helpers"
 	"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
 // @Name ServeAsset
 // @Summary serves assets to put in a webpage
 // @Summary serves assets to put in a webpage
 // @Tags cdn
 // @Tags cdn
@@ -124,3 +151,38 @@ func (c *Controller) ServeImage(ctx *gin.Context) {
 	ctx.Data(200, "image/jpeg", b)
 	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
 package controller
 
 
 import (
 import (
-	"fmt"
 	"os"
 	"os"
 	"time"
 	"time"
 
 
@@ -24,7 +23,7 @@ func (c *Controller) ServeBlogDirectory(ctx *gin.Context) {
 
 
 
 
 func (c *Controller) GetBlogPostEditor(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")
 	post, exist := ctx.Params.Get("post-name")
 	if !exist {
 	if !exist {
 		ctx.JSON(404, map[string]string{
 		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) {
 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.
 	// 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 {
 	if err != nil {
-		ctx.JSON(400, map[string]string{
-			"Error": err.Error(),
-		})
+		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
 		return
 		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
 	Cache		*helpers.AllCache
 }
 }
 
 
+/*
+Retrieve the header configuration from redis
+*/
 func (c *Controller) Headers() *helpers.HeaderCollection {
 func (c *Controller) Headers() *helpers.HeaderCollection {
 	headers, err := helpers.GetHeaders(c.RedisConfig)
 	headers, err := helpers.GetHeaders(c.RedisConfig)
 	if err != nil {
 	if err != nil {
@@ -22,6 +25,9 @@ func (c *Controller) Headers() *helpers.HeaderCollection {
 	return headers
 	return headers
 }
 }
 
 
+/*
+Retrieve the menu configuration from redis
+*/
 func (c *Controller) Menu() *helpers.MenuElement {
 func (c *Controller) Menu() *helpers.MenuElement {
 	links, err := helpers.GetMenuLinks(c.RedisConfig)
 	links, err := helpers.GetMenuLinks(c.RedisConfig)
 	if err != nil {
 	if err != nil {
@@ -30,6 +36,9 @@ func (c *Controller) Menu() *helpers.MenuElement {
 	return links
 	return links
 }
 }
 
 
+/*
+Retrieve the administrator table configuration from redis
+*/
 func (c *Controller) AdminTables() *helpers.AdminTables {
 func (c *Controller) AdminTables() *helpers.AdminTables {
 	tables, err := helpers.GetAdminTables(c.RedisConfig)
 	tables, err := helpers.GetAdminTables(c.RedisConfig)
 	if err != nil {
 	if err != nil {
@@ -38,6 +47,10 @@ func (c *Controller) AdminTables() *helpers.AdminTables {
 	return tables
 	return tables
 }
 }
 
 
+
+/*
+Retrieve the post data and format it for the post management page
+*/
 func (c *Controller) FormatDocTable() *helpers.AdminTables {
 func (c *Controller) FormatDocTable() *helpers.AdminTables {
 	var postTables helpers.AdminTables
 	var postTables helpers.AdminTables
 	for i := range helpers.Topics {
 	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 {
 func NewController(root string, domain string, redisPort string, redisAddr string) *Controller {
 	return &Controller{WebRoot: root, Cache: helpers.NewCache(),
 	return &Controller{WebRoot: root, Cache: helpers.NewCache(),
 								Domain: domain, RedisConfig: helpers.RedisConf{
 								Domain: domain, RedisConfig: helpers.RedisConf{

+ 22 - 17
pkg/controller/html_handlers.go

@@ -1,7 +1,7 @@
 package controller
 package controller
 
 
 import (
 import (
-	"fmt"
+	"html/template"
 	"net/http"
 	"net/http"
 
 
 	"git.aetherial.dev/aeth/keiji/pkg/helpers"
 	"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{
 	ctx.HTML(http.StatusOK, "blogpost", gin.H{
 		"navigation": gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 			"headers": c.Headers().Elements,
 		},
 		},
-		"title": doc.Ident,
-		"Ident": doc.Ident,
+		"title":   doc.Ident,
+		"Ident":   doc.Ident,
 		"Created": doc.Created,
 		"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{
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 			"headers": c.Headers().Elements,
 		},
 		},
 		"listings": docs,
 		"listings": docs,
 	})
 	})
 }
 }
 
 
-
 // @Name ServeHtml
 // @Name ServeHtml
 // @Summary serves HTML files out of the HTML directory
 // @Summary serves HTML files out of the HTML directory
 // @Tags webpages
 // @Tags webpages
@@ -82,7 +80,7 @@ func (c *Controller) ServeHome(ctx *gin.Context) {
 	}
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 			"headers": c.Headers().Elements,
 		},
 		},
 		"listings": docs,
 		"listings": docs,
@@ -103,7 +101,7 @@ func (c *Controller) ServeCreativeWriting(ctx *gin.Context) {
 	}
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 			"headers": c.Headers().Elements,
 		},
 		},
 		"listings": docs,
 		"listings": docs,
@@ -125,7 +123,7 @@ func (c *Controller) ServeTechnicalWriteups(ctx *gin.Context) {
 	}
 	}
 	ctx.HTML(http.StatusOK, "home", gin.H{
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 			"headers": c.Headers().Elements,
 		},
 		},
 		"listings": docs,
 		"listings": docs,
@@ -138,15 +136,22 @@ func (c *Controller) ServeTechnicalWriteups(ctx *gin.Context) {
 // @Tags webpages
 // @Tags webpages
 // @Router /digital [get]
 // @Router /digital [get]
 func (c *Controller) ServeDigitalArt(ctx *gin.Context) {
 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{
 	ctx.HTML(http.StatusOK, "digital_art", gin.H{
 		"navigation": gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
+			"menu":    c.Menu(),
 			"headers": c.Headers().Elements,
 			"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 {
 func (c *AllCache) Read(id string) bool {
     _, ok := c.AuthCookies.Get(id)
     _, 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 (
 import (
 	"encoding/json"
 	"encoding/json"
-	"fmt"
-	"os"
+
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	"github.com/gomarkdown/markdown"
+	"github.com/gomarkdown/markdown/html"
+	"github.com/gomarkdown/markdown/parser"
 	"github.com/redis/go-redis/v9"
 	"github.com/redis/go-redis/v9"
 )
 )
 
 
@@ -28,83 +30,81 @@ var Topics = []string{
 
 
 var TopicMap = map[string]string{
 var TopicMap = map[string]string{
 	TECHNICAL: TECHNICAL,
 	TECHNICAL: TECHNICAL,
-	BLOG: BLOG,
-	CREATIVE: CREATIVE,
+	BLOG:      BLOG,
+	CREATIVE:  CREATIVE,
 }
 }
 
 
 type HeaderCollection struct {
 type HeaderCollection struct {
-	Category	string		`json:"category"`
-	Elements	[]HeaderElem	`json:"elements"`
+	Category string       `json:"category"`
+	Elements []HeaderElem `json:"elements"`
 }
 }
 
 
 type HeaderElem struct {
 type HeaderElem struct {
-	Png		string	`json:"png"`
-	Link	string	`json:"link"`
+	Png  string `json:"png"`
+	Link string `json:"link"`
 }
 }
 
 
 type ImageElement struct {
 type ImageElement struct {
-	ImgUrl	string	`json:"img_url"`
+	ImgUrl string `json:"img_url"`
 }
 }
 
 
 type MenuElement struct {
 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 {
 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 {
 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 {
 type AdminTables struct {
-	Tables		[]Table	`json:"tables"`
+	Tables []Table `json:"tables"`
 }
 }
 
 
 type Table struct {
 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 {
 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 {
 func NewDocument(ident string, created *time.Time, body string, category string) Document {
-	
+
 	var ts time.Time
 	var ts time.Time
 	if created == nil {
 	if created == nil {
 		rn := time.Now()
 		rn := time.Now()
 		ts = time.Date(rn.Year(), rn.Month(), rn.Day(), rn.Hour(), rn.Minute(),
 		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 {
 	} else {
 		ts = *created
 		ts = *created
 	}
 	}
-	
+
 	return Document{Ident: ident, Created: ts.String(), Body: body, Category: category}
 	return Document{Ident: ident, Created: ts.String(), Body: body, Category: category}
 }
 }
 
 
 type DocumentUpload struct {
 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 {
 type HeaderIo interface {
 	GetHeaders() (*HeaderCollection, error)
 	GetHeaders() (*HeaderCollection, error)
 	AddHeaders(HeaderCollection) error
 	AddHeaders(HeaderCollection) error
-	GetMenuLinks()	(*MenuElement, error)
+	GetMenuLinks() (*MenuElement, error)
 }
 }
 
 
 /*
 /*
@@ -120,7 +120,8 @@ func GetHeaders(redisCfg RedisConf) (*HeaderCollection, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	header := &HeaderCollection{}
 	header := &HeaderCollection{}
-	err = json.Unmarshal([]byte(d), header); if err != nil {
+	err = json.Unmarshal([]byte(d), header)
+	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	return header, nil
 	return header, nil
@@ -139,7 +140,8 @@ func GetMenuLinks(redisCfg RedisConf) (*MenuElement, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	header := &MenuElement{}
 	header := &MenuElement{}
-	err = json.Unmarshal([]byte(d), header); if err != nil {
+	err = json.Unmarshal([]byte(d), header)
+	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	return header, nil
 	return header, nil
@@ -158,7 +160,8 @@ func GetAdminTables(redisCfg RedisConf) (*AdminTables, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	tables := &AdminTables{}
 	tables := &AdminTables{}
-	err = json.Unmarshal([]byte(d), tables); if err != nil {
+	err = json.Unmarshal([]byte(d), tables)
+	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	return tables, nil
 	return tables, nil
@@ -199,33 +202,29 @@ func (d *Document) MakeSample() string {
 
 
 /*
 /*
 Retrieve all documents from the category specified in the argument category
 Retrieve all documents from the category specified in the argument category
+
 	:param category: the category to get documents from
 	:param category: the category to get documents from
 */
 */
 func GetAllDocuments(category string, redisCfg RedisConf) ([]*Document, error) {
 func GetAllDocuments(category string, redisCfg RedisConf) ([]*Document, error) {
 	rdc := NewRedisClient(redisCfg)
 	rdc := NewRedisClient(redisCfg)
-	fmt.Fprintf(os.Stdout, "%+v\n", redisCfg)
 	ids, err := rdc.AllDocIds()
 	ids, err := rdc.AllDocIds()
 	if err != nil {
 	if err != nil {
-		fmt.Fprint(os.Stdout, "failed 1")
 		return nil, err
 		return nil, err
 	}
 	}
 	var docs []*Document
 	var docs []*Document
 	for idx := range ids {
 	for idx := range ids {
 		doc, err := rdc.GetItem(ids[idx])
 		doc, err := rdc.GetItem(ids[idx])
 		if err != nil {
 		if err != nil {
-			fmt.Fprint(os.Stdout, "failed 2")
 			return nil, err
 			return nil, err
 		}
 		}
 		if doc.Category != category {
 		if doc.Category != category {
 			continue
 			continue
 		}
 		}
-		
 		docs = append(docs, &Document{
 		docs = append(docs, &Document{
-			Ident: doc.Ident,
+			Ident:   doc.Ident,
 			Created: doc.Created,
 			Created: doc.Created,
-			Body: doc.Body,
-			Sample: doc.MakeSample(),
-
+			Body:    doc.Body,
+			Sample:  doc.MakeSample(),
 		})
 		})
 	}
 	}
 	return docs, nil
 	return docs, nil
@@ -240,4 +239,18 @@ func AddDocument(d Document, redisCfg RedisConf) error {
 	return rdc.AddDoc(d)
 	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 err
     }
     }
 	return &DocAlreadyExists{Key: doc.Ident, Value: val}
 	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
 	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
 Delete the target document in redis
 	:param id: the id to delete from redis
 	:param id: the id to delete from redis

+ 75 - 0
pkg/helpers/storage.go

@@ -1,15 +1,90 @@
 package helpers
 package helpers
 
 
 import (
 import (
+	"encoding/json"
+	"fmt"
 	"os"
 	"os"
+	"time"
 
 
 	"git.aetherial.dev/aeth/keiji/pkg/env"
 	"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 {
 func GetImageStore() string {
 	return os.Getenv(env.IMAGE_STORE)
 	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("/style/mdb/:file", c.ServeMdbCss)
 	cdn.GET("/assets/:file", c.ServeAsset)
 	cdn.GET("/assets/:file", c.ServeAsset)
 	cdn.GET("/images/:file", c.ServeImage)
 	cdn.GET("/images/:file", c.ServeImage)
+	cdn.GET("/cdn/:file", c.ServeGeneric)
+	cdn.GET("/htmx/:file", c.ServeHtmx)
 
 
 
 
 
 
 	priv := e.Group("/admin")
 	priv := e.Group("/admin")
 	priv.Use(c.IsAuthenticated)
 	priv.Use(c.IsAuthenticated)
+	priv.GET("/upload", c.ServeFileUpload)
+	priv.POST("/upload", c.SaveFile)
 	priv.GET("/panel", c.AdminPanel)
 	priv.GET("/panel", c.AdminPanel)
 	priv.POST("/add-document", c.AddDocument)
 	priv.POST("/add-document", c.AddDocument)
 	priv.POST("/images/upload", c.SaveFile)
 	priv.POST("/images/upload", c.SaveFile)

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