diff --git a/data.go b/data.go index a49ef13..1f8ba11 100644 --- a/data.go +++ b/data.go @@ -27,3 +27,29 @@ func (a *App) GetAllRAMTypes() ([]string, error) { } return types, err } + +func (a *App) GetAllTypes() ([]string, error) { + var types []string + var err error + for row := range a.db.Query("SELECT type FROM assets GROUP BY type ORDER BY type ASC").Range(&err) { + var name string + err := row.Scan(&name) + if err != nil { + return nil, err + } + types = append(types, name) + } + return types, err +} + +func (a *App) GetAssetCount() (int, error) { + var count int + err := a.db.Query("SELECT COUNT(*) FROM assets").ScanSingle(&count) + return count, err +} + +func (a *App) GetBrandCount() (int, error) { + var count int + err := a.db.Query("SELECT COUNT(DISTINCT brand) FROM assets").ScanSingle(&count) + return count, err +} diff --git a/go.mod b/go.mod index fd6289b..54d933d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24 toolchain go1.24.1 require ( - gitea.seeseepuff.be/seeseemelk/mysqlite v0.7.0 + gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0 github.com/gin-gonic/gin v1.10.0 ) diff --git a/go.sum b/go.sum index cb8a92c..7c65f36 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -gitea.seeseepuff.be/seeseemelk/mysqlite v0.7.0 h1:gq75Ce7QTQ5Rj5fzS/6eeOA/enyV0oDMVt5mejwX14Y= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.7.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0 h1:GaU2DSrgDfZEqST3HdnNgfKSI4sNXvMm8SSfeMvBxA4= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= diff --git a/main.go b/main.go index 0a073c1..39324fd 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,8 @@ func main() { Funcs(template.FuncMap{ "statusText": http.StatusText, "createDeviceLink": createDeviceLink, + "formatMemorySize": formatMemorySize, + "formatType": formatType, }). ParseFS(templateFS, "templates/*.gohtml") @@ -54,6 +56,7 @@ func main() { r.GET("/device", app.getDevice) r.GET("/create", app.getCreateDevice) r.POST("/create", app.postCreateDevice) + r.GET("/browse", app.getBrowse) err = r.Run() if err != nil { log.Fatalf("error serving website: %v", err) diff --git a/template_funcs.go b/template_funcs.go index de900cc..fad1a49 100644 --- a/template_funcs.go +++ b/template_funcs.go @@ -1,5 +1,9 @@ package main +import ( + "fmt" +) + func createDeviceLink(deviceType, name string, qr *int) CreateDeviceLink { return CreateDeviceLink{ Type: deviceType, @@ -13,3 +17,31 @@ type CreateDeviceLink struct { Name string Qr *int } + +func formatMemorySize(size int) string { + const ( + KB = 1024 + MB = KB * 1024 + GB = MB * 1024 + ) + + switch { + case size >= GB: + return fmt.Sprintf("%.2f GB", float64(size)/GB) + case size >= MB: + return fmt.Sprintf("%.2f MB", float64(size)/MB) + case size >= KB: + return fmt.Sprintf("%.2f KB", float64(size)/KB) + default: + return fmt.Sprintf("%d B", size) + } +} + +func formatType(t string) string { + switch t { + case "ram": + return "Random Access Memory" + default: + return t + } +} diff --git a/templates/browse.gohtml b/templates/browse.gohtml new file mode 100644 index 0000000..c24bd45 --- /dev/null +++ b/templates/browse.gohtml @@ -0,0 +1,31 @@ +{{- /*gotype: main.BrowseVM */}} +{{define "browse"}} +{{template "header" "Search Results"}} + + + + + + + + {{if .HasRam}} + + + {{end}} + + {{range .Assets}} + + + + + + + {{if $.HasRam}} + + + {{end}} + + {{end}} +
QRTypeNameBrandDescriptionRAM TypeRAM Capacity
{{.Qr}}{{.Type | formatType}}{{.Name}}{{.Brand}}{{.Description}}{{.RamType}}{{.RamCapacity | formatMemorySize}}
+{{template "footer"}} +{{end}} diff --git a/templates/device.gohtml b/templates/device.gohtml new file mode 100644 index 0000000..bafc0f7 --- /dev/null +++ b/templates/device.gohtml @@ -0,0 +1,33 @@ +{{- /*gotype: main.DeviceVM */}} +{{define "device"}} +{{template "header" "Device Details"}} + + + + + + + + + + + + + + + + + + {{if eq .Type "ram"}} + + + + + + + + + {{end}} +
Name:{{.Name}}
Brand:{{.Brand}}
Type:{{.Type}}
Description:{{.Description}}
RAM Type:{{.RamType}}
RAM Capacity:{{.RamCapacity | formatMemorySize}}
+{{template "footer"}} +{{end}} diff --git a/templates/index.gohtml b/templates/index.gohtml index 3d0ee11..6aa2b34 100644 --- a/templates/index.gohtml +++ b/templates/index.gohtml @@ -1,9 +1,32 @@ {{- /*gotype: main.IndexVM */}} {{define "index"}} {{template "header"}} - Some statistics: +

Some statistics

+ +

Filter Devices

+
+

Select Brands:

+ {{range .Brands}} +
+ {{end}} + +

Select Types:

+ {{range .Types}} +
+ {{end}} + + +
+ {{template "footer"}} {{end}} diff --git a/views.go b/views.go index 8ba770e..a514f67 100644 --- a/views.go +++ b/views.go @@ -16,11 +16,33 @@ type App struct { type IndexVM struct { AssetCount int + BrandCount int + Brands []string + Types []string } func (a *App) getIndex(c *gin.Context) { vm := &IndexVM{} - err := a.db.Query("SELECT COUNT(*) FROM assets").ScanSingle(&vm.AssetCount) + var err error + vm.AssetCount, err = a.GetAssetCount() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.BrandCount, err = a.GetBrandCount() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.Brands, err = a.GetAllBrands() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.Types, err = a.GetAllTypes() if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return @@ -29,6 +51,15 @@ func (a *App) getIndex(c *gin.Context) { c.HTML(http.StatusOK, "index", vm) } +type DeviceVM struct { + Name string + Brand string + Type string + Description string + RamType string + RamCapacity int +} + func (a *App) getDevice(c *gin.Context) { qr, err := strconv.Atoi(c.Query("id")) if err != nil { @@ -49,6 +80,27 @@ func (a *App) getDevice(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, "/create?id="+strconv.Itoa(qr)) return } + + vm := &DeviceVM{} + err = a.db.Query("SELECT name, brand, type, description FROM assets WHERE qr = ?"). + Bind(qr). + ScanSingle(&vm.Name, &vm.Brand, &vm.Type, &vm.Description) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if vm.Type == "ram" { + err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). + Bind(qr). + ScanSingle(&vm.RamType, &vm.RamCapacity) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + } + + c.HTML(http.StatusOK, "device", vm) } type CreateDeviceVM struct { @@ -153,3 +205,70 @@ func (a *App) postCreateDeviceRam(c *gin.Context, qr int) error { } return tx.Commit() } + +type BrowseVM struct { + Assets []Asset + HasRam bool +} + +type Asset struct { + Qr int + Name string + Brand string + Type string + Description string + RamType string + RamCapacity int +} + +func (a *App) getBrowse(c *gin.Context) { + brands := c.QueryArray("brand") + types := c.QueryArray("type") + + query := `SELECT assets.qr, assets.name, assets.brand, assets.type, assets.description, + info_ram.type, info_ram.capacity + FROM assets + JOIN info_ram ON info_ram.asset = assets.qr + WHERE 1=1` + if len(brands) > 0 { + query += " AND assets.brand IN (" + placeholders(len(brands)) + ")" + } + if len(types) > 0 { + query += " AND assets.type IN (" + placeholders(len(types)) + ")" + } + + vm := &BrowseVM{} + + var err error + q := a.db.Query(query).Bind(brands, types) + for row := range q.Range(&err) { + var asset Asset + err := row.Scan(&asset.Qr, &asset.Name, &asset.Brand, &asset.Type, &asset.Description, &asset.RamType, &asset.RamCapacity) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + vm.Assets = append(vm.Assets, asset) + if asset.Type == "ram" { + vm.HasRam = true + } + } + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.HTML(http.StatusOK, "browse", vm) +} + +func placeholders(count int) string { + if count == 0 { + return "" + } + placeholder := "?" + for count > 1 { + placeholder += ", ?" + count-- + } + return placeholder +}