Bladeren bron

fixing merge edits

AETH-erial 5 maanden geleden
bovenliggende
commit
6fef70c996

+ 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


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));
+    }
+});

+ 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 }}

+ 2 - 2
html/templates/home.html

@@ -38,9 +38,9 @@
 
 
         <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,
 	})

+ 26 - 0
pkg/controller/cdn_handlers.go

@@ -80,6 +80,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

+ 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 - 1
pkg/routes/register.go

@@ -25,11 +25,14 @@ 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("/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