Merge pull request 'Automatic Price Calculation' (#33) from priceCalculation into master
All checks were successful
Go / build (push) Successful in 13m37s

Reviewed-on: #33
This commit was merged in pull request #33.
This commit is contained in:
2025-07-02 11:26:52 +02:00
24 changed files with 1475 additions and 175 deletions

View File

@@ -0,0 +1,350 @@
package controllers
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"git.dynamicdiscord.de/kalipso/zineshop/models"
//"git.dynamicdiscord.de/kalipso/zineshop/services"
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
)
type ConfigController interface {
AddConfigHandler(*gin.Context)
ConfigHandler(*gin.Context)
ConfigView(*gin.Context)
GetAllPaper(*gin.Context)
PaperView(*gin.Context)
PaperHandler(*gin.Context)
AddPaperHandler(*gin.Context)
InvoiceView(*gin.Context)
InvoiceHandler(*gin.Context)
CreateTag(*gin.Context)
GetAllTags(*gin.Context)
TagView(*gin.Context)
TagHandler(*gin.Context)
AddTagHandler(*gin.Context)
}
type configController struct{}
func NewConfigController() ConfigController {
return &configController{}
}
func (rc *configController) AddConfigHandler(c *gin.Context) {
key := c.PostForm("key")
value := c.PostForm("value")
if key == "" || value == "" {
err := "Key or Value empty during config creation"
fmt.Println(err)
c.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err})
return
}
config := models.Config{
Key: key,
Value: value,
}
_, err := repositories.ConfigOptions.Create(config)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "configview.html", data)
return
}
rc.ConfigView(c)
}
func (rc *configController) ConfigView(c *gin.Context) {
configOptions, err := repositories.ConfigOptions.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "configview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"configOptions": configOptions,
})
if err != nil {
c.HTML(http.StatusBadRequest, "configview.html", data)
}
c.HTML(http.StatusOK, "configview.html", data)
}
func (rc *configController) ConfigHandler(ctx *gin.Context) {
key := ctx.PostForm("key")
value := ctx.PostForm("value")
action := ctx.PostForm("action")
config, err := repositories.ConfigOptions.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err})
return
}
if action == "update" {
config.Key = key
config.Value = value
config, err = repositories.ConfigOptions.Update(config)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.ConfigOptions.DeleteById(ctx.Param("id"))
}
rc.ConfigView(ctx)
}
func (rc *configController) PaperHandler(ctx *gin.Context) {
newPaper, err := models.NewPaper(ctx)
action := ctx.PostForm("action")
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
paper, err := repositories.Papers.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
if action == "update" {
paper.Name = newPaper.Name
paper.Brand = newPaper.Brand
paper.Size = newPaper.Size
paper.Weight = newPaper.Weight
paper.Price = newPaper.Price
paper, err = repositories.Papers.Update(paper)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.Papers.DeleteById(ctx.Param("id"))
}
rc.PaperView(ctx)
}
func (rc *configController) AddPaperHandler(c *gin.Context) {
paper, err := models.NewPaper(c)
if err != nil {
fmt.Println(err)
c.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
_, err = repositories.Papers.Create(paper)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "paperview.html", data)
return
}
rc.PaperView(c)
}
func (rc *configController) PaperView(c *gin.Context) {
papers, err := repositories.Papers.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "paperview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"paper": papers,
})
if err != nil {
c.HTML(http.StatusBadRequest, "paperview.html", data)
}
c.HTML(http.StatusOK, "paperview.html", data)
}
func (rc *configController) GetAllPaper(c *gin.Context) {
papers, err := repositories.Papers.GetAll()
if err != nil {
ReplyError(c, fmt.Errorf("Could not query Papers"))
return
}
c.JSON(http.StatusOK, papers)
}
//////////////////////////////////////////////////////////////////////
func (rc *configController) InvoiceView(c *gin.Context) {
invoices, err := repositories.Invoices.GetAllNewestFirst()
if err != nil {
c.HTML(http.StatusBadRequest, "invoiceview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"invoices": invoices,
})
if err != nil {
c.HTML(http.StatusBadRequest, "invoiceview.html", data)
}
c.HTML(http.StatusOK, "invoiceview.html", data)
}
func (rc *configController) InvoiceHandler(ctx *gin.Context) {
action := ctx.PostForm("action")
if action == "delete" {
repositories.Invoices.DeleteById(ctx.Param("id"))
}
rc.InvoiceView(ctx)
}
//////////////////////////////////////////////////////////////////////
func (rc *configController) TagHandler(ctx *gin.Context) {
name := ctx.PostForm("name")
color := ctx.PostForm("color")
action := ctx.PostForm("action")
tag, err := repositories.Tags.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
if action == "update" {
tag.Name = name
tag.Color = color
tag, err = repositories.Tags.Update(tag)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.Tags.DeleteById(ctx.Param("id"))
}
rc.TagView(ctx)
}
func (rc *configController) AddTagHandler(c *gin.Context) {
tag, err := models.NewTag(c)
if err != nil {
fmt.Println(err)
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
_, err = repositories.Tags.Create(tag)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "tagview.html", data)
return
}
rc.TagView(c)
}
func (rc *configController) TagView(c *gin.Context) {
tags, err := repositories.Tags.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"tags": tags,
})
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", data)
}
c.HTML(http.StatusOK, "tagview.html", data)
}
func (rc *configController) CreateTag(c *gin.Context) {
tag, err := models.NewTagByJson(c)
if err != nil {
ReplyError(c, err)
}
_, err = repositories.Tags.Create(tag)
if err != nil {
ReplyError(c, fmt.Errorf("tag creation failed: %s", err))
return
}
//userID := user.(models.User).ID
//rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID})
//if result.Error != nil {
// ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error))
// return
//}
ReplyOK(c, fmt.Sprintf("tag '%s' was created", tag.Name))
}
func (rc *configController) GetAllTags(c *gin.Context) {
tags, err := repositories.Tags.GetAll()
if err != nil {
ReplyError(c, fmt.Errorf("Could not query Tags"))
return
}
c.JSON(http.StatusOK, tags)
}

View File

