Compare commits
31 Commits
19ce41aca7
...
more-print
| Author | SHA1 | Date | |
|---|---|---|---|
|
134730396a
|
|||
|
5f53d66bc4
|
|||
|
459c873986
|
|||
|
ef2e6c99a7
|
|||
|
e29287c29d
|
|||
| f55470636f | |||
|
8f89c14961
|
|||
|
6f5c0354cc
|
|||
|
6ef7c53001
|
|||
|
d4e7401586
|
|||
|
1688e61ccb
|
|||
|
861343b338
|
|||
|
68c8654bf3
|
|||
|
e62a45372f
|
|||
|
d17c33f6ee
|
|||
|
ae36903e73
|
|||
|
1a5df21fa8
|
|||
|
9c15514758
|
|||
|
27cf7c37cf
|
|||
|
03f1ce361a
|
|||
| c55cf4480b | |||
|
bcbb091dfb
|
|||
|
adfb3df283
|
|||
|
1525f44687
|
|||
|
3955d8626a
|
|||
|
6d63e53200
|
|||
|
cca0b2775c
|
|||
|
98c75c111f
|
|||
|
b2735e178f
|
|||
|
1c9fc230b1
|
|||
|
6c2b3964fe
|
@@ -1,8 +1,6 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -15,6 +13,7 @@ import (
|
|||||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
//"git.dynamicdiscord.de/kalipso/zineshop/services"
|
//"git.dynamicdiscord.de/kalipso/zineshop/services"
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||||
|
"git.dynamicdiscord.de/kalipso/zineshop/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CartItemController interface {
|
type CartItemController interface {
|
||||||
@@ -53,15 +52,6 @@ func getSetCookieValue(c *gin.Context, cookieName string) string {
|
|||||||
return "" // Return empty string if cookie is not found
|
return "" // Return empty string if cookie is not found
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSessionId(length int) string {
|
|
||||||
bytes := make([]byte, length) // 16 bytes = 128 bits
|
|
||||||
_, err := rand.Read(bytes)
|
|
||||||
if err != nil {
|
|
||||||
panic("failed to generate session ID")
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSessionId(ctx *gin.Context) string {
|
func GetSessionId(ctx *gin.Context) string {
|
||||||
sessionId, err := ctx.Cookie("session_id")
|
sessionId, err := ctx.Cookie("session_id")
|
||||||
|
|
||||||
@@ -73,17 +63,13 @@ func GetSessionId(ctx *gin.Context) string {
|
|||||||
return responseCookie
|
return responseCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionId = generateSessionId(16)
|
sessionId = utils.GenerateSessionId(16)
|
||||||
ctx.SetCookie("session_id", sessionId, 3600, "/", "", false, true)
|
ctx.SetCookie("session_id", sessionId, 3600, "/", "", false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionId
|
return sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateToken() string {
|
|
||||||
return generateSessionId(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *cartItemController) NewCartItemFromForm(ctx *gin.Context) (models.CartItem, error) {
|
func (rc *cartItemController) NewCartItemFromForm(ctx *gin.Context) (models.CartItem, error) {
|
||||||
sessionId := GetSessionId(ctx)
|
sessionId := GetSessionId(ctx)
|
||||||
shopItemIdStr := ctx.PostForm("ShopItemId")
|
shopItemIdStr := ctx.PostForm("ShopItemId")
|
||||||
@@ -162,7 +148,7 @@ func (rc *cartItemController) NewAddressFromForm(ctx *gin.Context) (models.Addre
|
|||||||
func (rc *cartItemController) NewOrderFromForm(ctx *gin.Context) (models.Order, error) {
|
func (rc *cartItemController) NewOrderFromForm(ctx *gin.Context) (models.Order, error) {
|
||||||
sessionId := GetSessionId(ctx)
|
sessionId := GetSessionId(ctx)
|
||||||
status := models.OrderStatus("AwaitingConfirmation")
|
status := models.OrderStatus("AwaitingConfirmation")
|
||||||
token := GenerateToken()
|
token := utils.GenerateToken()
|
||||||
email := ctx.PostForm("email")
|
email := ctx.PostForm("email")
|
||||||
comment := ctx.PostForm("comment")
|
comment := ctx.PostForm("comment")
|
||||||
firstName := ctx.PostForm("firstName")
|
firstName := ctx.PostForm("firstName")
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ func (rc *shopItemController) GetById(c *gin.Context) {
|
|||||||
ReplyOK(c, shopItem)
|
ReplyOK(c, shopItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this currently creates quite big preview images
|
||||||
|
// workaround is running the following command in the uploads folder:
|
||||||
|
// for file in *.png; do convert "$file" -resize 35% "$file"; done
|
||||||
func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.ShopItem, error) {
|
func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.ShopItem, error) {
|
||||||
defaultImagePath := "static/img/zine.jpg"
|
defaultImagePath := "static/img/zine.jpg"
|
||||||
name := ctx.PostForm("name")
|
name := ctx.PostForm("name")
|
||||||
@@ -106,6 +109,14 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error during pdftoppm: ", err.Error())
|
fmt.Println("Error during pdftoppm: ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd2 := exec.Command("convert", dstImage, "-resize", "35%", dstImage)
|
||||||
|
_, err = cmd2.Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error during resizing preview image: ", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -249,14 +260,16 @@ func (rc *shopItemController) ShopItemView(c *gin.Context) {
|
|||||||
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "shopitem.html", gin.H{"data": gin.H{"error": err}})
|
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": "Item does not exist"}})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: get tags by item
|
//TODO: get tags by item
|
||||||
tags, err := repositories.Tags.GetAll()
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "shopitem.html", gin.H{"data": gin.H{"error": err}})
|
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
data := CreateSessionData(c, gin.H{
|
||||||
@@ -264,10 +277,6 @@ func (rc *shopItemController) ShopItemView(c *gin.Context) {
|
|||||||
"tags": tags,
|
"tags": tags,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.HTML(http.StatusBadRequest, "shopitem.html", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "shopitem.html", data)
|
c.HTML(http.StatusOK, "shopitem.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,6 +374,13 @@ func (rc *shopItemController) AddItemsHandler(c *gin.Context) {
|
|||||||
fmt.Println("Error during pdftoppm: ", err.Error())
|
fmt.Println("Error during pdftoppm: ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd2 := exec.Command("convert", dstImage, "-resize", "35%", dstImage)
|
||||||
|
_, err = cmd2.Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error during resizing preview image: ", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
category, err := models.ParseCategory("Zine")
|
category, err := models.ParseCategory("Zine")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorHandler(err)
|
errorHandler(err)
|
||||||
@@ -414,23 +430,93 @@ func (rc *shopItemController) AddItemsHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "batchupload.html", data)
|
c.HTML(http.StatusOK, "batchupload.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *shopItemController) EditItemView(c *gin.Context) {
|
func GetCheckedTags(item models.ShopItem) ([]models.CheckedTag, error) {
|
||||||
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
allTags, err := repositories.Tags.GetAll()
|
||||||
tags, err := repositories.Tags.GetAll()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(shopItem)
|
var tags []models.CheckedTag
|
||||||
|
for _, tag := range allTags {
|
||||||
|
tmpTag := models.CheckedTag{
|
||||||
|
Tag: tag,
|
||||||
|
Checked: "",
|
||||||
|
}
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
for _, itemTag := range item.Tags {
|
||||||
"error": "",
|
if itemTag.Name == tmpTag.Name {
|
||||||
"success": "",
|
tmpTag.Checked = "checked"
|
||||||
"shopItem": shopItem,
|
break
|
||||||
"tags": tags,
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
tags = append(tags, tmpTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *shopItemController) getEditTemplatData(shopItem models.ShopItem) (gin.H, error) {
|
||||||
|
tags, err := GetCheckedTags(shopItem)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return gin.H{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
priceBW := ""
|
||||||
|
priceColored := ""
|
||||||
|
for _, variant := range shopItem.Variants {
|
||||||
|
if variant.Name == "B/W" {
|
||||||
|
priceBW = fmt.Sprintf("%.2f", variant.Price)
|
||||||
|
}
|
||||||
|
|
||||||
|
if variant.Name == "Colored" {
|
||||||
|
priceColored = fmt.Sprintf("%.2f", variant.Price)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData := gin.H{
|
||||||
|
"error": "",
|
||||||
|
"success": "",
|
||||||
|
"shopItem": shopItem,
|
||||||
|
"tags": tags,
|
||||||
|
"priceBW": priceBW,
|
||||||
|
"priceColored": priceColored,
|
||||||
|
}
|
||||||
|
|
||||||
|
id := fmt.Sprintf("%d", shopItem.ID)
|
||||||
|
nextShopItem, err := repositories.ShopItems.GetNextOfId(id)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("Setting nextitem")
|
||||||
|
fmt.Println(nextShopItem)
|
||||||
|
templateData["nextShopItem"] = nextShopItem
|
||||||
|
}
|
||||||
|
|
||||||
|
previousShopItem, err := repositories.ShopItems.GetPreviousOfId(id)
|
||||||
|
if err == nil {
|
||||||
|
templateData["previousShopItem"] = previousShopItem
|
||||||
|
}
|
||||||
|
|
||||||
|
return templateData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *shopItemController) EditItemView(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
shopItem, err := repositories.ShopItems.GetById(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData, err := rc.getEditTemplatData(shopItem)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||||
|
}
|
||||||
|
|
||||||
|
data := CreateSessionData(c, templateData)
|
||||||
c.HTML(http.StatusOK, "edititem.html", data)
|
c.HTML(http.StatusOK, "edititem.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,30 +557,22 @@ func (rc *shopItemController) EditItemHandler(c *gin.Context) {
|
|||||||
|
|
||||||
newShopItem.PrintMode = shopItem.PrintMode
|
newShopItem.PrintMode = shopItem.PrintMode
|
||||||
|
|
||||||
tags, err := repositories.Tags.GetAll()
|
templateData, err := rc.getEditTemplatData(newShopItem)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
|
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = repositories.ShopItems.Update(newShopItem)
|
_, err = repositories.ShopItems.Update(newShopItem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data := CreateSessionData(c, gin.H{
|
templateData["error"] = err
|
||||||
"error": err,
|
data := CreateSessionData(c, templateData)
|
||||||
"success": "",
|
|
||||||
"tags": tags,
|
|
||||||
})
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "edititem.html", data)
|
c.HTML(http.StatusOK, "edititem.html", data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
templateData["success"] = fmt.Sprintf("Item '%s' Updated", newShopItem.Name)
|
||||||
"error": "",
|
data := CreateSessionData(c, templateData)
|
||||||
"success": fmt.Sprintf("Item '%s' Updated", newShopItem.Name),
|
|
||||||
"tags": tags,
|
|
||||||
})
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "edititem.html", data)
|
c.HTML(http.StatusOK, "edititem.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||||
@@ -36,7 +37,7 @@ func (uc *UserController) Register(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = services.Users.Register(body.Name, body.Email, body.Password)
|
_, err = services.Users.Register(body.Name, body.Email, body.Password, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error: ", err)
|
fmt.Println("Error: ", err)
|
||||||
@@ -140,10 +141,12 @@ func (rc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateSessionData(c *gin.Context, extra any) gin.H {
|
func CreateSessionData(c *gin.Context, extra any) gin.H {
|
||||||
_, exists := c.Get("user")
|
user, exists := c.Get("user")
|
||||||
|
userImpl, _ := user.(models.User)
|
||||||
|
|
||||||
return gin.H{
|
return gin.H{
|
||||||
"loggedIn": exists,
|
"loggedIn": exists,
|
||||||
|
"isAdmin": userImpl.IsAdmin,
|
||||||
"data": extra,
|
"data": extra,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,11 +156,34 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
|||||||
email := c.PostForm("email")
|
email := c.PostForm("email")
|
||||||
password := c.PostForm("password")
|
password := c.PostForm("password")
|
||||||
|
|
||||||
_, err := services.Users.Register(name, email, password)
|
//first registered user is admin
|
||||||
|
isEmpty, _ := repositories.Users.IsEmpty()
|
||||||
|
if isEmpty {
|
||||||
|
_, err := services.Users.Register(name, email, password, true)
|
||||||
|
if err != nil {
|
||||||
|
data := gin.H{
|
||||||
|
"error": "Registering Failed.",
|
||||||
|
"success": "",
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"error": "Registering Failed.",
|
"error": "",
|
||||||
|
"success": "You successfully registered as Admin. Try logging in.",
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//for any other user token is required
|
||||||
|
token := c.PostForm("token")
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
data := gin.H{
|
||||||
|
"error": "No token. No register.",
|
||||||
"success": "",
|
"success": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +191,43 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokenExists, err := repositories.Tokens.Exists(token)
|
||||||
|
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
data := gin.H{
|
||||||
|
"error": err,
|
||||||
|
"success": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tokenExists {
|
||||||
|
data := gin.H{
|
||||||
|
"error": "Invalid Token.",
|
||||||
|
"success": "",
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = services.Users.Register(name, email, password, false)
|
||||||
|
if err != nil {
|
||||||
|
data := gin.H{
|
||||||
|
"error": "Registering Failed.",
|
||||||
|
"success": "",
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "register.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repositories.Tokens.Delete(token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Could not delete RegisterToken: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"error": "",
|
"error": "",
|
||||||
"success": "You successfully registered. Try logging in.",
|
"success": "You successfully registered. Try logging in.",
|
||||||
@@ -174,6 +237,39 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rc *UserController) RegisterView(c *gin.Context) {
|
func (rc *UserController) RegisterView(c *gin.Context) {
|
||||||
|
data := gin.H{
|
||||||
|
"error": "",
|
||||||
|
"success": "",
|
||||||
|
"token": c.Param("token"),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "registertoken.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *UserController) InitAdmin(c *gin.Context) {
|
||||||
|
isEmpty, err := repositories.Users.IsEmpty()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
data := gin.H{
|
||||||
|
"error": err,
|
||||||
|
"success": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusInternalServerError, "error.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isEmpty {
|
||||||
|
data := gin.H{
|
||||||
|
"error": "Registration is closed",
|
||||||
|
"success": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusInternalServerError, "error.html", data)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"error": "",
|
"error": "",
|
||||||
"success": "",
|
"success": "",
|
||||||
@@ -204,9 +300,53 @@ func (rc *UserController) ResetHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "passwordreset.html", data)
|
c.HTML(http.StatusOK, "passwordreset.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *UserController) InviteView(c *gin.Context) {
|
||||||
|
tokens, _ := repositories.Tokens.GetAll()
|
||||||
|
fmt.Println(tokens)
|
||||||
|
|
||||||
|
data := gin.H{
|
||||||
|
"tokens": tokens,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "invites.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *UserController) InviteHandler(c *gin.Context) {
|
||||||
|
action := c.PostForm("action")
|
||||||
|
|
||||||
|
if action == "create" {
|
||||||
|
_, err := repositories.Tokens.Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if action == "delete" {
|
||||||
|
token := c.PostForm("token")
|
||||||
|
repositories.Tokens.Delete(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.InviteView(c)
|
||||||
|
}
|
||||||
|
|
||||||
func (rc *UserController) MainView(c *gin.Context) {
|
func (rc *UserController) MainView(c *gin.Context) {
|
||||||
shopItems, _ := repositories.ShopItems.GetAll()
|
itemOrder := c.Query("order")
|
||||||
fmt.Println(len(shopItems))
|
|
||||||
|
var shopItems []models.ShopItem
|
||||||
|
if itemOrder == "newestFirst" {
|
||||||
|
shopItems, _ = repositories.ShopItems.GetAllNewestFirst()
|
||||||
|
} else if itemOrder == "oldestFirst" {
|
||||||
|
shopItems, _ = repositories.ShopItems.GetAllNewestLast()
|
||||||
|
} else if itemOrder == "az" {
|
||||||
|
shopItems, _ = repositories.ShopItems.GetAllLexicalFirst()
|
||||||
|
} else if itemOrder == "za" {
|
||||||
|
shopItems, _ = repositories.ShopItems.GetAllLexicalLast()
|
||||||
|
} else {
|
||||||
|
shopItems, _ = repositories.ShopItems.GetAllNewestFirst()
|
||||||
|
}
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
data := CreateSessionData(c, gin.H{
|
||||||
"title": "shopItem Page",
|
"title": "shopItem Page",
|
||||||
@@ -227,68 +367,6 @@ func (rc *UserController) TagView(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "index.html", data)
|
c.HTML(http.StatusOK, "index.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
type booking struct {
|
|
||||||
Booked bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type calendarbooking struct {
|
|
||||||
Time string
|
|
||||||
Bookings []booking
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *UserController) CalendarView(c *gin.Context) {
|
|
||||||
shopItems, _ := repositories.ShopItems.GetAll()
|
|
||||||
fmt.Println(len(shopItems))
|
|
||||||
|
|
||||||
generateBookings := func(amountShopItems int) []calendarbooking {
|
|
||||||
var result []calendarbooking
|
|
||||||
time := 6
|
|
||||||
for _ = range 18 {
|
|
||||||
book := calendarbooking{
|
|
||||||
Time: fmt.Sprintf("%d:00", time),
|
|
||||||
Bookings: []booking{},
|
|
||||||
}
|
|
||||||
for _ = range amountShopItems {
|
|
||||||
book.Bookings = append(book.Bookings, booking{Booked: rand.Float32() < 0.5})
|
|
||||||
}
|
|
||||||
|
|
||||||
time += 1
|
|
||||||
result = append(result, book)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
bookings := gin.H{
|
|
||||||
"head": []string{
|
|
||||||
"malobeo",
|
|
||||||
"hole of fame",
|
|
||||||
"BK",
|
|
||||||
"AZ Conni",
|
|
||||||
},
|
|
||||||
"bookings": generateBookings(4),
|
|
||||||
//"bookings": []calendarbooking{
|
|
||||||
// {
|
|
||||||
// Time: "10:00",
|
|
||||||
// Bookings: []booking{
|
|
||||||
// { Booked: true },
|
|
||||||
// { Booked: false },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
//},
|
|
||||||
}
|
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
|
||||||
"title": "shopItem Page",
|
|
||||||
"bookings": bookings,
|
|
||||||
"shopItemcount": len(bookings["head"].([]string)) + 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Println(data)
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "calendar.html", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *UserController) Logout(c *gin.Context) {
|
func (rc *UserController) Logout(c *gin.Context) {
|
||||||
c.SetCookie("Authorization", "", -1, "", "", false, true)
|
c.SetCookie("Authorization", "", -1, "", "", false, true)
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{})
|
c.HTML(http.StatusOK, "index.html", gin.H{})
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
go
|
go
|
||||||
gotools
|
gotools
|
||||||
poppler_utils #get first pdf page to png
|
poppler_utils #get first pdf page to png
|
||||||
|
cups
|
||||||
|
imagemagick
|
||||||
tailwindcss
|
tailwindcss
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -71,6 +73,8 @@
|
|||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
zineshop-pkg
|
zineshop-pkg
|
||||||
pkgs.poppler_utils #get first pdf page to png
|
pkgs.poppler_utils #get first pdf page to png
|
||||||
|
pkgs.cups
|
||||||
|
pkgs.imagemagick
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
@@ -100,7 +104,7 @@
|
|||||||
WorkingDirectory = "/var/lib/zineshop";
|
WorkingDirectory = "/var/lib/zineshop";
|
||||||
ExecStart = pkgs.writeScript "start-zineshop" ''
|
ExecStart = pkgs.writeScript "start-zineshop" ''
|
||||||
#! ${pkgs.bash}/bin/bash
|
#! ${pkgs.bash}/bin/bash
|
||||||
PATH="$PATH:${lib.makeBinPath [ pkgs.poppler_utils ]}"
|
PATH="$PATH:${lib.makeBinPath [ pkgs.poppler_utils pkgs.cups pkgs.imagemagick ]}"
|
||||||
${zineshop-pkg}/bin/zineshop
|
${zineshop-pkg}/bin/zineshop
|
||||||
'';
|
'';
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
|
|||||||
76
main.go
76
main.go
@@ -55,66 +55,50 @@ func main() {
|
|||||||
server.Static("/static", os.Getenv("STATIC"))
|
server.Static("/static", os.Getenv("STATIC"))
|
||||||
server.LoadHTMLGlob(fmt.Sprintf("%s/*.html", os.Getenv("VIEWS")))
|
server.LoadHTMLGlob(fmt.Sprintf("%s/*.html", os.Getenv("VIEWS")))
|
||||||
|
|
||||||
apiRoutes := server.Group("/api")
|
|
||||||
//apiRoutes.Use(middlewares.BasicAuth())
|
|
||||||
{
|
|
||||||
apiRoutes.POST("/tags", authValidator.RequireAuth, shopItemController.CreateTag)
|
|
||||||
apiRoutes.GET("/tags", authValidator.OptionalAuth, shopItemController.GetAllTags)
|
|
||||||
apiRoutes.POST("/shopitems", authValidator.RequireAuth, shopItemController.Create)
|
|
||||||
apiRoutes.GET("/shopitems", authValidator.OptionalAuth, shopItemController.GetAll)
|
|
||||||
apiRoutes.GET("/shopitems/:id", authValidator.OptionalAuth, shopItemController.GetById)
|
|
||||||
apiRoutes.PUT("/shopitems/:id", authValidator.RequireAuth, shopItemController.Update)
|
|
||||||
apiRoutes.DELETE("/shopitems/:id", authValidator.RequireAuth, shopItemController.Delete)
|
|
||||||
|
|
||||||
//apiRoutes.GET("/rooms/:id/users", authValidator.RequireAuth, authValidator.RequireRoomAdmin, shopItemController.GetUsers)
|
|
||||||
//apiRoutes.POST("/rooms/:id/users", authValidator.RequireAuth, shopItemController.AddUser)
|
|
||||||
|
|
||||||
apiRoutes.POST("/users/register", userController.Register)
|
|
||||||
apiRoutes.POST("/users/login", userController.Login)
|
|
||||||
apiRoutes.GET("/users/validate", authValidator.OptionalAuth, userController.Validate)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewRoutes := server.Group("/", authValidator.OptionalAuth)
|
viewRoutes := server.Group("/", authValidator.OptionalAuth)
|
||||||
{
|
{
|
||||||
viewRoutes.GET("/", userController.MainView)
|
viewRoutes.GET("/", userController.MainView)
|
||||||
viewRoutes.GET("/shopitems/:id", shopItemController.ShopItemView)
|
viewRoutes.GET("/shopitems/:id", shopItemController.ShopItemView)
|
||||||
viewRoutes.GET("/shopitems/:id/edit", authValidator.RequireAuth, shopItemController.EditItemView)
|
viewRoutes.GET("/shopitems/:id/edit", authValidator.RequireAdmin, shopItemController.EditItemView)
|
||||||
viewRoutes.POST("/shopitems/:id/edit", authValidator.RequireAuth, shopItemController.EditItemHandler)
|
viewRoutes.POST("/shopitems/:id/edit", authValidator.RequireAdmin, shopItemController.EditItemHandler)
|
||||||
viewRoutes.GET("/shopitems/:id/delete", authValidator.RequireAuth, shopItemController.DeleteItemView)
|
viewRoutes.GET("/shopitems/:id/delete", authValidator.RequireAdmin, shopItemController.DeleteItemView)
|
||||||
viewRoutes.POST("/shopitems/:id/delete", authValidator.RequireAuth, shopItemController.DeleteItemHandler)
|
viewRoutes.POST("/shopitems/:id/delete", authValidator.RequireAdmin, shopItemController.DeleteItemHandler)
|
||||||
viewRoutes.GET("/variant/:id/print", authValidator.RequireAuth, printController.PrintVariantView)
|
viewRoutes.GET("/variant/:id/print", authValidator.RequireAdmin, printController.PrintVariantView)
|
||||||
viewRoutes.GET("/cart/print", authValidator.RequireAuth, printController.PrintCartView)
|
viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView)
|
||||||
viewRoutes.POST("/print", authValidator.RequireAuth, printController.PrintHandler)
|
viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler)
|
||||||
|
|
||||||
viewRoutes.GET("/tags", authValidator.RequireAuth, shopItemController.TagView)
|
viewRoutes.GET("/tags", authValidator.RequireAdmin, shopItemController.TagView)
|
||||||
viewRoutes.POST("/tags/:id", authValidator.RequireAuth, shopItemController.TagHandler)
|
viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, shopItemController.TagHandler)
|
||||||
viewRoutes.GET("/tags/:id", userController.TagView)
|
viewRoutes.GET("/tags/:id", userController.TagView)
|
||||||
viewRoutes.POST("/tags", authValidator.RequireAuth, shopItemController.AddTagHandler)
|
viewRoutes.POST("/tags", authValidator.RequireAdmin, shopItemController.AddTagHandler)
|
||||||
viewRoutes.GET("/cart", cartItemController.CartItemView)
|
viewRoutes.GET("/cart", authValidator.RequireAuth, cartItemController.CartItemView)
|
||||||
viewRoutes.POST("/cart", cartItemController.AddItemHandler)
|
viewRoutes.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler)
|
||||||
viewRoutes.POST("/cart/delete", cartItemController.DeleteItemHandler)
|
viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler)
|
||||||
viewRoutes.POST("/cart/edit", cartItemController.EditItemHandler)
|
viewRoutes.POST("/cart/edit", authValidator.RequireAuth, cartItemController.EditItemHandler)
|
||||||
viewRoutes.GET("/checkout", cartItemController.CheckoutView)
|
viewRoutes.GET("/checkout", authValidator.RequireAuth, cartItemController.CheckoutView)
|
||||||
viewRoutes.POST("/checkout", cartItemController.CheckoutHandler)
|
viewRoutes.POST("/checkout", authValidator.RequireAuth, cartItemController.CheckoutHandler)
|
||||||
viewRoutes.POST("/order", cartItemController.OrderHandler)
|
viewRoutes.POST("/order", authValidator.RequireAuth, cartItemController.OrderHandler)
|
||||||
viewRoutes.GET("/order/:token", cartItemController.OrderView)
|
viewRoutes.GET("/order/:token", authValidator.RequireAuth, cartItemController.OrderView)
|
||||||
viewRoutes.GET("/order/:token/print", authValidator.RequireAuth, printController.PrintOrderView)
|
viewRoutes.GET("/order/:token/print", authValidator.RequireAuth, printController.PrintOrderView)
|
||||||
|
|
||||||
viewRoutes.GET("/orders", authValidator.RequireAuth, cartItemController.OrdersView)
|
viewRoutes.GET("/orders", authValidator.RequireAdmin, cartItemController.OrdersView)
|
||||||
viewRoutes.POST("/order/:token/edit", authValidator.RequireAuth, cartItemController.OrdersHandler)
|
viewRoutes.POST("/order/:token/edit", authValidator.RequireAdmin, cartItemController.OrdersHandler)
|
||||||
|
|
||||||
//write middleware that redirects to homescreen on register/login/reset for logged in users
|
//write middleware that redirects to homescreen on register/login/reset for logged in users
|
||||||
viewRoutes.GET("/login", userController.LoginView)
|
viewRoutes.GET("/login", userController.LoginView)
|
||||||
viewRoutes.GET("/logout", userController.Logout)
|
viewRoutes.GET("/logout", userController.Logout)
|
||||||
viewRoutes.GET("/register", userController.RegisterView)
|
viewRoutes.GET("/register", userController.InitAdmin)
|
||||||
viewRoutes.GET("/passwordreset", userController.ResetView)
|
viewRoutes.GET("/register/:token", userController.RegisterView)
|
||||||
viewRoutes.GET("/additem", authValidator.RequireAuth, shopItemController.AddItemView)
|
viewRoutes.GET("/invites", authValidator.RequireAdmin, userController.InviteView)
|
||||||
viewRoutes.GET("/batchupload", authValidator.RequireAuth, shopItemController.AddItemsView)
|
viewRoutes.POST("/invites", authValidator.RequireAdmin, userController.InviteHandler)
|
||||||
|
viewRoutes.GET("/passwordreset", authValidator.RequireAuth, userController.ResetView)
|
||||||
|
viewRoutes.GET("/additem", authValidator.RequireAdmin, shopItemController.AddItemView)
|
||||||
|
viewRoutes.GET("/batchupload", authValidator.RequireAdmin, shopItemController.AddItemsView)
|
||||||
viewRoutes.POST("/login", userController.LoginHandler)
|
viewRoutes.POST("/login", userController.LoginHandler)
|
||||||
viewRoutes.POST("/register", userController.RegisterHandler)
|
viewRoutes.POST("/register", userController.RegisterHandler)
|
||||||
viewRoutes.POST("/additem", authValidator.RequireAuth, shopItemController.AddItemHandler)
|
viewRoutes.POST("/additem", authValidator.RequireAdmin, shopItemController.AddItemHandler)
|
||||||
viewRoutes.POST("/batchupload", authValidator.RequireAuth, shopItemController.AddItemsHandler)
|
viewRoutes.POST("/batchupload", authValidator.RequireAdmin, shopItemController.AddItemsHandler)
|
||||||
viewRoutes.POST("/passwordreset", userController.ResetHandler)
|
viewRoutes.POST("/passwordreset", authValidator.RequireAuth, userController.ResetHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Run(":" + os.Getenv("PORT"))
|
server.Run(":" + os.Getenv("PORT"))
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import(
|
import (
|
||||||
"os"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
//"strconv"
|
//"strconv"
|
||||||
"net/http"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
//"git.dynamicdiscord.de/kalipso/zineshop/models"
|
//"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||||
@@ -70,7 +70,7 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
|||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||||
//Check Expiration
|
//Check Expiration
|
||||||
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
||||||
@@ -78,7 +78,7 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
|||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Find user
|
//Find user
|
||||||
user, err := repositories.Users.GetById(claims["sub"])
|
user, err := repositories.Users.GetById(claims["sub"])
|
||||||
|
|
||||||
@@ -86,15 +86,72 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
|||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Attach to req
|
//Attach to req
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
|
|
||||||
// Coninue
|
// Coninue
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (av *AuthValidator) RequireAdmin(c *gin.Context) {
|
||||||
|
// Get Cookie
|
||||||
|
tokenString, err := c.Cookie("Authorization")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Validate
|
||||||
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
// Don't forget to validate the alg is what you expect:
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
|
||||||
|
return []byte(os.Getenv("SECRET")), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||||
|
//Check Expiration
|
||||||
|
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
||||||
|
//expired
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Find user
|
||||||
|
user, err := repositories.Users.GetById(claims["sub"])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.IsAdmin {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Attach to req
|
||||||
|
c.Set("user", user)
|
||||||
|
|
||||||
|
// Coninue
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,19 +176,19 @@ func (av *AuthValidator) OptionalAuth(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||||
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Find user
|
//Find user
|
||||||
user, err := repositories.Users.GetById(claims["sub"])
|
user, err := repositories.Users.GetById(claims["sub"])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Attach to req
|
//Attach to req
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ import (
|
|||||||
type PrintOption string
|
type PrintOption string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CoverPage PrintOption = "-o FrontCoverPage=Printed -o FrontCoverTray=BypassTray"
|
CoverPage PrintOption = "-o FrontCoverPage=Printed -o FrontCoverTray=BypassTray"
|
||||||
Colored PrintOption = "-o SelectColor=Color"
|
Colored PrintOption = "-o SelectColor=Color"
|
||||||
Grayscale PrintOption = "-o SelectColor=Grayscale"
|
Grayscale PrintOption = "-o SelectColor=Grayscale"
|
||||||
LongEdge PrintOption = ""
|
LongEdge PrintOption = ""
|
||||||
ShortEdge PrintOption = "-o Binding=TopBinding"
|
ShortEdge PrintOption = "-o Binding=TopBinding"
|
||||||
CreateBooklet PrintOption = "-o Combination=Booklet -o PageSize=A5"
|
CreateBooklet PrintOption = "-o MediaType=Thick1 -o Combination=Booklet -o PageSize=A5"
|
||||||
|
TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding"
|
||||||
|
LongEdgeA5 PrintOption = "-o PageSize=A5"
|
||||||
|
ShortEdgeA5 PrintOption = "-o Binding=TopBinding -o PageSize=A5"
|
||||||
|
CreateBookletA5 PrintOption = "-o Combination=Booklet -o PageSize=A6"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PrintJob struct {
|
type PrintJob struct {
|
||||||
@@ -24,6 +28,10 @@ type PrintJob struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetPrintMode(mode string) PrintOption {
|
func GetPrintMode(mode string) PrintOption {
|
||||||
|
if mode == "CreateBooklet" {
|
||||||
|
return CreateBooklet
|
||||||
|
}
|
||||||
|
|
||||||
if mode == "LongEdge" {
|
if mode == "LongEdge" {
|
||||||
return LongEdge
|
return LongEdge
|
||||||
}
|
}
|
||||||
@@ -32,6 +40,22 @@ func GetPrintMode(mode string) PrintOption {
|
|||||||
return ShortEdge
|
return ShortEdge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mode == "CreateBookletA5" {
|
||||||
|
return CreateBookletA5
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == "LongEdgeA5" {
|
||||||
|
return LongEdgeA5
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == "ShortEdgeA5" {
|
||||||
|
return ShortEdgeA5
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == "TriFold" {
|
||||||
|
return TriFold
|
||||||
|
}
|
||||||
|
|
||||||
return CreateBooklet
|
return CreateBooklet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ type Tag struct {
|
|||||||
ShopItems []ShopItem `gorm:"many2many:item_tags;"`
|
ShopItems []ShopItem `gorm:"many2many:item_tags;"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CheckedTag struct {
|
||||||
|
Tag
|
||||||
|
Checked string
|
||||||
|
}
|
||||||
|
|
||||||
func NewTag(ctx *gin.Context) (Tag, error) {
|
func NewTag(ctx *gin.Context) (Tag, error) {
|
||||||
colors := []string{
|
colors := []string{
|
||||||
"red",
|
"red",
|
||||||
|
|||||||
@@ -4,9 +4,15 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RegisterToken struct {
|
||||||
|
gorm.Model
|
||||||
|
Token string `json:"token" binding:"required" gorm:"unique;not null"`
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User 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"`
|
||||||
Password string `json:"password" binding:"required" gorm:"not null"`
|
Password string `json:"password" binding:"required" gorm:"not null"`
|
||||||
Email string `json:"email" binding:"required,email" gorm:"unique;not null"`
|
Email string `json:"email" binding:"required,email" gorm:"unique;not null"`
|
||||||
|
IsAdmin bool `json:"isAdmin" gorm:"default:false;not null"`
|
||||||
}
|
}
|
||||||
|
|||||||
86
repositories/registerTokenRepository.go
Normal file
86
repositories/registerTokenRepository.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
|
"git.dynamicdiscord.de/kalipso/zineshop/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterTokenRepository interface {
|
||||||
|
Create() (models.RegisterToken, error)
|
||||||
|
GetAll() ([]models.RegisterToken, error)
|
||||||
|
Exists(string) (bool, error)
|
||||||
|
Delete(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GORMRegisterTokenRepository struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGORMRegisterTokenRepository(db *gorm.DB) RegisterTokenRepository {
|
||||||
|
return &GORMRegisterTokenRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMRegisterTokenRepository) Create() (models.RegisterToken, error) {
|
||||||
|
token := utils.GenerateToken()
|
||||||
|
|
||||||
|
exists, err := t.Exists(token)
|
||||||
|
if err != nil {
|
||||||
|
return models.RegisterToken{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return t.Create()
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken := models.RegisterToken{
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
result := t.DB.Create(&newToken)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return models.RegisterToken{}, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return newToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMRegisterTokenRepository) GetAll() ([]models.RegisterToken, error) {
|
||||||
|
var tokens []models.RegisterToken
|
||||||
|
result := t.DB.Find(&tokens)
|
||||||
|
|
||||||
|
return tokens, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMRegisterTokenRepository) Exists(tokenString string) (bool, error) {
|
||||||
|
var token models.RegisterToken
|
||||||
|
result := t.DB.First(&token, "token = ?", tokenString)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMRegisterTokenRepository) Delete(token string) error {
|
||||||
|
result := t.DB.Where("token = ?", token).Delete(&models.RegisterToken{})
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
} else if result.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("Token not found, could not be deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ var (
|
|||||||
Tags TagRepository
|
Tags TagRepository
|
||||||
CartItems CartItemRepository
|
CartItems CartItemRepository
|
||||||
Orders OrderRepository
|
Orders OrderRepository
|
||||||
|
Tokens RegisterTokenRepository
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitRepositories() {
|
func InitRepositories() {
|
||||||
@@ -27,7 +28,8 @@ func InitRepositories() {
|
|||||||
&models.User{},
|
&models.User{},
|
||||||
&models.Tag{},
|
&models.Tag{},
|
||||||
&models.CartItem{},
|
&models.CartItem{},
|
||||||
&models.Order{})
|
&models.Order{},
|
||||||
|
&models.RegisterToken{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to migrate database")
|
panic("failed to migrate database")
|
||||||
@@ -38,4 +40,5 @@ func InitRepositories() {
|
|||||||
Tags = NewGORMTagRepository(db)
|
Tags = NewGORMTagRepository(db)
|
||||||
CartItems = NewGORMCartItemRepository(db)
|
CartItems = NewGORMCartItemRepository(db)
|
||||||
Orders = NewGORMOrderRepository(db)
|
Orders = NewGORMOrderRepository(db)
|
||||||
|
Tokens = NewGORMRegisterTokenRepository(db)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package repositories
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -10,8 +11,15 @@ import (
|
|||||||
type ShopItemRepository interface {
|
type ShopItemRepository interface {
|
||||||
Create(models.ShopItem) (models.ShopItem, error)
|
Create(models.ShopItem) (models.ShopItem, error)
|
||||||
GetAll() ([]models.ShopItem, error)
|
GetAll() ([]models.ShopItem, error)
|
||||||
|
GetAllSorted(string) ([]models.ShopItem, error)
|
||||||
|
GetAllNewestFirst() ([]models.ShopItem, error)
|
||||||
|
GetAllNewestLast() ([]models.ShopItem, error)
|
||||||
|
GetAllLexicalFirst() ([]models.ShopItem, error)
|
||||||
|
GetAllLexicalLast() ([]models.ShopItem, error)
|
||||||
GetAllPublic() ([]models.ShopItem, error)
|
GetAllPublic() ([]models.ShopItem, error)
|
||||||
GetById(string) (models.ShopItem, error)
|
GetById(string) (models.ShopItem, error)
|
||||||
|
GetNextOfId(string) (models.ShopItem, error)
|
||||||
|
GetPreviousOfId(string) (models.ShopItem, error)
|
||||||
GetByTagId(string) ([]models.ShopItem, error)
|
GetByTagId(string) ([]models.ShopItem, error)
|
||||||
GetVariantById(string) (models.ItemVariant, error)
|
GetVariantById(string) (models.ItemVariant, error)
|
||||||
Update(models.ShopItem) (models.ShopItem, error)
|
Update(models.ShopItem) (models.ShopItem, error)
|
||||||
@@ -44,6 +52,29 @@ func (r *GORMShopItemRepository) GetAll() ([]models.ShopItem, error) {
|
|||||||
return shopItems, result.Error
|
return shopItems, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetAllSorted(sortString string) ([]models.ShopItem, error) {
|
||||||
|
var shopItems []models.ShopItem
|
||||||
|
result := r.DB.Preload("Tags").Preload("Variants").Order(sortString).Find(&shopItems)
|
||||||
|
|
||||||
|
return shopItems, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetAllNewestFirst() ([]models.ShopItem, error) {
|
||||||
|
return r.GetAllSorted("created_at desc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetAllNewestLast() ([]models.ShopItem, error) {
|
||||||
|
return r.GetAllSorted("created_at asc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetAllLexicalFirst() ([]models.ShopItem, error) {
|
||||||
|
return r.GetAllSorted("name asc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetAllLexicalLast() ([]models.ShopItem, error) {
|
||||||
|
return r.GetAllSorted("name desc")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *GORMShopItemRepository) GetAllPublic() ([]models.ShopItem, error) {
|
func (r *GORMShopItemRepository) GetAllPublic() ([]models.ShopItem, error) {
|
||||||
var shopItems []models.ShopItem
|
var shopItems []models.ShopItem
|
||||||
result := r.DB.Preload("Tags").Preload("Variants").Where("is_public = 1").Find(&shopItems)
|
result := r.DB.Preload("Tags").Preload("Variants").Where("is_public = 1").Find(&shopItems)
|
||||||
@@ -68,6 +99,30 @@ func (r *GORMShopItemRepository) GetById(id string) (models.ShopItem, error) {
|
|||||||
return shopItem, nil
|
return shopItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetNextOfId(id string) (models.ShopItem, error) {
|
||||||
|
var nextItem models.ShopItem
|
||||||
|
if err := r.DB.Where("id > ?", id).Order("id asc").First(&nextItem).Error; err != nil {
|
||||||
|
if err != gorm.ErrRecordNotFound {
|
||||||
|
return models.ShopItem{}, err
|
||||||
|
} else {
|
||||||
|
return models.ShopItem{}, fmt.Errorf("No Item found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextItem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GORMShopItemRepository) GetPreviousOfId(id string) (models.ShopItem, error) {
|
||||||
|
var previousItem models.ShopItem
|
||||||
|
if err := r.DB.Where("id < ?", id).Order("id desc").First(&previousItem).Error; err != nil {
|
||||||
|
if err != gorm.ErrRecordNotFound {
|
||||||
|
return models.ShopItem{}, err
|
||||||
|
} else {
|
||||||
|
return models.ShopItem{}, fmt.Errorf("No Item found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return previousItem, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *GORMShopItemRepository) GetByTagId(id string) ([]models.ShopItem, error) {
|
func (r *GORMShopItemRepository) GetByTagId(id string) ([]models.ShopItem, error) {
|
||||||
tagId, err := strconv.Atoi(id)
|
tagId, err := strconv.Atoi(id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package repositories
|
package repositories
|
||||||
|
|
||||||
import(
|
import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
Create(models.User) (models.User, error)
|
Create(models.User) (models.User, error)
|
||||||
GetByEmail(string) (models.User, error)
|
GetByEmail(string) (models.User, error)
|
||||||
GetById(interface{}) (models.User, error)
|
GetById(interface{}) (models.User, error)
|
||||||
|
IsEmpty() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GORMUserRepository struct {
|
type GORMUserRepository struct {
|
||||||
@@ -22,7 +23,7 @@ func NewGORMUserRepository(db *gorm.DB) UserRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *GORMUserRepository) Create(user models.User) (models.User, error) {
|
func (u *GORMUserRepository) Create(user models.User) (models.User, error) {
|
||||||
result := u.DB.Create(&user)
|
result := u.DB.Create(&user)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
@@ -53,3 +54,18 @@ func (u *GORMUserRepository) GetById(id interface{}) (models.User, error) {
|
|||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *GORMUserRepository) IsEmpty() (bool, error) {
|
||||||
|
var user models.User
|
||||||
|
result := u.DB.First(&user)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
return true, nil
|
||||||
|
} else {
|
||||||
|
return false, result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import(
|
import (
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
|
||||||
@@ -11,13 +11,13 @@ import(
|
|||||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||||
)
|
)
|
||||||
|
|
||||||
var(
|
var (
|
||||||
Users UserService = UserService{}
|
Users UserService = UserService{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct {}
|
type UserService struct{}
|
||||||
|
|
||||||
func (u *UserService) Register(name string, email string, password string) (models.User, error) {
|
func (u *UserService) Register(name string, email string, password string, isAdmin bool) (models.User, error) {
|
||||||
//hash pw
|
//hash pw
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ func (u *UserService) Register(name string, email string, password string) (mode
|
|||||||
return models.User{}, err
|
return models.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user := models.User{Name: name, Email: email, Password: string(hash)}
|
user := models.User{Name: name, Email: email, Password: string(hash), IsAdmin: isAdmin}
|
||||||
_, err = repositories.Users.Create(user)
|
_, err = repositories.Users.Create(user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -35,7 +35,7 @@ func (u *UserService) Register(name string, email string, password string) (mode
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//return jwt tokenstring on success
|
// return jwt tokenstring on success
|
||||||
func (u *UserService) Login(email string, password string) (string, error) {
|
func (u *UserService) Login(email string, password string) (string, error) {
|
||||||
//lookup requested user
|
//lookup requested user
|
||||||
user, err := repositories.Users.GetByEmail(email)
|
user, err := repositories.Users.GetByEmail(email)
|
||||||
|
|||||||
@@ -587,6 +587,10 @@ video {
|
|||||||
grid-column: span 12 / span 12;
|
grid-column: span 12 / span 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col-span-3 {
|
||||||
|
grid-column: span 3 / span 3;
|
||||||
|
}
|
||||||
|
|
||||||
.m-2 {
|
.m-2 {
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -650,10 +654,6 @@ video {
|
|||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ms-2 {
|
|
||||||
margin-inline-start: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-1 {
|
.mt-1 {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -810,12 +810,12 @@ video {
|
|||||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-col {
|
.grid-cols-6 {
|
||||||
flex-direction: column;
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.items-start {
|
.flex-col {
|
||||||
align-items: flex-start;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.items-center {
|
.items-center {
|
||||||
@@ -929,11 +929,6 @@ video {
|
|||||||
border-bottom-left-radius: 0.375rem;
|
border-bottom-left-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-l {
|
|
||||||
border-top-left-radius: 0.25rem;
|
|
||||||
border-bottom-left-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
@@ -1069,9 +1064,9 @@ video {
|
|||||||
background-color: rgb(224 231 255 / var(--tw-bg-opacity, 1));
|
background-color: rgb(224 231 255 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-indigo-300 {
|
.bg-indigo-500 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(165 180 252 / var(--tw-bg-opacity, 1));
|
background-color: rgb(99 102 241 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-indigo-600 {
|
.bg-indigo-600 {
|
||||||
@@ -1234,46 +1229,6 @@ video {
|
|||||||
background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1));
|
background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-indigo-400 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(129 140 248 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-indigo-500 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(99 102 241 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-pink-200 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(251 207 232 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-pink-300 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(249 168 212 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-pink-700 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(190 24 93 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-pink-500 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(236 72 153 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-fuchsia-500 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(217 70 239 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-700 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.fill-red-50 {
|
.fill-red-50 {
|
||||||
fill: #fef2f2;
|
fill: #fef2f2;
|
||||||
}
|
}
|
||||||
@@ -1299,10 +1254,6 @@ video {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-8 {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-2 {
|
.px-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
@@ -1402,10 +1353,6 @@ video {
|
|||||||
padding-top: 1.25rem;
|
padding-top: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pt-8 {
|
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-left {
|
.text-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -1770,10 +1717,6 @@ video {
|
|||||||
color: rgb(39 39 42 / var(--tw-text-opacity, 1));
|
color: rgb(39 39 42 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.underline {
|
|
||||||
text-decoration-line: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.antialiased {
|
.antialiased {
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@@ -1785,6 +1728,23 @@ video {
|
|||||||
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-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 {
|
||||||
|
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||||
|
--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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-red-900 {
|
||||||
|
--tw-shadow-color: #7f1d1d;
|
||||||
|
--tw-shadow: var(--tw-shadow-colored);
|
||||||
|
}
|
||||||
|
|
||||||
.outline {
|
.outline {
|
||||||
outline-style: solid;
|
outline-style: solid;
|
||||||
}
|
}
|
||||||
@@ -1908,10 +1868,6 @@ video {
|
|||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:no-underline:hover {
|
|
||||||
text-decoration-line: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.focus\:z-10:focus {
|
.focus\:z-10:focus {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
@@ -1948,12 +1904,6 @@ video {
|
|||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:ring-2:focus {
|
|
||||||
--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(2px + 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
.focus\:ring-4:focus {
|
.focus\:ring-4:focus {
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
--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(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
@@ -2065,10 +2015,6 @@ video {
|
|||||||
max-width: 24rem;
|
max-width: 24rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sm\:grid-cols-2 {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm\:items-center {
|
.sm\:items-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -2156,26 +2102,10 @@ video {
|
|||||||
max-width: 80rem;
|
max-width: 80rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lg\:max-w-4xl {
|
|
||||||
max-width: 56rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg\:max-w-6xl {
|
|
||||||
max-width: 72rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg\:grid-cols-3 {
|
.lg\:grid-cols-3 {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.lg\:grid-cols-4 {
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg\:grid-cols-2 {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg\:p-8 {
|
.lg\:p-8 {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
@@ -2199,26 +2129,9 @@ video {
|
|||||||
.xl\:grid-cols-4 {
|
.xl\:grid-cols-4 {
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.xl\:grid-cols-8 {
|
|
||||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.xl\:gap-x-8 {
|
|
||||||
-moz-column-gap: 2rem;
|
|
||||||
column-gap: 2rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1536px) {
|
@media (min-width: 1536px) {
|
||||||
.\32xl\:grid-cols-8 {
|
|
||||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.\32xl\:grid-cols-6 {
|
|
||||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.\32xl\:px-0 {
|
.\32xl\:px-0 {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
@@ -2305,10 +2218,6 @@ video {
|
|||||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1));
|
color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:ring-offset-gray-800 {
|
|
||||||
--tw-ring-offset-color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:hover\:bg-gray-600:hover {
|
.dark\:hover\:bg-gray-600:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
|
background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
|
||||||
|
|||||||
19
utils/utils.go
Normal file
19
utils/utils.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateSessionId(length int) string {
|
||||||
|
bytes := make([]byte, length) // 16 bytes = 128 bits
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to generate session ID")
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateToken() string {
|
||||||
|
return GenerateSessionId(16)
|
||||||
|
}
|
||||||
@@ -131,6 +131,10 @@
|
|||||||
<option selected value="CreateBooklet">Create Booklet</option>
|
<option selected value="CreateBooklet">Create Booklet</option>
|
||||||
<option value="LongEdge">Long Edge</option>
|
<option value="LongEdge">Long Edge</option>
|
||||||
<option value="ShortEdge">Short Edge</option>
|
<option value="ShortEdge">Short Edge</option>
|
||||||
|
<option value="CreateBookletA5">CreateBooklet A5</option>
|
||||||
|
<option value="LongEdgeA5">Long Edge A5</option>
|
||||||
|
<option value="ShortEdgeA5">Short Edge A5</option>
|
||||||
|
<option value="TriFold">Tri-Fold (Flyer)</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
||||||
<i class="text-xs">{{ .data.shopItem.Description }}</i>
|
<i class="text-xs">{{ .data.shopItem.Description }}</i>
|
||||||
<p class="">Price: {{ .data.shopItem.BasePrice }}</p>
|
<p class="">Price: {{ .data.shopItem.BasePrice }}</p>
|
||||||
{{ if .loggedIn }}
|
{{ if .isAdmin }}
|
||||||
|
|
||||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
Do you really want to delete this item??
|
Do you really want to delete this item??
|
||||||
|
|||||||
@@ -1,13 +1,46 @@
|
|||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
<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">
|
<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">
|
<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 Item</h2>
|
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Edit Item</h2>
|
||||||
|
|
||||||
<a href="/shopitems/{{ .data.shopItem.ID }}">
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
|
{{ .data.error }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-green-500">
|
||||||
|
{{ .data.success }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/{{ .data.shopItem.Pdf }}" target="_blank">
|
||||||
<img src="/{{ .data.shopItem.Image }}" alt="Product Image" class="aspect-4/5 mx-auto rounded-md bg-gray-200 object-cover group-hover:opacity-75 lg:aspect-auto lg:h-80">
|
<img src="/{{ .data.shopItem.Image }}" alt="Product Image" class="aspect-4/5 mx-auto rounded-md bg-gray-200 object-cover group-hover:opacity-75 lg:aspect-auto lg:h-80">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-6 gap-4 mb-4">
|
||||||
|
<div class="col-span-3">
|
||||||
|
{{ if .data.previousShopItem }}
|
||||||
|
<form action="/shopitems/{{ .data.previousShopItem.ID }}/edit">
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Previous</button>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="col-span-3">
|
||||||
|
{{ if .data.nextShopItem }}
|
||||||
|
<form action="/shopitems/{{ .data.nextShopItem.ID }}/edit">
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6
|
||||||
|
font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2
|
||||||
|
focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Next</button>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -32,12 +65,28 @@
|
|||||||
<textarea id="description" name="description" type="textarea" class="block w-full px-4 py-2 mt-2 text-gray-900 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-900 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring">{{ .data.shopItem.Description}}</textarea>
|
<textarea id="description" name="description" type="textarea" class="block w-full px-4 py-2 mt-2 text-gray-900 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-900 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring">{{ .data.shopItem.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label class="block text-sm/6 font-medium text-gray-900">
|
||||||
|
Print Mode
|
||||||
|
</label>
|
||||||
|
<select name="print-mode" required class="bg-white 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="{{ .data.shopItem.PrintMode }}">{{ .data.shopItem.PrintMode }}</option>
|
||||||
|
<option value="CreateBooklet">CreateBooklet</option>
|
||||||
|
<option value="LongEdge">Long Edge</option>
|
||||||
|
<option value="ShortEdge">Short Edge</option>
|
||||||
|
<option value="CreateBookletA5">CreateBooklet A5</option>
|
||||||
|
<option value="LongEdgeA5">Long Edge A5</option>
|
||||||
|
<option value="ShortEdgeA5">Short Edge A5</option>
|
||||||
|
<option value="TriFold">Tri-Fold (Flyer)</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm/6 text-gray-900">Select Categories</label>
|
<label class="block text-sm/6 text-gray-900">Select Categories</label>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{{ range .data.tags }}
|
{{ range .data.tags }}
|
||||||
<label class="flex text-sm/6 items-center">
|
<label class="flex text-sm/6 items-center">
|
||||||
<input type="checkbox" class="form-checkbox h-4 w-4 text-gray-900" value="{{ .ID }}" name="tags[]">
|
<input type="checkbox" {{ .Checked }} class="form-checkbox h-4 w-4 text-gray-900" value="{{ .ID }}" name="tags[]">
|
||||||
<span class="ml-2 text-sm/6 text-gray-900">{{ .Name }}</span>
|
<span class="ml-2 text-sm/6 text-gray-900">{{ .Name }}</span>
|
||||||
</label>
|
</label>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -65,7 +114,7 @@
|
|||||||
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price B/W</label>
|
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price B/W</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input type="number" name="variant-value[]" id="variant-value1" step="0.01" min="0.00" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
<input type="number" value="{{ .data.priceBW }}" name="variant-value[]" id="variant-value1" step="0.01" min="0.00" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -76,7 +125,7 @@
|
|||||||
<label for="variant-value2" class="block text-sm/6 font-medium text-gray-900">Price Colored</label>
|
<label for="variant-value2" class="block text-sm/6 font-medium text-gray-900">Price Colored</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input type="number" name="variant-value[]" id="variant-value2" step="0.01" min="0.00" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
<input type="number" value="{{ .data.priceColored }}" name="variant-value[]" id="variant-value2" step="0.01" min="0.00" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -127,24 +176,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="block text-sm font-medium text-gray-900">
|
|
||||||
Print Mode
|
|
||||||
</label>
|
|
||||||
<select name="print-mode" 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="CreateBooklet">Create Booklet</option>
|
|
||||||
<option value="LongEdge">Long Edge</option>
|
|
||||||
<option value="ShortEdge">Short Edge</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
|
|
||||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
|
||||||
{{ .data.error }}
|
|
||||||
</p>
|
|
||||||
<p class="mt-10 text-center text-sm/6 text-green-500">
|
|
||||||
{{ .data.success }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Update Item</button>
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Update Item</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Zine Shop</title>
|
<title>Zines</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link href="/static/output.css" rel="stylesheet">
|
<link href="/static/output.css" rel="stylesheet">
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
-->
|
-->
|
||||||
</div>
|
</div>
|
||||||
{{ if .loggedIn }}
|
{{ if .isAdmin }}
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
||||||
<a href="/additem" 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">Add Item</a>
|
<a href="/additem" 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">Add Item</a>
|
||||||
<a href="/batchupload" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300
|
<a href="/batchupload" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300
|
||||||
@@ -33,13 +33,15 @@
|
|||||||
<a href="/orders" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300
|
<a href="/orders" 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">Orders</a>
|
hover:bg-gray-700 hover:text-white">Orders</a>
|
||||||
<a href="/tags" 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">Tags</a>
|
<a href="/tags" 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">Tags</a>
|
||||||
|
<a href="/invites" 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">Invites</a>
|
||||||
<a href="/cart/print" 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/print" 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">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="/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>
|
||||||
{{ else }}
|
{{ end }}
|
||||||
|
{{ if .loggedIn }}
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
||||||
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
28
views/invites.html
Normal file
28
views/invites.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{{ 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">Create/Delete Invites</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
{{ range .tokens }}
|
||||||
|
<form action="/invites" method="POST">
|
||||||
|
<div class="max-w-md mx-auto mt-4">
|
||||||
|
<div class="flex">
|
||||||
|
<input type="text" id="token" name="token" value="{{ .Token }}" readonly="readonly" class="flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
|
||||||
|
<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="/invites" method="POST">
|
||||||
|
<div class="max-w-md mx-auto mt-4">
|
||||||
|
<div class="flex">
|
||||||
|
<button type="submit" name="action" value="create" class="bg-green-600 text-white ml-4 mr-4 rounded px-4 hover:bg-green-700">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{ template "footer.html" . }}
|
||||||
59
views/registertoken.html
Normal file
59
views/registertoken.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{{ 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="Logo">
|
||||||
|
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Register your account</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<form class="space-y-6" action="/register" method="POST">
|
||||||
|
<div>
|
||||||
|
<label for="token" class="block text-sm/6 font-medium text-gray-900">Token</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="token" id="token" value="{{ .token }}" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm/6 font-medium text-gray-900">Username</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="name" id="name" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="email" class="block text-sm/6 font-medium text-gray-900">Email address</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="email" name="email" id="email" autocomplete="email" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label for="password" class="block text-sm/6 font-medium text-gray-900">Password</label>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="password" name="password" id="password" autocomplete="current-password" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
|
{{ .error }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-green-500">
|
||||||
|
{{ .success }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Register</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
||||||
@@ -9,15 +9,18 @@
|
|||||||
<img class="w-full h-full object-cover" src="/{{ .data.shopItem.Image}}" alt="Product Image">
|
<img class="w-full h-full object-cover" src="/{{ .data.shopItem.Image}}" alt="Product Image">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex -mx-2 mb-4">
|
<div class="flex -mx-2 mb-4">
|
||||||
|
{{ if .loggedIn }}
|
||||||
<input type="hidden" id="{{ .data.shopItem.ID}}" name="ShopItemId" value="{{ .data.shopItem.ID }}">
|
<input type="hidden" id="{{ .data.shopItem.ID}}" name="ShopItemId" value="{{ .data.shopItem.ID }}">
|
||||||
<div class="w-1/3 px-2">
|
<div class="w-1/3 px-2">
|
||||||
<button type="submit" class="w-full bg-gray-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700">Add to Cart</button>
|
<button type="submit" class="w-full bg-gray-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700">Add to Cart</button>
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<div class="w-1/3 px-2">
|
<div class="w-1/3 px-2">
|
||||||
<button type="button" class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="/{{ .data.shopItem.Pdf }}">View</a></button>
|
<button type="button" class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="/{{ .data.shopItem.Pdf }}">View</a></button>
|
||||||
</div>
|
</div>
|
||||||
{{ if .loggedIn }}
|
|
||||||
|
{{ if .isAdmin }}
|
||||||
<div class="w-1/3 px-2">
|
<div class="w-1/3 px-2">
|
||||||
<button type="button" class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="{{ .data.shopItem.ID }}/edit">Edit</a></button>
|
<button type="button" class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="{{ .data.shopItem.ID }}/edit">Edit</a></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,6 +37,7 @@
|
|||||||
{{ .data.shopItem.Abstract }}
|
{{ .data.shopItem.Abstract }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{{ if .loggedIn }}
|
||||||
<div class="flex mb-4">
|
<div class="flex mb-4">
|
||||||
<label for="ItemVariantId" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"></label>
|
<label for="ItemVariantId" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"></label>
|
||||||
<select name="ItemVariantId" id="ItemVariantId" 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="ItemVariantId" id="ItemVariantId" 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">
|
||||||
@@ -43,6 +47,7 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<div class="flex mb-4">
|
<div class="flex mb-4">
|
||||||
<div class="mr-4">
|
<div class="mr-4">
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<div class="bg-white">
|
<div class="bg-white">
|
||||||
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl">
|
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl">
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-gray-900">Available Zines</h2>
|
<h2 class="text-2xl font-bold tracking-tight text-gray-900">Available Zines</h2>
|
||||||
|
|
||||||
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names..">
|
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names..">
|
||||||
<div class="mt-6 grid grid-cols-1 gap-x-6 gap-y-10 md:grid-cols-2 xl:grid-cols-4">
|
<div class="mt-6 grid grid-cols-1 gap-x-6 gap-y-10 md:grid-cols-2 xl:grid-cols-4">
|
||||||
{{ range .data.shopItems }}
|
{{ range .data.shopItems }}
|
||||||
|
|
||||||
<div class="myClass group relative">
|
<div class="myClass group relative">
|
||||||
<a href="/shopitems/{{ .ID }}">
|
<a href="/shopitems/{{ .ID }}">
|
||||||
<img src="/{{ .Image }}" alt="Product Image" class="aspect-4/5 mx-auto rounded bg-gray-200 object-cover group-hover:opacity-75 lg:aspect-auto lg:h-80">
|
<img loading="lazy" src="/{{ .Image }}" alt="Product Image" class="shadow-2xl aspect-4/5 mx-auto rounded bg-gray-200 object-cover group-hover:opacity-75 lg:aspect-auto lg:h-80">
|
||||||
</a>
|
</a>
|
||||||
<div class="mt-4 flex justify-between">
|
<div class="mt-4 flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -25,7 +25,9 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm font-medium text-gray-900">{{ .BasePrice }}€</p>
|
{{ if $.loggedIn }}
|
||||||
|
<p class="text-sm font-medium text-gray-900">{{ .BasePrice }}€</p>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user