Browse Source

need to figure out what to do about sourcing the bootstrap and mdb files

aeth 2 months ago
parent
commit
053b34659d

+ 5 - 1
.gitignore

@@ -6,9 +6,13 @@ build/linux/webserver/*
 build/linux/seed/*
 
 html/assets/images/*
+html/
 
 # db seed data
 db_seed/*
 
+sqlite.db
+
 # ignoring nohup.out file
-nohup.out
+nohup.out
+

+ 11 - 3
cmd/seed/seed.go

@@ -3,6 +3,7 @@ package main
 import (
 	"log"
 	"os"
+	"database/sql"
 
 	"git.aetherial.dev/aeth/keiji/pkg/env"
 	"git.aetherial.dev/aeth/keiji/pkg/helpers"
@@ -13,9 +14,16 @@ func main() {
 	err := env.LoadAndVerifyEnv(os.Args[1], env.REQUIRED_VARS); if err != nil {
 		log.Fatal(err)
 	}
-	rds := helpers.NewRedisClient(helpers.RedisConf{Port: os.Getenv("REDIS_PORT"), Addr: os.Getenv("REDIS_ADDR")})
-	err = rds.SeedData(os.Args[2]); if err != nil {
+	dbfile := "sqlite.db"
+	db, err := sql.Open("sqlite3", dbfile)
+	if err != nil {
 		log.Fatal(err)
 	}
+	webserverDb := helpers.NewSQLiteRepo(db)
+	err = webserverDb.Migrate()
+	if err != nil {
+		log.Fatal(err)
+	} 
+	webserverDb.Seed(os.Args[2], os.Args[3], os.Args[4])
 	
-}
+}

+ 13 - 1
cmd/webserver/webserver.go

@@ -4,12 +4,14 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"database/sql"
 
 	"github.com/gin-contrib/multitemplate"
 	"github.com/gin-gonic/gin"
 
 	"git.aetherial.dev/aeth/keiji/pkg/env"
 	"git.aetherial.dev/aeth/keiji/pkg/routes"
+	"git.aetherial.dev/aeth/keiji/pkg/helpers"
 )
 
 var WEB_ROOT string
@@ -101,8 +103,18 @@ func main() {
 		fmt.Sprintf("%s/templates/listing.html", WEB_ROOT),
 	)
 	e := gin.Default()
+	dbfile := "sqlite.db"
+	db, err := sql.Open("sqlite3", dbfile)
+	if err != nil {
+		log.Fatal(err)
+	}
 	e.HTMLRender = renderer
-	routes.Register(e, WEB_ROOT, DOMAIN_NAME, REDIS_PORT, REDIS_ADDR)
+	webserverDb := helpers.NewSQLiteRepo(db)
+	err = webserverDb.Migrate()
+	if err != nil {
+		log.Fatal(err)
+	}
+	routes.Register(e, WEB_ROOT, DOMAIN_NAME, REDIS_PORT, REDIS_ADDR, webserverDb)
 	if os.Getenv("SSL_MODE") == "ON" {
 		e.RunTLS(fmt.Sprintf("%s:%s", os.Getenv("HOST_ADDR"), os.Getenv("HOST_PORT")),
 		os.Getenv(env.CHAIN), os.Getenv(env.KEY))

+ 6 - 6
html/templates/admin.html

@@ -4,8 +4,8 @@
     <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">
+        <link rel="stylesheet" href="/api/v1/cdn/bootstrap.min.css">
+        <link rel="stylesheet" href="/api/v1/cdn/mdb.min.css">
         <link rel="stylesheet" href="/api/v1/cdn/custom.css">
 
     </head>
@@ -45,9 +45,9 @@
         {{ end }}
       </div>
     </div>
-    <script type="text/javascript" src="/api/v1/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
-    <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/cdn/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
+    <script src="/api/v1/cdn/htmx.min.js"></script>
+    <script src="/api/v1/cdn/json-enc.js"></script>
 </body>
 </html>
-{{ end }}
+{{ end }}

+ 6 - 9
html/templates/home.html

@@ -4,8 +4,8 @@
     <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">
+        <link rel="stylesheet" href="/api/v1/cdn/bootstrap.min.css">
+        <link rel="stylesheet" href="/api/v1/cdn/mdb.min.css">
         <link rel="stylesheet" href="/api/v1/cdn/custom.css">
     </head>
 
@@ -25,18 +25,15 @@
 
         <div id="mySidenav" class="sidenav">
             <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a>
-            {{ range .menu.MenuLinks }}
+            {{ range .menu}}
                 <a href="{{ .MenuLink }}" style="font-family: monospace;">{{ .LinkText }}</a>
             {{ 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/cdn/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
         <script src="/api/v1/cdn/slide.js"></script>
-        <script src="/api/v1/htmx/htmx.min.js"></script>
-        <script src="/api/v1/htmx/json-enc.js"></script>
+        <script src="/api/v1/cdn/htmx.min.js"></script>
+        <script src="/api/v1/cdn/json-enc.js"></script>
     </body>
 </html>
 {{ end }}

+ 3 - 3
html/templates/link.html

@@ -5,11 +5,11 @@
         <div class="bg-image hover-overlay shadow-1-strong rounded d-inline-flex p-0"
             data-mdb-ripple-init
             data-mdb-ripple-color="light">
-            <img src="/api/v1/assets/{{ .Png }}" style="max-height: 5vh;"/>
-            <a href="{{ .Link }}" target="_blank">
+            <img src="/api/v1/assets/{{ .Link}}" style="max-height: 5vh;"/>
+            <a href="{{ .Redirect}}" target="_blank">
               <div class="mask" style="background-color: hsla(0, 0%, 98%, 0.2)"></div>
             </a>
         </div>
     </div>
 </html>
-{{ end }}
+{{ end }}

+ 0 - 1
html/templates/navigation.html

@@ -8,7 +8,6 @@
         </div>
         <div class="col-sm">
             <div class="row">
-
                 {{ range .headers }}
                     {{ template "link.html" . }}
                 {{ end }}

+ 6 - 6
html/templates/upload.html

@@ -4,8 +4,8 @@
 <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">
+    <link rel="stylesheet" href="/api/v1/cdn/style/bootstrap.min.css">
+    <link rel="stylesheet" href="/api/v1/cdn/mdb.min.css">
 </head>
 <body style="background-color: rgb(56, 56, 56);">
     {{ template "navigation.html" .navigation }}
@@ -33,10 +33,10 @@
         </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>
+    <script src="/api/v1/cdn/htmx.min.js"></script>
+    <script src="/api/v1/cdn/json-enc.js"></script>
+    <script type="text/javascript" src="/api/v1/cdn/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
     </body>
 </html>
 
-{{ end }}
+{{ end }}

+ 2 - 2
pkg/controller/admin_handlers.go

@@ -88,8 +88,8 @@ func (c *Controller) AdminPanel(ctx *gin.Context) {
 
 	ctx.HTML(http.StatusOK, "admin", gin.H{
 		"navigation": gin.H{
-			"headers": c.Headers().Elements,
-			"menu":    c.Menu(),
+			"headers": c.database.GetNavBarLinks(),
+			"menu":    c.database.GetDropdownElements(),
 		},
 		"Tables": c.AdminTables().Tables,
 	})

+ 21 - 111
pkg/controller/cdn_handlers.go

@@ -5,96 +5,24 @@ import (
 	"os"
 	"path"
 	"strings"
+	"net/http"
 
 	"git.aetherial.dev/aeth/keiji/pkg/helpers"
 	"github.com/gin-gonic/gin"
 )
 
-// @Name ServeCss
-// @Summary serves css files from the web root directory
-// @Tags cdn
-// @Param file path string true "The CSS file to serve to the client"
-// @Router /api/v1/style/{file} [get]
-func (c *Controller) ServeCss(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/css/bootstrap-5.0.2/dist/css/%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/css", b)
-
-}
-
-// @Name ServeJs
-// @Summary serves js files from the web root directory
-// @Tags cdn
-// @Param file path string true "The Javascript file to serve to the client"
-// @Router /api/v1/js/{file} [get]
-func (c *Controller) ServeJs(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/css/bootstrap-5.0.2/dist/js/%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 ServeMdbCss
-// @Summary serves some mdb assets
-// @Tags cdn
-// @Param file path string true "The CSS file to serve to the client"
-// @Router /api/v1/style/mdb/{file} [get]
-func (c *Controller) ServeMdbCss(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/css/MDB5-STANDARD-UI-KIT-Free-7.1.0/css/%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/css", b)
-
-}
-
-// @Name ServeHtmx
-// @Summary serves some htmx assets
+// @Name ServeImage
+// @Summary serves image from the image store
 // @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) {
+// @Router /images/{file} [get]
+func (c *Controller) ServeImage(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)
+	css := fmt.Sprintf("%s/%s", helpers.GetImageStore(), f)
 	b, err := os.ReadFile(css)
 	if err != nil {
 		ctx.JSON(500, map[string]string{
@@ -102,54 +30,34 @@ func (c *Controller) ServeHtmx(ctx *gin.Context) {
 			"msg":   err.Error(),
 		})
 	}
-	ctx.Data(200, "text/javascript", b)
-
+	ctx.Data(200, "image/jpeg", b)
 }
 
 // @Name ServeAsset
-// @Summary serves assets to put in a webpage
+// @Summary serves file from the html file
 // @Tags cdn
-// @Router /assets/{file} [get]
+// @Router /api/v1/assets/{file} [get]
 func (c *Controller) ServeAsset(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
 	}
-	css := fmt.Sprintf("%s/assets/%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(),
-		})
+	assets := c.database.GetAssets()
+	for i := range assets {
+		if strings.Contains(assets[i].Name, f) {
+			ctx.Data(200, "image/png", assets[i].Data)
+			return 
+		}
 	}
-	ctx.Data(200, "image/jpeg", b)
-}
+	ctx.Data(http.StatusNotFound, "text", []byte("Couldnt find the image requested."))
 
-// @Name ServeImage
-// @Summary serves image from the image store
-// @Tags cdn
-// @Router /images/{file} [get]
-func (c *Controller) ServeImage(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/%s", helpers.GetImageStore(), 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, "image/jpeg", b)
 }
 
+
+
 // @Name ServeGeneric
 // @Summary serves file from the html file
 // @Tags cdn
@@ -171,6 +79,8 @@ func (c *Controller) ServeGeneric(ctx *gin.Context) {
 		ctype = "text/javascript"
 	case fext == "json":
 		ctype = "application/json"
+	case fext == "png":
+		ctype = "image/png"
 	default:
 		ctype = "text"
 	}

+ 11 - 21
pkg/controller/content_handlers.go

@@ -12,8 +12,8 @@ import (
 func (c *Controller) ServeBlogDirectory(ctx *gin.Context) {
 	ctx.HTML(200, "admin", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
-			"headers": c.Headers().Elements,
+			"menu": c.database.GetDropdownElements(),
+			"headers": c.database.GetNavBarLinks(),
 		},
 		"Tables": c.FormatDocTable().Tables,
 
@@ -40,8 +40,8 @@ func (c *Controller) GetBlogPostEditor(ctx *gin.Context) {
 	}
 	ctx.HTML(200, "blogpost_editor", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
-			"headers": c.Headers().Elements,
+			"menu": c.database.GetDropdownElements(),
+			"headers": c.database.GetNavBarLinks(),
 		},
 		"HttpMethod": "patch",
 		"Ident": doc.Ident,
@@ -75,8 +75,8 @@ func (c *Controller) ServeNewBlogPage(ctx *gin.Context) {
 
 	ctx.HTML(200, "new_blogpost", gin.H{
 		"navigation": gin.H{
-			"menu": c.Menu(),
-			"headers": c.Headers().Elements,
+			"menu": c.database.GetDropdownElements(),
+			"headers": c.database.GetNavBarLinks(),
 		},
 		"HttpMethod": "post",
 		"Topics": helpers.Topics,
@@ -106,8 +106,8 @@ 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,
+			"menu": c.database.GetDropdownElements(),
+			"headers": c.database.GetNavBarLinks(),
 		},
 	})
 }
@@ -115,29 +115,19 @@ func (c *Controller) ServeFileUpload(ctx *gin.Context) {
 
 
 func (c *Controller) SaveFile(ctx *gin.Context) {
-	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 {
+	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 {
+	savedImg := helpers.NewImageStoreItem(img.Title, img.Desc)
+	err = c.database.AddImage(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, savedImg.AbsolutePath)
-	if err != nil {
-		ctx.HTML(400, "upload_status", gin.H{"UpdateMessage": err, "Color": "red"})
-		return
-	}
 
 	ctx.HTML(200, "upload_status", gin.H{"UpdateMessage": "Update Successful!", "Color": "green"})
 }

+ 3 - 32
pkg/controller/controller.go

@@ -10,31 +10,12 @@ import (
 type Controller struct{
 	WebRoot		string
 	Domain		string
+	database    helpers.DocumentIO
 	RedisConfig helpers.RedisConf
 	Cache		*helpers.AllCache
 }
 
-/*
-Retrieve the header configuration from redis
-*/
-func (c *Controller) Headers() *helpers.HeaderCollection {
-	headers, err := helpers.GetHeaders(c.RedisConfig)
-	if err != nil {
-		log.Fatal(err, "Headers couldnt be loaded. Exiting.")
-	}
-	return headers
-}
 