@@ -63,7 +63,14 @@ func (rc *printController) PrintCartView(c *gin.Context) {
return return
} }
paper, err := repositories.Papers.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
data := CreateSessionData(c, gin.H{ data := CreateSessionData(c, gin.H{
"paper": paper,
"cartItems": cartItems, "cartItems": cartItems,
}) })
@@ -78,9 +85,16 @@ func (rc *printController) PrintOrderView(c *gin.Context) {
return return
} }
paper, err := repositories.Papers.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
cartItems := order.CartItems cartItems := order.CartItems
data := CreateSessionData(c, gin.H{ data := CreateSessionData(c, gin.H{
"paper": paper,
"cartItems": cartItems, "cartItems": cartItems,
}) })
@@ -90,6 +104,7 @@ func (rc *printController) PrintOrderView(c *gin.Context) {
func (rc *printController) PrintHandler(c *gin.Context) { func (rc *printController) PrintHandler(c *gin.Context) {
variantIds := c.PostFormArray("variant-id[]") variantIds := c.PostFormArray("variant-id[]")
variantAmounts := c.PostFormArray("variant-amount[]") variantAmounts := c.PostFormArray("variant-amount[]")
variantPapertypes := c.PostFormArray("variant-papertype[]")
variantCoverPages := c.PostFormArray("variant-coverpage[]") variantCoverPages := c.PostFormArray("variant-coverpage[]")
if len(variantIds) != len(variantAmounts) || len(variantIds) != len(variantCoverPages) { if len(variantIds) != len(variantAmounts) || len(variantIds) != len(variantCoverPages) {
@@ -98,6 +113,7 @@ func (rc *printController) PrintHandler(c *gin.Context) {
} }
var printJobs []models.PrintJob var printJobs []models.PrintJob
priceTotal := 0.0
for idx := range variantIds { for idx := range variantIds {
variant, err := repositories.ShopItems.GetVariantById(variantIds[idx]) variant, err := repositories.ShopItems.GetVariantById(variantIds[idx])
@@ -113,9 +129,21 @@ func (rc *printController) PrintHandler(c *gin.Context) {
return return
} }
coverPage := false paperType, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantPapertypes[idx]))
if variantCoverPages[idx] == "1" { if err != nil {
coverPage = true c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
var coverPage *models.Paper
if variantCoverPages[idx] != "0" {
coverPageTmp, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantCoverPages[idx]))
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
coverPage = &coverPageTmp
} }
variantAmount, err := strconv.Atoi(variantAmounts[idx]) variantAmount, err := strconv.Atoi(variantAmounts[idx])
@@ -124,23 +152,56 @@ func (rc *printController) PrintHandler(c *gin.Context) {
return return
} }
printJob, err := models.NewPrintJob(shopItem, variant, coverPage, uint(variantAmount)) fmt.Println("Printing Costs:")
printJob, err := models.NewPrintJob(shopItem, variant, paperType, coverPage, uint(variantAmount))
if err != nil { if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return return
} }
printJob.CalculatePrintCosts()
priceTotal += printJob.PriceTotal
printJobs = append(printJobs, printJob) printJobs = append(printJobs, printJob)
} }
invoice := models.Invoice{
PrintJobs: printJobs,
PricePerClick: 0.002604,
PartCosts: 0.0067,
PriceTotal: priceTotal,
}
invoice, err := repositories.Invoices.Create(invoice)
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
executeJobs := func() { executeJobs := func() {
for _, printJob := range printJobs { for _, printJob := range printJobs {
printJob.Execute() err := printJob.Execute()
if err == nil {
return
}
printJob.ShopItem.WasPrinted = true
_, err = repositories.ShopItems.Update(printJob.ShopItem)
if err != nil {
fmt.Printf("Error: %s\n", err)
}
} }
} }
go executeJobs() go executeJobs()
c.HTML(http.StatusOK, "index.html", nil) fmt.Println(invoice)
data := CreateSessionData(c, gin.H{
"invoice": invoice,
})
c.HTML(http.StatusOK, "printstarted.html", data)
} }

View File

@@ -29,15 +29,10 @@ type ShopItemController interface {
AddItemHandler(*gin.Context) AddItemHandler(*gin.Context)
AddItemsView(*gin.Context) AddItemsView(*gin.Context)
AddItemsHandler(*gin.Context) AddItemsHandler(*gin.Context)
CreateTag(*gin.Context)
GetAllTags(*gin.Context)
EditItemView(*gin.Context) EditItemView(*gin.Context)
EditItemHandler(*gin.Context) EditItemHandler(*gin.Context)
DeleteItemView(*gin.Context) DeleteItemView(*gin.Context)
DeleteItemHandler(*gin.Context) DeleteItemHandler(*gin.Context)
TagView(*gin.Context)
TagHandler(*gin.Context)
AddTagHandler(*gin.Context)
} }
type shopItemController struct{} type shopItemController struct{}
@@ -166,6 +161,7 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop
Pdf: dstPdf, Pdf: dstPdf,
Variants: variants, Variants: variants,
PrintMode: printMode, PrintMode: printMode,
WasPrinted: false,
} }
fmt.Println("Creating Shopitem: ", shopItem) fmt.Println("Creating Shopitem: ", shopItem)
@@ -405,6 +401,7 @@ func (rc *shopItemController) AddItemsHandler(c *gin.Context) {
Pdf: dstPdf, Pdf: dstPdf,
Variants: variants, Variants: variants,
PrintMode: "CreateBooklet", PrintMode: "CreateBooklet",
WasPrinted: false,
} }
_, err = repositories.ShopItems.Create(shopItem) _, err = repositories.ShopItems.Create(shopItem)
@@ -542,6 +539,7 @@ func (rc *shopItemController) EditItemHandler(c *gin.Context) {
newShopItem.Variants = shopItem.Variants newShopItem.Variants = shopItem.Variants
newShopItem.BasePrice = shopItem.BasePrice newShopItem.BasePrice = shopItem.BasePrice
newShopItem.IsPublic = shopItem.IsPublic newShopItem.IsPublic = shopItem.IsPublic
newShopItem.WasPrinted = false
if len(shopItem.Tags) != 0 { if len(shopItem.Tags) != 0 {
newShopItem.Tags = shopItem.Tags newShopItem.Tags = shopItem.Tags
@@ -621,115 +619,6 @@ func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", data) c.HTML(http.StatusOK, "index.html", data)
} }
func (rc *shopItemController) TagHandler(ctx *gin.Context) {
name := ctx.PostForm("name")
color := ctx.PostForm("color")
action := ctx.PostForm("action")
tag, err := repositories.Tags.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
if action == "update" {
tag.Name = name
tag.Color = color
tag, err = repositories.Tags.Update(tag)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.Tags.DeleteById(ctx.Param("id"))
}
rc.TagView(ctx)
}
func (rc *shopItemController) AddTagHandler(c *gin.Context) {
tag, err := models.NewTag(c)
if err != nil {
fmt.Println(err)
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
_, err = repositories.Tags.Create(tag)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "tagview.html", data)
return
}
rc.TagView(c)
}
func (rc *shopItemController) TagView(c *gin.Context) {
tags, err := repositories.Tags.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"tags": tags,
})
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", data)
}
c.HTML(http.StatusOK, "tagview.html", data)
}
func (rc *shopItemController) CreateTag(c *gin.Context) {
tag, err := models.NewTagByJson(c)
if err != nil {
ReplyError(c, err)
}
_, err = repositories.Tags.Create(tag)
if err != nil {
ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
return
}
//userID := user.(models.User).ID
//rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID})
//if result.Error != nil {
// ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error))
// return
//}
ReplyOK(c, fmt.Sprintf("tag '%s' was created", tag.Name))
}
func (rc *shopItemController) GetAllTags(c *gin.Context) {
tags, err := repositories.Tags.GetAll()
if err != nil {
ReplyError(c, fmt.Errorf("Could not query Tags"))
return
}
c.JSON(http.StatusOK, tags)
}
func ReplyError(ctx *gin.Context, err error) { func ReplyError(ctx *gin.Context, err error) {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} }

15
go.mod
View File

@@ -6,7 +6,8 @@ require (
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.23.0 github.com/pdfcpu/pdfcpu v0.11.0
golang.org/x/crypto v0.38.0
gorm.io/driver/sqlite v1.5.7 gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
) )
@@ -22,22 +23,30 @@ 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.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/image v0.27.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.25.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

31
go.sum
View File

@@ -30,6 +30,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
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/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -46,6 +52,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -53,10 +61,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pdfcpu/pdfcpu v0.11.0 h1:mL18Y3hSHzSezmnrzA21TqlayBOXuAx7BUzzZyroLGM=
github.com/pdfcpu/pdfcpu v0.11.0/go.mod h1:F1ca4GIVFdPtmgvIdvXAycAm88noyNxZwzr9CpTy+Mw=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -76,22 +91,26 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

20
main.go
View File

@@ -19,6 +19,7 @@ var (
userController controllers.UserController = controllers.UserController{} userController controllers.UserController = controllers.UserController{}
cartItemController controllers.CartItemController = controllers.NewCartItemController() cartItemController controllers.CartItemController = controllers.NewCartItemController()
printController controllers.PrintController = controllers.NewPrintController() printController controllers.PrintController = controllers.NewPrintController()
configController controllers.ConfigController = controllers.NewConfigController()
authValidator middlewares.AuthValidator = middlewares.AuthValidator{} authValidator middlewares.AuthValidator = middlewares.AuthValidator{}
) )
@@ -67,10 +68,23 @@ func main() {
viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView) viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView)
viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler) viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler)
viewRoutes.GET("/tags", authValidator.RequireAdmin, shopItemController.TagView) viewRoutes.GET("/config", authValidator.RequireAdmin, configController.ConfigView)
viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, shopItemController.TagHandler) viewRoutes.POST("/config/:id", authValidator.RequireAdmin, configController.ConfigHandler)
viewRoutes.POST("/config", authValidator.RequireAdmin, configController.AddConfigHandler)
viewRoutes.GET("/tags", authValidator.RequireAdmin, configController.TagView)
viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, configController.TagHandler)
viewRoutes.GET("/tags/:id", userController.TagView) viewRoutes.GET("/tags/:id", userController.TagView)
viewRoutes.POST("/tags", authValidator.RequireAdmin, shopItemController.AddTagHandler) viewRoutes.POST("/tags", authValidator.RequireAdmin, configController.AddTagHandler)
viewRoutes.GET("/paper", authValidator.RequireAdmin, configController.PaperView)
viewRoutes.POST("/paper/:id", authValidator.RequireAdmin, configController.PaperHandler)
viewRoutes.GET("/paper/:id", userController.TagView)
viewRoutes.POST("/paper", authValidator.RequireAdmin, configController.AddPaperHandler)
viewRoutes.GET("/invoice", authValidator.RequireAdmin, configController.InvoiceView)
viewRoutes.POST("/invoice/:id", authValidator.RequireAdmin, configController.InvoiceHandler)
viewRoutes.GET("/cart", authValidator.RequireAuth, cartItemController.CartItemView) viewRoutes.GET("/cart", authValidator.RequireAuth, cartItemController.CartItemView)
viewRoutes.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler) viewRoutes.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler)
viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler) viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler)

11
models/config.go Normal file
View File

@@ -0,0 +1,11 @@
package models
import (
"gorm.io/gorm"
)
type Config struct {
gorm.Model
Key string `json:"key" binding:"required" gorm:"unique;not null"`
Value string `json:"value" binding:"required"`
}

83
models/paper.go Normal file
View File

@@ -0,0 +1,83 @@
package models
import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
"strings"
)
type PaperSize string
const (
A3 PaperSize = "A3"
A4 PaperSize = "A4"
A5 PaperSize = "A5"
SRA3 PaperSize = "SRA3"
)
func ParseSize(s string) (c PaperSize, err error) {
s = strings.ToUpper(s)
if s == "A3" {
return A3, nil
} else if s == "A4" {
return A4, nil
} else if s == "A5" {
return A5, nil
} else if s == "SRA3" {
return SRA3, nil
}
return c, fmt.Errorf("Cannot parse category %s", s)
}
type Paper struct {
gorm.Model
Name string `json:"name" binding:"required" gorm:"not null"`
Brand string `json:"brand" binding:"required"`
Size PaperSize `json:"size" binding:"required"`
Weight int `json:"weight" binding:"required"`
Price float64 `json:"price" binding:"required"`
}
func NewPaper(ctx *gin.Context) (Paper, error) {
name := ctx.PostForm("name")
brand := ctx.PostForm("brand")
sizeTmp := ctx.PostForm("size")
weightTmp := ctx.PostForm("weight")
priceTmp := ctx.PostForm("price")
size, err := ParseSize(sizeTmp)
if err != nil {
return Paper{}, fmt.Errorf("Couldnt parse Size")
}
weight, err := strconv.Atoi(weightTmp)
if err != nil {
return Paper{}, fmt.Errorf("Couldnt parse Weight")
}
price, err := strconv.ParseFloat(priceTmp, 64)
if err != nil {
return Paper{}, fmt.Errorf("Couldnt parse Price")
}
if name == "" || brand == "" {
return Paper{}, fmt.Errorf("Name or brand empty")
}
// Convert the price string to float64
tag := Paper{
Name: name,
Brand: brand,
Size: size,
Weight: weight,
Price: price,
}
return tag, nil
}

View File