-/*
-Retrieve the menu configuration from redis
-*/
-func (c *Controller) Menu() *helpers.MenuElement {
-	links, err := helpers.GetMenuLinks(c.RedisConfig)
-	if err != nil {
-		log.Fatal(err, "Menu links couldnt be couldnt be loaded. Exiting.")
-	}
-	return links
-}
 
 /*
 Retrieve the administrator table configuration from redis
@@ -73,24 +54,14 @@ 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, database helpers.DocumentIO) *Controller {
 	return &Controller{WebRoot: root, Cache: helpers.NewCache(),
 								Domain: domain, RedisConfig: helpers.RedisConf{
 																		Port: redisPort,
 																		Addr: redisAddr,
 																		},
+																		database: database,
 																	}
 }

+ 7 - 78
pkg/controller/html_handlers.go

@@ -34,13 +34,13 @@ func (c *Controller) ServePost(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "blogpost", gin.H{
 		"navigation": gin.H{
-			"headers": c.Headers().Elements,
+			"headers": c.database.GetNavBarLinks(),
 		},
 		"title":   doc.Ident,
 		"Ident":   doc.Ident,
 		"Created": doc.Created,
 		"Body":    template.HTML(helpers.MdToHTML([]byte(doc.Body))),
-		"menu":    c.Menu(),
+		"menu":    c.database.GetDropdownElements(),
 	})
 
 }
@@ -50,87 +50,16 @@ func (c *Controller) ServePost(ctx *gin.Context) {
 // @Tags webpages
 // @Router /blog [get]
 func (c *Controller) ServeBlogHome(ctx *gin.Context) {
-	docs, err := helpers.GetAllDocuments(helpers.BLOG, c.RedisConfig)
-	if err != nil {
-		ctx.JSON(500, map[string]string{
-			"Error getting docs": err.Error(),
-		})
-		return
-	}
-	ctx.HTML(http.StatusOK, "home", gin.H{
-		"navigation": gin.H{
-			"headers": c.Headers().Elements,
-		},
-		"listings": docs,
-		"menu":    c.Menu(),
-	})
-}
-
-// @Name ServeHtml
-// @Summary serves HTML files out of the HTML directory
-// @Tags webpages
-// @Router /home [get]
-func (c *Controller) ServeHome(ctx *gin.Context) {
-	docs, err := helpers.GetAllDocuments(helpers.BLOG, c.RedisConfig)
-	if err != nil {
-		ctx.JSON(500, map[string]string{
-			"Error getting docs": err.Error(),
-		})
-		return
-	}
+	docs := c.database.GetByCategory(helpers.BLOG)
 	ctx.HTML(http.StatusOK, "home", gin.H{
 		"navigation": gin.H{
-			"headers": c.Headers().Elements,
+			"headers": c.database.GetNavBarLinks(),
 		},
-		"menu":    c.Menu(),
 		"listings": docs,
+		"menu":     c.database.GetDropdownElements(),
 	})
 }
 
-// @Name ServeCreativeWriting
-// @Summary serves the HTML file for the creative writing homepage
-// @Tags webpages
-// @Router /creative [get]
-func (c *Controller) ServeCreativeWriting(ctx *gin.Context) {
-	docs, err := helpers.GetAllDocuments(helpers.CREATIVE, c.RedisConfig)
-	if err != nil {
-		ctx.JSON(500, map[string]string{
-			"Error getting docs": err.Error(),
-		})
-		return
-	}
-	ctx.HTML(http.StatusOK, "home", gin.H{
-		"navigation": gin.H{
-			"headers": c.Headers().Elements,
-		},
-		"listings": docs,
-		"menu":    c.Menu(),
-	})
-
-}
-
-// @Name ServeTechnicalWriteups
-// @Summary serves the HTML file for the technical writeup homepage
-// @Tags webpages
-// @Router /writeups [get]
-func (c *Controller) ServeTechnicalWriteups(ctx *gin.Context) {
-	docs, err := helpers.GetAllDocuments(helpers.TECHNICAL, c.RedisConfig)
-	if err != nil {
-		ctx.JSON(500, map[string]string{
-			"Error getting docs": err.Error(),
-		})
-		return
-	}
-	ctx.HTML(http.StatusOK, "home", gin.H{
-		"navigation": gin.H{
-			"headers": c.Headers().Elements,
-		},
-		"listings": docs,
-		"menu":    c.Menu(),
-	})
-
-}
-
 // @Name ServeDigitalArt
 // @Summary serves the HTML file for the digital art homepage
 // @Tags webpages
@@ -149,9 +78,9 @@ func (c *Controller) ServeDigitalArt(ctx *gin.Context) {
 	}
 	ctx.HTML(http.StatusOK, "digital_art", gin.H{
 		"navigation": gin.H{
-			"headers": c.Headers().Elements,
+			"headers": c.database.GetNavBarLinks(),
 		},
 		"images": fnames,
-		"menu":    c.Menu(),
+		"menu":   c.database.GetDropdownElements(),
 	})
 }

+ 1 - 1
pkg/helpers/helpers.go

@@ -70,7 +70,7 @@ type Table struct {
 	TableData []TableData `json:"table_data"`
 }
 
-type TableData struct {
+type TableData struct { // TODO: add this to the database io interface 
 	DisplayName string `json:"display_name"`
 	Link        string `json:"link"`
 }

+ 346 - 68
pkg/helpers/storage.go

@@ -3,6 +3,8 @@ package helpers
 import (
 	"database/sql"
 	"encoding/json"
+	"log"
+	"path"
 	"errors"
 	"fmt"
 	"os"
@@ -11,7 +13,6 @@ import (
 
 	"git.aetherial.dev/aeth/keiji/pkg/env"
 	"github.com/google/uuid"
-	"github.com/mattn/go-sqlite3"
 	"github.com/redis/go-redis/v9"
 )
 
@@ -23,22 +24,32 @@ type DatabaseSchema struct {
 }
 
 type MenuLinkPair struct {
-	MenuLink string `json:"menu_link"`
-	LinkText string `json:"link_text"`
+	MenuLink string `json:"link"`
+	LinkText string `json:"text"`
 }
 
 type NavBarItem struct {
-	Png  string `json:"png"`
+	Png  []byte `json:"png"`
 	Link string `json:"link"`
+	Redirect string `json:"redirect"`
 }
 
+type Asset struct {
+	Name string
+	Data []byte
+}
+
+
+type Identifier string
+
 type Document struct {
-	PostId   string
+	Row int
+	Ident   string   `json:"id"`
 	Title    string `json:"title"`
 	Created  string `json:"created"`
 	Body     string `json:"body"`
 	Category string `json:"category"`
-	Sample   string
+	Sample   string `json:"sample"`
 }
 
 /*
@@ -59,23 +70,27 @@ func (d *Document) MakeSample() string {
 }
 
 type Image struct {
+	Ident string
 	Location string
 	Title    string
 	Desc     string
+	Created string
+	Category string
+	Data     []byte
 }
 
 type DocumentIO interface {
-	GetDocument(id string) (Document, error)
-	GetImage(id string) (Image, error)
+	GetDocument(id Identifier) (Document, error)
+	GetImage(id Identifier) (Image, error)
 	UpdateDocument(doc Document) error
-	DeleteDocument(id string) error
+	DeleteDocument(id Identifier) error
 	AddDocument(doc Document) error
-	AddImage(img ImageStoreItem) error
-	GetByCategory(category string) []string
+	AddImage(img Image) error
+	GetByCategory(category string) []Document
 	AllDocuments() []Document
 	GetDropdownElements() []MenuLinkPair
 	GetNavBarLinks() []NavBarItem
-	ExportAll()
+	GetAssets() []Asset
 }
 
 var (
@@ -99,10 +114,10 @@ func NewSQLiteRepo(db *sql.DB) *SQLiteRepo {
 
 // Creates a new SQL table for text posts
 func (r *SQLiteRepo) Migrate() error {
-	query := `
+	postsTable := `
     CREATE TABLE IF NOT EXISTS posts(
-        id INTEGER PRIMARY KEY AUTOINCREMENT,
-		postid TEXT NOT NULL,
+        row INTEGER PRIMARY KEY AUTOINCREMENT,
+		id TEXT NOT NULL,
 		title TEXT NOT NULL,
         created TEXT NOT NULL,
         body TEXT NOT NULL UNIQUE,
@@ -110,55 +125,167 @@ func (r *SQLiteRepo) Migrate() error {
 		sample TEXT NOT NULL
     );
     `
+	imagesTable := `
+	CREATE TABLE IF NOT EXISTS images(
+		row INTEGER PRIMARY KEY AUTOINCREMENT,
+		id TEXT NOT NULL,
+		title TEXT NOT NULL,
+		location TEXT NOT NULL,
+		description TEXT NOT NULL,
+		created TEXT NOT NULL,
+		category TEXT NOT NULL
+	);
+	`
+	menuItemsTable := `
+	CREATE TABLE IF NOT EXISTS menu(
+		row INTEGER PRIMARY KEY AUTOINCREMENT,
+		link TEXT NOT NULL,
+		text TEXT NOT NULL
+	);
+	`
+	navbarItemsTable := `
+	CREATE TABLE IF NOT EXISTS navbar(
+		row INTEGER PRIMARY KEY AUTOINCREMENT,
+		png BLOB NOT NULL,
+		link TEXT NOT NULL,
+		redirect TEXT
+	);`
+	assetTable := `
+	CREATE TABLE IF NOT EXISTS assets(
+		row INTEGER PRIMARY KEY AUTOINCREMENT,
+		name TEXT NOT NULL,
+		data BLOB NOT NULL
+	);
+	`
+	seedQueries := []string{postsTable, imagesTable, menuItemsTable, navbarItemsTable, assetTable}
+    for i := range seedQueries {
+	    _, err := r.db.Exec(seedQueries[i])
+	    if err != nil {
+			return err
+		}
+    }
+	return nil
+}
+
+
+
+func (s *SQLiteRepo) Seed(menu string, pngs string, dir string) {
+	b, err := os.ReadFile(menu)
+
+	if err != nil {log.Fatal(err)}
+	entries := strings.Split(string(b), "\n")
+	for i := range entries {
+		if entries[i] == "" {
+			continue
+		}
+		info := strings.Split(entries[i], "=")
+		err := s.AddMenuItem(MenuLinkPair{MenuLink: info[0], LinkText: info[1]})
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+	b, err = os.ReadFile(pngs)
+	if err != nil {log.Fatal(err)}
+	entries = strings.Split(string(b), "\n")
+	for i := range entries {
+		if entries[i] == "" {continue}
+		info := strings.Split(entries[i], "=")
+		b, err := os.ReadFile(path.Join(dir, info[0]))
+		if err != nil {log.Fatal(err)}
+		err = s.AddNavbarItem(NavBarItem{Png: b, Link: info[0], Redirect: info[1]})
+		if err != nil {log.Fatal(err)}
+	}
+	assets, err := os.ReadDir(dir)
+	if err != nil {
+		log.Fatal(err)
+	}
+	for i := range assets {
+		b, err := os.ReadFile(path.Join(dir, assets[i].Name()))
+		if err != nil {
+			log.Fatal(err)
+		}
+		err = s.AddAsset(assets[i].Name(), b)
+		if err != nil {
+			log.Fatal(err)
+		}
 
-	_, err := r.db.Exec(query)
-	return err
+	}
 }
 
-/*
-Create an entry in the hosts table
 
-	:param host: a Host entry from a port scan
+/*
+Get all dropdown menu elements
 */