@@ -2,6 +2,9 @@ package models
import ( import (
"fmt" "fmt"
"git.dynamicdiscord.de/kalipso/zineshop/utils"
"gorm.io/gorm"
"math"
"os/exec" "os/exec"
"strings" "strings"
) )
@@ -18,12 +21,36 @@ const (
TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding" TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding"
) )
type PrintJob struct { type OldPrintJob struct {
Pdf string Pdf string
Amount uint Amount uint
Options []PrintOption Options []PrintOption
} }
type Invoice struct {
gorm.Model
PrintJobs []PrintJob
PricePerClick float64
PartCosts float64
PriceTotal float64
}
type PrintJob struct {
gorm.Model
ShopItemID uint
ShopItem ShopItem
VariantID uint
Variant ItemVariant
PaperTypeId uint
PaperType Paper `gorm:"foreignKey:PaperTypeId"`
CoverPaperTypeId *uint
CoverPaperType *Paper `gorm:"foreignKey:CoverPaperTypeId"`
Amount uint
PricePerPiece float64
PriceTotal float64
InvoiceID uint
}
func GetPrintMode(mode string) PrintOption { func GetPrintMode(mode string) PrintOption {
if mode == "LongEdge" { if mode == "LongEdge" {
return LongEdge return LongEdge
@@ -40,7 +67,7 @@ func GetPrintMode(mode string) PrintOption {
return CreateBooklet return CreateBooklet
} }
func NewPrintJob(shopItem ShopItem, variant ItemVariant, coverPage bool, amount uint) (PrintJob, error) { func NewPrintJob(shopItem ShopItem, variant ItemVariant, paperType Paper, coverPaperType *Paper, amount uint) (PrintJob, error) {
if shopItem.Pdf == "" { if shopItem.Pdf == "" {
return PrintJob{}, fmt.Errorf("ShopItem has no PDF assigned") return PrintJob{}, fmt.Errorf("ShopItem has no PDF assigned")
} }
@@ -49,32 +76,44 @@ func NewPrintJob(shopItem ShopItem, variant ItemVariant, coverPage bool, amount
return PrintJob{}, fmt.Errorf("Amount to big. This is denied for security reasons") return PrintJob{}, fmt.Errorf("Amount to big. This is denied for security reasons")
} }
var result PrintJob result := PrintJob{
result.Pdf = shopItem.Pdf ShopItem: shopItem,
result.Amount = amount Variant: variant,
PaperType: paperType,
if variant.Name == "Colored" { CoverPaperType: coverPaperType,
result.Options = append(result.Options, Colored) Amount: amount,
} }
if coverPage {
result.Options = append(result.Options, CoverPage)
}
result.Options = append(result.Options, GetPrintMode(shopItem.PrintMode))
return result, nil return result, nil
} }
func (p *PrintJob) IsColored() bool {
return p.Variant.Name == "Colored"
}
func (p *PrintJob) GeneratePrintOptions() []PrintOption {
var result []PrintOption
if p.Variant.Name == "Colored" {
result = append(result, Colored)
}
if p.CoverPaperType != nil {
result = append(result, CoverPage)
}
result = append(result, GetPrintMode(p.ShopItem.PrintMode))
return result
}
func (p *PrintJob) Execute() error { func (p *PrintJob) Execute() error {
baseCommand := "lp -d KonicaBooklet" baseCommand := "lp -d KonicaBooklet"
baseCommand += fmt.Sprintf(" -n %v ", p.Amount) baseCommand += fmt.Sprintf(" -n %v ", p.Amount)
for _, option := range p.Options { for _, option := range p.GeneratePrintOptions() {
baseCommand += fmt.Sprintf(" %v ", option) baseCommand += fmt.Sprintf(" %v ", option)
} }
baseCommand += fmt.Sprintf(" -- %s", p.Pdf) baseCommand += fmt.Sprintf(" -- %s", p.ShopItem.Pdf)
parts := strings.Fields(baseCommand) parts := strings.Fields(baseCommand)
@@ -91,3 +130,51 @@ func (p *PrintJob) Execute() error {
fmt.Printf("Output:\n%s\n", output) fmt.Printf("Output:\n%s\n", output)
return nil return nil
} }
func (p *PrintJob) CalculatePrintCosts() (float64, error) {
pageCount := utils.CountPagesAtPath(p.ShopItem.Pdf)
if pageCount == 0 {
fmt.Println("Pagecount of 0 - something is wrong here.")
return 0, fmt.Errorf("Cant calculate price, pdf seems to be empty")
}
printMode := GetPrintMode(p.ShopItem.PrintMode)
//Get actual pagecount depending on printmode
actualPageCount := pageCount
fmt.Println("PagCount: ", actualPageCount)
if printMode == CreateBooklet {
dividedCount := float64(pageCount) / 4.0
actualPageCount = int(math.Ceil(dividedCount))
}
if printMode == LongEdge || printMode == ShortEdge {
dividedCount := float64(pageCount) / 2.0
actualPageCount = int(math.Ceil(dividedCount))
}
PPC := 0.002604
partCost := 0.0067
if p.IsColored() {
partCost = 0.0478
}
printingCosts := float64(actualPageCount-1) * p.PaperType.Price
if p.CoverPaperType != nil {
printingCosts += p.CoverPaperType.Price
} else {
printingCosts += p.PaperType.Price
}
printingCosts += float64(actualPageCount/2) * PPC
printingCosts += partCost * float64(actualPageCount)
fmt.Printf("Printing Costs per Zine: %v\n", printingCosts)
fmt.Printf("Printing Costs Total: %v\n", printingCosts*float64(p.Amount))
p.PricePerPiece = printingCosts
p.PriceTotal = printingCosts * float64(p.Amount)
return printingCosts, nil
}

View File

@@ -43,7 +43,7 @@ type ItemVariant struct {
type ShopItem struct { type ShopItem struct {
gorm.Model gorm.Model
Name string `json:"name" binding:"required" gorm:"unique;not null"` Name string `json:"name" binding:"required" gorm:"unique;not null"`
Abstract string `json:"Abstract" binding:"required"` Abstract string `json:"abstract" binding:"required"`
Description string `json:"description" binding:"required"` Description string `json:"description" binding:"required"`
Category Category `json:"category"` Category Category `json:"category"`
Variants []ItemVariant `json:"variant"` Variants []ItemVariant `json:"variant"`
@@ -53,4 +53,5 @@ type ShopItem struct {
Image string Image string
Pdf string Pdf string
PrintMode string `json:"printMode" gorm:"default:CreateBooklet"` PrintMode string `json:"printMode" gorm:"default:CreateBooklet"`
WasPrinted bool `gorm:"default:false"`
} }

View File

@@ -0,0 +1,100 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type InvoiceRepository interface {
Create(models.Invoice) (models.Invoice, error)
GetAll() ([]models.Invoice, error)
GetAllSorted(string) ([]models.Invoice, error)
GetAllNewestFirst() ([]models.Invoice, error)
GetAllNewestLast() ([]models.Invoice, error)
GetById(string) (models.Invoice, error)
//GetByInvoiceId(string) (models.Invoice, error)
Update(models.Invoice) (models.Invoice, error)
DeleteById(string) error
}
type GORMInvoiceRepository struct {
DB *gorm.DB
}
func NewGORMInvoiceRepository(db *gorm.DB) InvoiceRepository {
return &GORMInvoiceRepository{
DB: db,
}
}
func (t *GORMInvoiceRepository) Create(invoice models.Invoice) (models.Invoice, error) {
result := t.DB.Create(&invoice)
if result.Error != nil {
return models.Invoice{}, result.Error
}
return invoice, nil
}
func (t *GORMInvoiceRepository) GetAll() ([]models.Invoice, error) {
var invoice []models.Invoice
result := t.DB.Preload("PrintJobs.ShopItem").Preload("PrintJobs.Variant").Preload("PrintJobs.PaperType").Preload("PrintJobs.CoverPaperType").Preload("PrintJobs").Find(&invoice)
return invoice, result.Error
}
func (t *GORMInvoiceRepository) GetAllSorted(sortString string) ([]models.Invoice, error) {
var invoices []models.Invoice
result := t.DB.Preload("PrintJobs.ShopItem").Preload("PrintJobs.Variant").Preload("PrintJobs.PaperType").Preload("PrintJobs.CoverPaperType").Preload("PrintJobs").Order(sortString).Find(&invoices)
return invoices, result.Error
}
func (r *GORMInvoiceRepository) GetAllNewestFirst() ([]models.Invoice, error) {
return r.GetAllSorted("created_at desc")
}
func (r *GORMInvoiceRepository) GetAllNewestLast() ([]models.Invoice, error) {
return r.GetAllSorted("created_at asc")
}
func (t *GORMInvoiceRepository) GetById(id string) (models.Invoice, error) {
invoiceId, err := strconv.Atoi(id)
if err != nil {
return models.Invoice{}, err
}
var invoice models.Invoice
result := t.DB.Preload("PrintJobs").First(&invoice, uint(invoiceId))
if result.Error != nil {
return models.Invoice{}, result.Error
}
return invoice, nil
}
func (t *GORMInvoiceRepository) Update(invoice models.Invoice) (models.Invoice, error) {
result := t.DB.Save(&invoice)
if result.Error != nil {
return models.Invoice{}, result.Error
}
return invoice, nil
}
func (t *GORMInvoiceRepository) DeleteById(id string) error {
invoiceId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.Invoice{}, invoiceId)
return result.Error
}

View File

@@ -0,0 +1,81 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type ConfigRepository interface {
Create(models.Config) (models.Config, error)
GetAll() ([]models.Config, error)
GetById(string) (models.Config, error)
Update(models.Config) (models.Config, error)
DeleteById(string) error
}
type GORMConfigRepository struct {
DB *gorm.DB
}
func NewGORMConfigRepository(db *gorm.DB) ConfigRepository {
return &GORMConfigRepository{
DB: db,
}
}
func (t *GORMConfigRepository) Create(config models.Config) (models.Config, error) {
result := t.DB.Create(&config)
if result.Error != nil {
return models.Config{}, result.Error
}
return config, nil
}
func (t *GORMConfigRepository) GetAll() ([]models.Config, error) {
var configs []models.Config
result := t.DB.Find(&configs)
return configs, result.Error
}
func (t *GORMConfigRepository) GetById(id string) (models.Config, error) {
configId, err := strconv.Atoi(id)
if err != nil {
return models.Config{}, err
}
var config models.Config
result := t.DB.First(&config, uint(configId))
if result.Error != nil {
return models.Config{}, result.Error
}
return config, nil
}
func (t *GORMConfigRepository) Update(config models.Config) (models.Config, error) {
result := t.DB.Save(&config)
if result.Error != nil {
return models.Config{}, result.Error
}
return config, nil
}
func (t *GORMConfigRepository) DeleteById(id string) error {
configId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.Config{}, configId)
return result.Error
}

View File

@@ -0,0 +1,82 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type PaperRepository interface {
Create(models.Paper) (models.Paper, error)
GetAll() ([]models.Paper, error)
GetById(string) (models.Paper, error)
//GetByShopItemId(string) (models.Paper, error)
Update(models.Paper) (models.Paper, error)
DeleteById(string) error
}
type GORMPaperRepository struct {
DB *gorm.DB
}
func NewGORMPaperRepository(db *gorm.DB) PaperRepository {
return &GORMPaperRepository{
DB: db,
}
}
func (t *GORMPaperRepository) Create(tag models.Paper) (models.Paper, error) {
result := t.DB.Create(&tag)
if result.Error != nil {
return models.Paper{}, result.Error
}
return tag, nil
}
func (t *GORMPaperRepository) GetAll() ([]models.Paper, error) {
var tags []models.Paper
result := t.DB.Find(&tags)
return tags, result.Error
}
func (t *GORMPaperRepository) GetById(id string) (models.Paper, error) {
tagId, err := strconv.Atoi(id)
if err != nil {
return models.Paper{}, err
}
var tag models.Paper
result := t.DB.First(&tag, uint(tagId))
if result.Error != nil {
return models.Paper{}, result.Error
}
return tag, nil
}
func (t *GORMPaperRepository) Update(tag models.Paper) (models.Paper, error) {
result := t.DB.Save(&tag)
if result.Error != nil {
return models.Paper{}, result.Error
}
return tag, nil
}
func (t *GORMPaperRepository) DeleteById(id string) error {
tagId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.Paper{}, tagId)
return result.Error
}

View File

@@ -0,0 +1,82 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type PrintJobRepository interface {
Create(models.PrintJob) (models.PrintJob, error)
GetAll() ([]models.PrintJob, error)
GetById(string) (models.PrintJob, error)
//GetByShopItemId(string) (models.PrintJob, error)
Update(models.PrintJob) (models.PrintJob, error)
DeleteById(string) error
}
type GORMPrintJobRepository struct {
DB *gorm.DB
}
func NewGORMPrintJobRepository(db *gorm.DB) PrintJobRepository {
return &GORMPrintJobRepository{
DB: db,
}
}
func (t *GORMPrintJobRepository) Create(printJob models.PrintJob) (models.PrintJob, error) {
result := t.DB.Create(&printJob)
if result.Error != nil {
return models.PrintJob{}, result.Error
}
return printJob, nil
}
func (t *GORMPrintJobRepository) GetAll() ([]models.PrintJob, error) {
var printJobs []models.PrintJob
result := t.DB.Preload("ShopItem").Preload("Variant").Preload("PaperType").Preload("CoverPaperType").Find(&printJobs)
return printJobs, result.Error
}
func (t *GORMPrintJobRepository) GetById(id string) (models.PrintJob, error) {
printJobId, err := strconv.Atoi(id)
if err != nil {
return models.PrintJob{}, err
}
var printJob models.PrintJob
result := t.DB.Preload("ShopItem").Preload("Variant").Preload("PaperType").Preload("CoverPaperType").First(&printJob, uint(printJobId))
if result.Error != nil {
return models.PrintJob{}, result.Error
}
return printJob, nil
}
func (t *GORMPrintJobRepository) Update(printJob models.PrintJob) (models.PrintJob, error) {
result := t.DB.Save(&printJob)
if result.Error != nil {
return models.PrintJob{}, result.Error
}
return printJob, nil
}
func (t *GORMPrintJobRepository) DeleteById(id string) error {
printJobId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.PrintJob{}, printJobId)
return result.Error
}

View File

@@ -9,12 +9,16 @@ import (
) )
var ( var (
ShopItems ShopItemRepository ShopItems ShopItemRepository
Users UserRepository Users UserRepository
Tags TagRepository Tags TagRepository
CartItems CartItemRepository CartItems CartItemRepository
Orders OrderRepository Orders OrderRepository
Tokens RegisterTokenRepository Tokens RegisterTokenRepository
ConfigOptions ConfigRepository
Papers PaperRepository
PrintJobs PrintJobRepository
Invoices InvoiceRepository
) )
func InitRepositories() { func InitRepositories() {
@@ -29,6 +33,10 @@ func InitRepositories() {
&models.Tag{}, &models.Tag{},
&models.CartItem{}, &models.CartItem{},
&models.Order{}, &models.Order{},
&models.Config{},
&models.Paper{},
&models.PrintJob{},
&models.Invoice{},
&models.RegisterToken{}) &models.RegisterToken{})
if err != nil { if err != nil {
@@ -41,4 +49,8 @@ func InitRepositories() {
CartItems = NewGORMCartItemRepository(db) CartItems = NewGORMCartItemRepository(db)
Orders = NewGORMOrderRepository(db) Orders = NewGORMOrderRepository(db)
Tokens = NewGORMRegisterTokenRepository(db) Tokens = NewGORMRegisterTokenRepository(db)
ConfigOptions = NewGORMConfigRepository(db)
Papers = NewGORMPaperRepository(db)
PrintJobs = NewGORMPrintJobRepository(db)
Invoices = NewGORMInvoiceRepository(db)
} }

View File

@@ -591,6 +591,10 @@ video {
grid-column: span 3 / span 3; grid-column: span 3 / span 3;
} }
.-m-1\.5 {
margin: -0.375rem;
}
.m-2 { .m-2 {
margin: 0.5rem; margin: 0.5rem;
} }
@@ -678,10 +682,18 @@ video {
display: block; display: block;
} }
.inline-block {
display: inline-block;
}
.flex { .flex {
display: flex; display: flex;
} }
.inline-flex {
display: inline-flex;
}
.table { .table {
display: table; display: table;
} }
@@ -758,6 +770,10 @@ video {
width: 100%; width: 100%;
} }
.min-w-full {
min-width: 100%;
}
.max-w-2xl { .max-w-2xl {
max-width: 42rem; max-width: 42rem;
} }
@@ -766,6 +782,10 @@ video {
max-width: 48rem; max-width: 48rem;
} }
.max-w-4xl {
max-width: 56rem;
}
.max-w-6xl { .max-w-6xl {
max-width: 72rem; max-width: 72rem;
} }
@@ -810,6 +830,14 @@ video {
grid-template-columns: repeat(12, minmax(0, 1fr)); grid-template-columns: repeat(12, minmax(0, 1fr));
} }
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-cols-6 { .grid-cols-6 {
grid-template-columns: repeat(6, minmax(0, 1fr)); grid-template-columns: repeat(6, minmax(0, 1fr));
} }
@@ -818,6 +846,10 @@ video {
flex-direction: column; flex-direction: column;
} }
.flex-wrap {
flex-wrap: wrap;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@@ -830,6 +862,10 @@ video {
justify-content: space-between; justify-content: space-between;
} }
.gap-2 {
gap: 0.5rem;
}
.gap-4 { .gap-4 {
gap: 1rem; gap: 1rem;
} }
@@ -892,6 +928,10 @@ video {
border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1));
} }
.overflow-hidden {
overflow: hidden;
}
.overflow-x-auto { .overflow-x-auto {
overflow-x: auto; overflow-x: auto;
} }
@@ -900,6 +940,19 @@ video {
white-space: nowrap; white-space: nowrap;
} }
.break-normal {
overflow-wrap: normal;
word-break: normal;
}
.break-words {
overflow-wrap: break-word;
}
.break-all {
word-break: break-all;
}
.rounded { .rounded {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
@@ -974,6 +1027,11 @@ video {
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
} }
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
}
.bg-blue-600 { .bg-blue-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
@@ -1049,6 +1107,16 @@ video {
background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1)); background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
} }
.bg-green-50 {
--tw-bg-opacity: 1;
background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1));
}
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1));
}
.bg-green-600 { .bg-green-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1)); background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
@@ -1134,6 +1202,16 @@ video {
background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1)); background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
} }
.bg-red-50 {
--tw-bg-opacity: 1;
background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
}
.bg-red-800 { .bg-red-800 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1)); background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1));
@@ -1242,6 +1320,10 @@ video {
object-fit: cover; object-fit: cover;
} }
.p-1\.5 {
padding: 0.375rem;
}
.p-2 { .p-2 {
padding: 0.5rem; padding: 0.5rem;
} }
@@ -1289,6 +1371,11 @@ video {
padding-bottom: 0.125rem; padding-bottom: 0.125rem;
} }
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.py-1\.5 { .py-1\.5 {
padding-top: 0.375rem; padding-top: 0.375rem;
padding-bottom: 0.375rem; padding-bottom: 0.375rem;
@@ -1319,6 +1406,11 @@ video {
padding-bottom: 6rem; padding-bottom: 6rem;
} }
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.py-4 { .py-4 {
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
@@ -1365,6 +1457,18 @@ video {
text-align: right; text-align: right;
} }
.text-start {
text-align: start;
}
.text-end {
text-align: end;
}
.align-middle {
vertical-align: middle;
}
.text-2xl { .text-2xl {
font-size: 1.5rem; font-size: 1.5rem;
line-height: 2rem; line-height: 2rem;
@@ -1436,6 +1540,10 @@ video {
font-weight: 600; font-weight: 600;
} }
.uppercase {
text-transform: uppercase;
}
.leading-10 { .leading-10 {
line-height: 2.5rem; line-height: 2.5rem;
} }
@@ -1547,6 +1655,11 @@ video {
color: rgb(34 197 94 / var(--tw-text-opacity, 1)); color: rgb(34 197 94 / var(--tw-text-opacity, 1));
} }
.text-green-700 {
--tw-text-opacity: 1;
color: rgb(21 128 61 / var(--tw-text-opacity, 1));
}
.text-green-800 { .text-green-800 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity, 1)); color: rgb(22 101 52 / var(--tw-text-opacity, 1));
@@ -1627,6 +1740,11 @@ video {
color: rgb(239 68 68 / var(--tw-text-opacity, 1)); color: rgb(239 68 68 / var(--tw-text-opacity, 1));
} }
.text-red-700 {
--tw-text-opacity: 1;
color: rgb(185 28 28 / var(--tw-text-opacity, 1));
}
.text-red-800 { .text-red-800 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity, 1)); color: rgb(153 27 27 / var(--tw-text-opacity, 1));
@@ -1722,27 +1840,22 @@ video {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.shadow-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-xl {
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-2xl { .shadow-2xl {
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color); --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.shadow-red-900 { .shadow-md {
--tw-shadow-color: #7f1d1d; --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tw-shadow: var(--tw-shadow-colored); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.outline { .outline {
@@ -1761,6 +1874,24 @@ video {
outline-color: #d1d5db; outline-color: #d1d5db;
} }
.ring-1 {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.ring-inset {
--tw-ring-inset: inset;
}
.ring-green-600\/20 {
--tw-ring-color: rgb(22 163 74 / 0.2);
}
.ring-red-600\/10 {
--tw-ring-color: rgb(220 38 38 / 0.1);
}
.filter { .filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
} }
@@ -1849,6 +1980,11 @@ video {
background-color: rgb(67 56 202 / var(--tw-bg-opacity, 1)); background-color: rgb(67 56 202 / var(--tw-bg-opacity, 1));
} }
.hover\:bg-red-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));
}
.hover\:bg-red-900:hover { .hover\:bg-red-900:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(127 29 29 / var(--tw-bg-opacity, 1)); background-color: rgb(127 29 29 / var(--tw-bg-opacity, 1));
@@ -2102,6 +2238,10 @@ video {
max-width: 80rem; max-width: 80rem;
} }
.lg\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.lg\:grid-cols-3 { .lg\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
@@ -2126,12 +2266,20 @@ video {
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {
.xl\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.xl\:grid-cols-4 { .xl\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
} }
@media (min-width: 1536px) { @media (min-width: 1536px) {
.\32xl\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.\32xl\:px-0 { .\32xl\:px-0 {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px;
@@ -2148,6 +2296,11 @@ video {
border-color: rgb(31 41 55 / var(--tw-divide-opacity, 1)); border-color: rgb(31 41 55 / var(--tw-divide-opacity, 1));
} }
.dark\:divide-neutral-700 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1;
border-color: rgb(64 64 64 / var(--tw-divide-opacity, 1));
}
.dark\:border-gray-600 { .dark\:border-gray-600 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity, 1)); border-color: rgb(75 85 99 / var(--tw-border-opacity, 1));
@@ -2203,6 +2356,16 @@ video {
color: rgb(17 24 39 / var(--tw-text-opacity, 1)); color: rgb(17 24 39 / var(--tw-text-opacity, 1));
} }
.dark\:text-neutral-200 {
--tw-text-opacity: 1;
color: rgb(229 229 229 / var(--tw-text-opacity, 1));
}
.dark\:text-neutral-500 {
--tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity, 1));
}
.dark\:text-white { .dark\:text-white {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1)); color: rgb(255 255 255 / var(--tw-text-opacity, 1));

View File

@@ -3,6 +3,8 @@ package utils
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt"
"github.com/pdfcpu/pdfcpu/pkg/api"
) )
func GenerateSessionId(length int) string { func GenerateSessionId(length int) string {
@@ -17,3 +19,14 @@ func GenerateSessionId(length int) string {
func GenerateToken() string { func GenerateToken() string {
return GenerateSessionId(16) return GenerateSessionId(16)
} }
func CountPagesAtPath(path string) (pages int) {
ctx, err := api.ReadContextFile(path)
if err != nil {
fmt.Println("Error reading PDF:", err)
return
}
pages = ctx.PageCount
return
}

33
views/configview.html Normal file
View File

@@ -0,0 +1,33 @@
{{ template "header.html" . }}
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<img class="mx-auto h-10 w-auto" src="/static/img/logo-black.png" alt="Your Company">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Edit config options</h2>
</div>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
{{ range .data.configOptions }}
<form action="/config/{{ .ID }}" method="POST">
<div class="max-w-md mx-auto mt-4">
<div class="flex">
<input type="text" id="key" name="key" value="{{ .Key }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<input type="text" id="value" name="value" value="{{ .Value }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<button type="submit" name="action" value="update" class="bg-blue-600 text-white ml-4 mr-4 rounded px-4 hover:bg-blue-700">Update</button>
<button type="submit" name="action" value="delete" class="bg-red-800 text-white rounded px-4 hover:bg-red-900">Delete</button>
</div>
</form>
{{ end }}
<form action="/config" method="POST">
<div class="max-w-md mx-auto mt-4">
<div class="flex">
<input type="text" id="key" name="key" placeholder="" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<input type="text" id="value" name="value" placeholder="" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<button type="submit" class="bg-green-600 text-white ml-4 mr-4 rounded px-4 hover:bg-green-700">Add</button>
</div>
</div>
</form>
</div>
{{ template "footer.html" . }}

View File

@@ -38,6 +38,12 @@
hover:text-white">Print</a> hover:text-white">Print</a>
<a href="/cart" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 <a href="/cart" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700
hover:text-white">Cart</a> hover:text-white">Cart</a>
<a href="/config" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700
hover:text-white">Config</a>
<a href="/paper" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700
hover:text-white">Paper</a>
<a href="/invoice" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700
hover:text-white">Invoices</a>
<a href="/logout" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-red-300 hover:bg-gray-700 hover:text-white">Logout</a> <a href="/logout" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-red-300 hover:bg-gray-700 hover:text-white">Logout</a>
</div> </div>
{{ end }} {{ end }}

55
views/invoice.html Normal file
View File

@@ -0,0 +1,55 @@
<div class="-m-1.5 overflow-x-auto">
<div class="p-1.5 min-w-full inline-block align-middle">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-gray-200 dark:divide-neutral-700">
<thead>
<tr>
<th scope="col" class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-neutral-500"></th>
<th scope="col" class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-neutral-500">Name</th>
<th scope="col" class="px-6 py-3 text-end text-xs font-medium text-gray-500 uppercase dark:text-neutral-500">Paper</th>
<th scope="col" class="px-6 py-3 text-end text-xs font-medium text-gray-500 uppercase dark:text-neutral-500">Amount</th>
<th scope="col" class="px-6 py-3 text-end text-xs font-medium text-gray-500 uppercase dark:text-neutral-500">Price</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-neutral-700">
{{ range .PrintJobs }}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-neutral-200">
<a href="/shopitems/{{ .Variant.ShopItemID }}" class="flex items-center aspect-square w-8 h-10 shrink-0">
<img class="h-auto w-full max-h-full dark:hidden" src="/{{ .ShopItem.Image }}" alt="imac image" />
</a>
</td>
<td class="px-6 py-4 text-sm text-gray-800 dark:text-neutral-200">
<div class="text-sm break-normal">
<p>{{ .ShopItem.Name }}</p>
<p>{{ .Variant.Name }}</p>
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-800 dark:text-neutral-200">
<div class="text-xs">
<p>{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g</p>
{{ if .CoverPaperType }}
<p class="text-red-700">{{ .CoverPaperType.Brand }} - {{.CoverPaperType.Name }}: {{ .CoverPaperType.Size }} {{ .CoverPaperType.Weight }}g</p>
{{ end }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-neutral-200">{{ .Amount }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-neutral-200">{{ .PriceTotal }}</td>
</tr>
{{ end }}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-neutral-200">
<b>TOTAL</b>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-neutral-200"></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-neutral-200"></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-neutral-200"></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 dark:text-neutral-200"><b>{{ .PriceTotal }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

29
views/invoiceview.html Normal file
View File

@@ -0,0 +1,29 @@
{{ template "header.html" . }}
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<img class="mx-auto h-10 w-auto" src="/static/img/logo-black.png" alt="Your Company">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Invoices</h2>
</div>
{{ range .data.invoices }}
<div class="w-full grid grid-cols-1 gap-4 mx-auto p-4 m-4 flex flex-wrap border rounded shadow-md max-w-4xl">
<div class="">
<div class="font-bold text-center mb-4">
Invoice #{{ .ID }} - {{ .CreatedAt }}
</div>
</div>
<div class="flex flex-col">
{{ template "invoice.html" . }}
</div>
<div class="grid grid-cols-2 mt-4 flex flex-wrap gap-2 w-full">
<form action="/invoice/{{ .ID }}" method="POST">
<button type="submit" name="action" value="delete" class="bg-red-500 hover:bg-red-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Delete</button>
</form>
</div>
</div>
{{ end }}
</div>
</div>
{{ template "footer.html" . }}

82
views/paperview.html Normal file
View File

@@ -0,0 +1,82 @@
{{ template "header.html" . }}
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<img class="mx-auto h-10 w-auto" src="/static/img/logo-black.png" alt="Your Company">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Edit Paper</h2>
</div>
<div class="mt-10 ">
<div class="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
{{ range .data.paper }}
<form action="/paper/{{ .ID }}" method="POST">
<div class="max-w-md mx-auto p-4 m-4 bg-gray-100 flex flex-wrap border rounded shadow-md">
<div class="font-bold text-center mb-4">
{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Name</label>
<input type="text" id="name" name="name" value="{{ .Name }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Brand</label>
<input type="text" id="brand" name="brand" value="{{ .Brand }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">DIN Size</label>
<input type="text" id="size" name="size" value="{{ .Size }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Weight</label>
<input type="text" id="weight" name="weight" value="{{ .Weight }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price per Sheet</label>
<input type="number" step="0.0001" min="0.0000" id="price" name="price" value="{{ .Price }}" class="w-full flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
</div>
<div class="grid grid-cols-2 mt-4 flex flex-wrap gap-2 w-full">
<button type="submit" name="action" value="update" class="bg-blue-500 hover:bg-blue-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Update</button>
<button type="submit" name="action" value="delete" class="bg-red-500 hover:bg-red-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Delete</button>
</div>
</div>
</form>
{{ end }}
</div>
<form action="/paper" method="POST">
<div class="max-w-md mx-auto p-4 flex flex-wrap border rounded shadow-md">
<div class="font-bold text-center mb-4">
Add new Paper
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Name</label>
<input type="text" id="name" name="name" placeholder="name" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Brand</label>
<input type="text" id="brand" name="brand" placeholder="brand" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">DIN Size</label>
<input type="text" id="size" name="size" placeholder="size" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Weight</label>
<input type="text" id="weight" name="weight" placeholder="Weight" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price per Sheet</label>
<input type="number" step="0.0001" min="0.0000" id="price" name="price" placeholder="price per sheet"
class="w-full flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
</div>
<div class="grid grid-cols-4 mt-4 flex flex-wrap gap-2 w-full">
<button type="submit" class="bg-green-500 hover:bg-green-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Add</button>
</div>
</div>
</form>
</div>
{{ template "footer.html" . }}

22
views/printstarted.html Normal file
View File

@@ -0,0 +1,22 @@
{{ template "header.html" . }}
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16">
<div class="mx-auto max-w-3xl">
<div class="mt-6 sm:mt-8">
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800">
<h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Print Started
</h2>
<div class="w-full grid grid-cols-1 gap-4 mx-auto p-4 m-4 flex flex-wrap border rounded shadow-md">
<div class="flex flex-col">
{{ template "invoice.html" .data.invoice }}
</div>
<div class="grid grid-cols-2 mt-4 flex flex-wrap gap-2 w-full">
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{{ template "footer.html" . }}

View File

@@ -1,7 +1,7 @@
{{ template "header.html" . }} {{ template "header.html" . }}
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16"> <section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16">
<div class="mx-auto max-w-3xl"> <div class="mx-auto max-w-4xl">
<div class="mt-6 sm:mt-8"> <div class="mt-6 sm:mt-8">
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800"> <div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800">
<h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Zineshop Print <h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Zineshop Print
@@ -15,7 +15,7 @@
<p class="font-normal text-base leading-7 text-gray-500 text-left mb-5 mt-6"> <p class="font-normal text-base leading-7 text-gray-500 text-left mb-5 mt-6">
<bold>CoverPage</bold>: If selected, the Printer will take Paper from the BypassTray for the first page. For <bold>CoverPage</bold>: If selected, the Printer will take Paper from the BypassTray for the first page. For
example you can put colored paper there to have a nice looking front page, and the rest will be normal paper. example you can put colored paper there to have a nice looking front page, and the rest will be normal paper.
Makue sure you put paper in that tray when selecting this option.<br><br> Make sure you put paper in that tray when selecting this option.<br><br>
Print Order: The Zines will be printed from top to bottom as seen in this list. Print Order: The Zines will be printed from top to bottom as seen in this list.
@@ -33,14 +33,30 @@
<a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="flex items-center aspect-square w-8 h-10 shrink-0"> <a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="flex items-center aspect-square w-8 h-10 shrink-0">
<img class="h-auto w-full max-h-full dark:hidden" src="/{{ .ShopItem.Image }}" alt="imac image" /> <img class="h-auto w-full max-h-full dark:hidden" src="/{{ .ShopItem.Image }}" alt="imac image" />
</a> </a>
{{ if .ShopItem.WasPrinted }}
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-green-600/20 ring-inset">Tested</span>
{{ else }}
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-red-600/10 ring-inset">NOT TESTED</span>
{{ end }}
<a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a> <a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a>
</div> </div>
</td> </td>
<td class="whitespace-nowrap py-4">
<select name="variant-papertype[]" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
{{ range $.data.paper }}
<option value="{{ .ID }}">{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2</option>
{{ end}}
</select>
</td>
<td class="whitespace-nowrap py-4"> <td class="whitespace-nowrap py-4">
<select name="variant-coverpage[]" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select name="variant-coverpage[]" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<option selected value="0">Normal</option> <option selected value="0">CoverPage: None</option>
<option value="1">CoverPage</option> {{ range $.data.paper }}
<option value="{{ .ID }}">{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2</option>
{{ end}}
</select> </select>
</td> </td>
<td class="whitespace-nowrap py-4"> <td class="whitespace-nowrap py-4">