-func (r *SQLiteRepo) Create(post Document) error {
-	_, err := r.db.Exec("INSERT INTO posts(postid, title, created, body, category, sample) values(?,?,?,?,?)", uuid.New().String(), post.Title, post.Created, post.Body, post.Category, post.MakeSample())
-	if err != nil {
-		var sqliteErr sqlite3.Error
-		if errors.As(err, &sqliteErr) {
-			if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) {
-				return ErrDuplicate
-			}
+func (s *SQLiteRepo) GetDropdownElements() []MenuLinkPair {
+	rows, err := s.db.Query("SELECT * FROM menu")
+	var menuItems []MenuLinkPair
+	defer rows.Close()
+	for rows.Next() {
+		var id int
+		var item MenuLinkPair 
+		err = rows.Scan(&id, &item.MenuLink, &item.LinkText)
+		if err != nil {
+			log.Fatal(err)
 		}
-		return err
+		menuItems = append(menuItems, item)
 	}
+	return menuItems
+
 
-	return nil
 }
 
-// Get all Hosts from the host table
-func (r *SQLiteRepo) AllDocuments() []Document {
-	rows, err := r.db.Query("SELECT * FROM posts")
-	if err != nil {
-		fmt.Printf("There was an issue getting all posts. %s", err.Error())
-		return nil
-	}
+
+/*
+Get all nav bar items
+*/
+func (s *SQLiteRepo) GetNavBarLinks() []NavBarItem {
+
+	rows, err := s.db.Query("SELECT * FROM navbar")
+	var navbarItems []NavBarItem
 	defer rows.Close()
+	for rows.Next() {
+		var item NavBarItem
+		var id int
+		err = rows.Scan(&id, &item.Png, &item.Link, &item.Redirect)
+		if err != nil {
+			log.Fatal(err)
+		}
+		navbarItems= append(navbarItems, item)
+	}
+	return navbarItems
 
-	var all []Document
+
+}
+
+/*
+get all assets from the asset table
+*/
+func (s *SQLiteRepo) GetAssets() []Asset {
+	rows, err := s.db.Query("SELECT * FROM assets")
+	var assets []Asset
+	defer rows.Close()
 	for rows.Next() {
-		var post Document
-		if err := rows.Scan(&post.PostId, &post.Title, &post.Created, &post.Body, &post.Sample); err != nil {
-			fmt.Printf("There was an error getting all documents. %s", err.Error())
-			return nil
+		var item Asset
+		var id int
+		err = rows.Scan(&id, &item.Name, &item.Data)
+		if err != nil {
+			log.Fatal(err)
 		}
-		all = append(all, post)
+		assets =  append(assets, item)
 	}
-	return all
+	return assets
+
 }
 
-// Get a blogpost by its postid
-func (r *SQLiteRepo) GetByIP(postId string) (Document, error) {
-	row := r.db.QueryRow("SELECT * FROM posts WHERE postid = ?", postId)
+
+
+/*
+Retrieve a document from the sqlite db
+
+	:param id: the Identifier of the post
+*/
+func (s *SQLiteRepo) GetDocument(id Identifier) (Document, error) {
+	row := s.db.QueryRow("SELECT * FROM posts WHERE id = ?", id)
 
 	var post Document
 	if err := row.Scan(&post); err != nil {
@@ -168,49 +295,201 @@ func (r *SQLiteRepo) GetByIP(postId string) (Document, error) {
 		return post, err
 	}
 	return post, nil
+
 }
 
-// Update a record by its ID
-func (r *SQLiteRepo) Update(id int64, updated Document) error {
-	if id == 0 {
-		return errors.New("invalid updated ID")
+/*
+Get all documents by category
+*/
+func (s *SQLiteRepo) GetByCategory(category string) []Document {
+	rows, err := s.db.Query("SELECT * FROM posts WHERE category = ?", category)
+	if err != nil {
+		log.Fatal(err)
 	}
-	res, err := r.db.Exec("UPDATE posts SET title = ?, body = ?, desc = ? WHERE id = ?", updated.Title, updated.Body, updated.MakeSample(), id)
+	var docs []Document
+	defer rows.Close()
+	for rows.Next() {
+		var doc Document
+		err := rows.Scan(&doc.Row, &doc.Ident, &doc.Title, &doc.Created, &doc.Body, &doc.Category, &doc.Sample)
+		if err != nil {
+			log.Fatal(err)
+		}
+		docs = append(docs, doc)
+	}
+	err = rows.Err()
+	if err != nil {
+		log.Fatal(err)
+	}
+	return docs
+
+}
+
+/*
+get image data from the images table
+	:param id: the serial identifier of the post  
+*/
+func (s *SQLiteRepo) GetImage(id Identifier) (Image, error) {
+	row := s.db.QueryRow("SELECT * FROM images WHERE id = ?", id)
+	var title string
+	var location string
+	var desc string
+	var category string
+	var created string
+	if err := row.Scan(&title, &location, &desc, &created, &category); err != nil {
+		if errors.Is(err, sql.ErrNoRows) {
+			return Image{}, ErrNotExists
+		}
+		return Image{}, err
+	}
+	data, err := os.ReadFile(location)
+	if err != nil {
+		return Image{}, err
+	}
+	return Image{Title: title, Location: location, Desc: desc, Data: data, Created: created, Category: category}, nil
+}
+
+/*
+Add an image to the database
+	:param title: the title of the image
+	:param location: the location to save the image to
+	:param desc: the description of the image, if any
+	:param data: the binary data for the image
+*/
+func (s *SQLiteRepo) AddImage(img Image) error {
+	err := os.WriteFile(path.Join(img.Location, img.Ident), img.Data, os.ModeAppend) 
 	if err != nil {
 		return err
 	}
+	_, err = s.db.Exec("INSERT INTO images (id, title, location, desc, created, category) VALUES (?,?,?,?,?,?,?)", img.Ident, img.Title, img.Location, img.Desc, img.Created, img.Category)
+	if err != nil {
+		return err
+	}
+	return nil
+}
 
-	rowsAffected, err := res.RowsAffected()
+
+
+func (s *SQLiteRepo) UpdateDocument(doc Document) error {
+	tx, err := s.db.Begin()
+	if err != nil {
+		return err
+	}
+	stmt,_ := tx.Prepare("UPDATE posts set title=? body=? category=? WHERE id=?")
+	_, err = stmt.Exec(doc.Title, doc.Body, doc.Category, doc.Ident)
 	if err != nil {
+		tx.Rollback()
 		return err
 	}
+	tx.Commit()
+	return nil
+}
 
-	if rowsAffected == 0 {
-		return ErrUpdateFailed
+func (s *SQLiteRepo) AddMenuItem(item MenuLinkPair) error {
+	tx, err := s.db.Begin()
+	if err != nil {
+		return err
 	}
+	stmt,_ := tx.Prepare("INSERT INTO menu(link, text) VALUES (?,?)")
+	_, err = stmt.Exec(item.MenuLink, item.LinkText)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	return nil
+	
+}
 
+func (s *SQLiteRepo) AddNavbarItem(item NavBarItem) error {
+	tx, err := s.db.Begin()
+	if err != nil {
+		return err
+	}
+	stmt,_ := tx.Prepare("INSERT INTO navbar(png, link, redirect) VALUES (?,?,?)")
+	_, err = stmt.Exec(item.Png, item.Link, item.Redirect)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
 	return nil
+	
 }
 
-// Delete a record by its ID
-func (r *SQLiteRepo) Delete(id int64) error {
-	res, err := r.db.Exec("DELETE FROM posts WHERE id = ?", id)
+func (s *SQLiteRepo) AddAsset(name string, data []byte) error {
+	tx, err := s.db.Begin()
 	if err != nil {
 		return err
 	}
+	stmt,_ := tx.Prepare("INSERT INTO assets(name, data) VALUES (?,?)")
+	_, err = stmt.Exec(name, data)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	return nil
+}	
 
-	rowsAffected, err := res.RowsAffected()
+
+func (s *SQLiteRepo) AddDocument(doc Document) error {
+	tx, err := s.db.Begin()
 	if err != nil {
 		return err
 	}
+	stmt,_ := tx.Prepare("INSERT INTO posts (id, title, created, body, category,sample) VALUES (?,?,?,?,?,?)")
+	_, err = stmt.Exec(doc.Ident, doc.Title, doc.Created, doc.Body, doc.Category, doc.Sample)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	tx.Commit()
+	return nil
 
-	if rowsAffected == 0 {
-		return ErrDeleteFailed
+}
+
+/*
+Delete a document from the db
+	:param id: the identifier of the document to remove
+*/
+func (s *SQLiteRepo) DeleteDocument(id Identifier) error {
+	tx, err := s.db.Begin()
+	if err != nil {
+		return err
 	}
+	stmt,_ := tx.Prepare("DELETE FROM posts WHERE id=?")
+	_, err = stmt.Exec(id)
+	if err != nil {
+		tx.Rollback()
+		return err}
+	tx.Commit()
+	return nil
+	
+}
 
-	return err
+// Get all Hosts from the host table
+func (s *SQLiteRepo) AllDocuments() []Document {
+	rows, err := s.db.Query("SELECT * FROM posts")
+	if err != nil {
+		fmt.Printf("There was an issue getting all posts. %s", err.Error())
+		return nil
+	}
+	defer rows.Close()
+
+	var all []Document
+	for rows.Next() {
+		var post Document
+		if err := rows.Scan(&post.Ident, &post.Title, &post.Created, &post.Body, &post.Sample); err != nil {
+			fmt.Printf("There was an error getting all documents. %s", err.Error())
+			return nil
+		}
+		all = append(all, post)
+	}
+	return all
 }
 
+
+
 type InvalidSkipArg struct{ Skip int }
 
 func (i *InvalidSkipArg) Error() string {
@@ -235,18 +514,17 @@ Create a new ImageStoreItem
 	: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 {
+func NewImageStoreItem(title string, desc string) Image {
 	id := uuid.New()
-	img := ImageStoreItem{
-		Identifier:   id.String(),
-		Filename:     fname,
+	img := Image{
+		Ident:   id.String(),
 		Title:        title,
 		Category:     DIGITAL_ART,
-		AbsolutePath: fmt.Sprintf("%s/%s", GetImageStore(), fname),
+		Location: GetImageStore(),
 		Created:      time.Now().UTC().String(),
 		Desc:         desc,
 	}
-	return &img
+	return img
 }
 
 /*

+ 4 - 10
pkg/routes/register.go

@@ -2,17 +2,15 @@ package routes
 
 import (
 	"git.aetherial.dev/aeth/keiji/pkg/controller"
+	"git.aetherial.dev/aeth/keiji/pkg/helpers"
 	"github.com/gin-gonic/gin"
 )
 
-func Register(e *gin.Engine, root string, domain string, redisPort string, redisAddr string) {
-	c := controller.NewController(root, domain, redisPort, redisAddr)
+func Register(e *gin.Engine, root string, domain string, redisPort string, redisAddr string, database helpers.DocumentIO) {
+	c := controller.NewController(root, domain, redisPort, redisAddr, database)
 	web := e.Group("")
 	web.GET("/", c.ServeBlogHome)
-	web.GET("/home", c.ServeHome)
 	web.GET("/blog", c.ServeBlogHome)
-	web.GET("/creative", c.ServeCreativeWriting)
-	web.GET("/technical", c.ServeTechnicalWriteups)
 	web.GET("/digital", c.ServeDigitalArt)
 	web.GET("/writing/:post-name", c.ServePost)
 	web.GET("/login", c.ServeLogin)
@@ -20,13 +18,9 @@ func Register(e *gin.Engine, root string, domain string, redisPort string, redis
 
 
 	cdn := e.Group("/api/v1")
-	cdn.GET("/style/:file", c.ServeCss)
-	cdn.GET("/js/:file", c.ServeJs)
-	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)
+	cdn.GET("assets/:file", c.ServeAsset)