Compare commits
131 Commits
1dc10d61bc
...
priceCalcu
| Author | SHA1 | Date | |
|---|---|---|---|
|
249cccd240
|
|||
|
db3dc9ecd1
|
|||
|
b75c46ec2f
|
|||
|
16c68cd0f8
|
|||
|
ad5573ee2c
|
|||
|
6c0440f06d
|
|||
|
9e638dcfc2
|
|||
|
6c59d1233f
|
|||
|
7025f526c1
|
|||
|
992b9c17c3
|
|||
|
4b0649439c
|
|||
|
8ce01417e7
|
|||
|
8e1df934b3
|
|||
|
17a1ef0123
|
|||
|
6330a990f5
|
|||
| f4faeb351d | |||
| 861b18651b | |||
|
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
|
|||
|
19ce41aca7
|
|||
|
763bb35a45
|
|||
|
6130843aa7
|
|||
|
667c3eba13
|
|||
|
e22cc0b243
|
|||
|
202c845bee
|
|||
|
d839416fdd
|
|||
|
821f4e526f
|
|||
|
9d2819cac4
|
|||
|
fd46f35023
|
|||
|
6943e3c9b7
|
|||
|
a90131c8be
|
|||
|
a04d057bce
|
|||
|
37ffeaa0f3
|
|||
|
961113ebd6
|
|||
|
529741e150
|
|||
|
8a7d66f815
|
|||
|
fa561c921d
|
|||
|
a9170b63b7
|
|||
|
f1e191a294
|
|||
|
6e14716305
|
|||
|
f70d053a23
|
|||
|
fb5091aad3
|
|||
|
2e82e3a8b9
|
|||
|
d2d0f39e33
|
|||
|
fc85113cd6
|
|||
|
08d79e2c38
|
|||
|
6a8ab81b88
|
|||
|
584030431d
|
|||
|
a8cb853c92
|
|||
|
0b4439647a
|
|||
|
2c4c21bd4d
|
|||
|
c4527ff228
|
|||
|
256b41c880
|
|||
|
af6787831a
|
|||
|
ec1a8b155a
|
|||
|
122651677f
|
|||
|
af11f88769
|
|||
|
0bcec807c8
|
|||
|
a3b066859b
|
|||
|
b349c4baba
|
|||
|
e406eda9c9
|
|||
|
a1fc053fa8
|
|||
|
1da5e3e8b4
|
|||
|
adac366896
|
|||
|
b14deeb24f
|
|||
|
d5c3d7fe75
|
|||
|
b55bf67e57
|
|||
|
d43401dc62
|
|||
|
8f5dd27ae0
|
|||
|
e864a75678
|
|||
|
0436d3a2bd
|
|||
|
490a9f1444
|
|||
|
fdb11bc57c
|
|||
|
f87a6352dd
|
|||
|
9e3a04cd78
|
|||
|
f39b6205d1
|
|||
|
5a7565663d
|
|||
|
89f7c8c4bb
|
|||
|
869642fa4d
|
|||
|
854573eb3a
|
|||
|
30b32a571c
|
|||
|
22bf9d4390
|
|||
|
2fce17d528
|
|||
|
e53796bda6
|
|||
|
69420fab6c
|
|||
|
6cbd9f5a3b
|
|||
|
9544849ef6
|
|||
| ac8b0339c3 | |||
| 974b95b244 | |||
| 1f411edf50 | |||
| cbaf48123f | |||
| a88be8de62 | |||
| 639eaa04f5 | |||
| fa214f4fdf | |||
| 503ab0e9bd | |||
| e54be98fe9 | |||
| 56035bb32f | |||
| 1ccfc620d0 | |||
| a65ba9c98c | |||
| d24dfdf262 | |||
| 0c85ca5938 | |||
| 23392cc5c1 | |||
| 5e22be5074 |
17
.gitea/workflows/flake-check.yml
Normal file
17
.gitea/workflows/flake-check.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Go
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24.x'
|
||||
- name: Install dependencies
|
||||
run: go get .
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
26
README.md
26
README.md
@@ -7,3 +7,29 @@ The zines will then be printed on demand and be send.
|
||||
for payment a simple random string will be created to connect payments to orders.
|
||||
This way also cash could be send like mullvad is doing it.
|
||||
|
||||
# Development
|
||||
To get the webserver running do the following:
|
||||
|
||||
```bash
|
||||
nix develop .#
|
||||
# run the webserver
|
||||
go run main.go
|
||||
```
|
||||
|
||||
For updating tailwindcss on the fly open extra shell
|
||||
```bash
|
||||
nix develop .#
|
||||
tailwindcss -i static/input.css -o static/output.css --watch
|
||||
```
|
||||
|
||||
# Printer Testing
|
||||
- [x] long edge + bypass front cover
|
||||
- `lp -d KONICA_MINOLTA_KONICA_MINOLTA_bizhub_C258/BookletPrint -o Fold=HalfFold -o FrontCoverPage=Printed -o FrontCoverTray=BypassTray ~/proggn/malobeo/zineshop/Test-book-long-edge.pdf`
|
||||
- [x] short edge + bypass front cover
|
||||
- ` lp -d KONICA_MINOLTA_KONICA_MINOLTA_bizhub_C258/BookletPrint -o Fold=HalfFold -o Binding=TopBinding -o FrontCoverPage=Printed -o FrontCoverTray=BypassTray ~/proggn/malobeo/zineshop/Test-book-short-edge.pdf `
|
||||
- [x] booklet + bypass front cover
|
||||
- `lp -d KONICA_MINOLTA_KONICA_MINOLTA_bizhub_C258/BookletPrint -o Combination=Booklet -o PageSize=A5 -o Fold=HalfFold -o FrontCoverPage=Printed -o FrontCoverTray=BypassTray ~/proggn/malobeo/zineshop/Test.pdf`
|
||||
---
|
||||
- [x] okular long edge + bypass front cover
|
||||
- [x] okular short edge + bypass front cover
|
||||
- [ ] okular booklet + bypass front cover
|
||||
|
||||
605
controllers/cartItemController.go
Normal file
605
controllers/cartItemController.go
Normal file
@@ -0,0 +1,605 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
//"git.dynamicdiscord.de/kalipso/zineshop/services"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/utils"
|
||||
)
|
||||
|
||||
type CartItemController interface {
|
||||
//CRUDController
|
||||
CartItemView(*gin.Context)
|
||||
AddItemHandler(*gin.Context)
|
||||
DeleteItemHandler(*gin.Context)
|
||||
EditItemHandler(*gin.Context)
|
||||
CheckoutView(*gin.Context)
|
||||
CheckoutHandler(*gin.Context)
|
||||
OrderView(*gin.Context)
|
||||
OrderHandler(*gin.Context)
|
||||
OrdersView(*gin.Context)
|
||||
OrdersHandler(*gin.Context)
|
||||
}
|
||||
|
||||
type cartItemController struct{}
|
||||
|
||||
func NewCartItemController() CartItemController {
|
||||
return &cartItemController{}
|
||||
}
|
||||
|
||||
// getSetCookieValue retrieves the value of a cookie from the Set-Cookie header
|
||||
func getSetCookieValue(c *gin.Context, cookieName string) string {
|
||||
// Check the Set-Cookie headers
|
||||
cookies := c.Writer.Header()["Set-Cookie"]
|
||||
for _, cookie := range cookies {
|
||||
if strings.HasPrefix(cookie, cookieName+"=") {
|
||||
// Extract the cookie value
|
||||
parts := strings.SplitN(cookie, ";", 2)
|
||||
if len(parts) > 0 {
|
||||
return strings.TrimPrefix(parts[0], cookieName+"=")
|
||||
}
|
||||
}
|
||||
}
|
||||
return "" // Return empty string if cookie is not found
|
||||
}
|
||||
|
||||
func GetSessionId(ctx *gin.Context) string {
|
||||
sessionId, err := ctx.Cookie("session_id")
|
||||
|
||||
if err != nil {
|
||||
//we need to check if we already set cookie in the response so that we dont do this multiple times
|
||||
responseCookie := getSetCookieValue(ctx, "session_id")
|
||||
|
||||
if len(responseCookie) != 0 {
|
||||
return responseCookie
|
||||
}
|
||||
|
||||
sessionId = utils.GenerateSessionId(16)
|
||||
ctx.SetCookie("session_id", sessionId, 3600, "/", "", false, true)
|
||||
}
|
||||
|
||||
return sessionId
|
||||
}
|
||||
|
||||
func (rc *cartItemController) NewCartItemFromForm(ctx *gin.Context) (models.CartItem, error) {
|
||||
sessionId := GetSessionId(ctx)
|
||||
shopItemIdStr := ctx.PostForm("ShopItemId")
|
||||
shopItemId, err := strconv.Atoi(shopItemIdStr)
|
||||
itemVariantIdStr := ctx.PostForm("ItemVariantId")
|
||||
itemVariantId, err := strconv.Atoi(itemVariantIdStr)
|
||||
|
||||
if err != nil {
|
||||
return models.CartItem{}, err
|
||||
}
|
||||
|
||||
quantity := 1
|
||||
|
||||
shopItem, err := repositories.ShopItems.GetById(shopItemIdStr)
|
||||
|
||||
if err != nil {
|
||||
return models.CartItem{}, err
|
||||
}
|
||||
|
||||
itemVariant, err := repositories.ShopItems.GetVariantById(itemVariantIdStr)
|
||||
|
||||
cartItem := models.CartItem{
|
||||
SessionId: sessionId,
|
||||
ShopItemId: uint(shopItemId),
|
||||
ShopItem: shopItem,
|
||||
ItemVariantId: uint(itemVariantId),
|
||||
ItemVariant: itemVariant,
|
||||
Quantity: quantity,
|
||||
}
|
||||
|
||||
return cartItem, nil
|
||||
}
|
||||
|
||||
func (rc *cartItemController) NewAddressFromForm(ctx *gin.Context) (models.AddressInfo, error) {
|
||||
firstName := ctx.PostForm("firstName")
|
||||
lastName := ctx.PostForm("lastName")
|
||||
address := ctx.PostForm("address")
|
||||
postalCode := ctx.PostForm("postalCode")
|
||||
city := ctx.PostForm("city")
|
||||
country := ctx.PostForm("country")
|
||||
|
||||
if firstName == "" {
|
||||
return models.AddressInfo{}, fmt.Errorf("first name missing.")
|
||||
}
|
||||
|
||||
if lastName == "" {
|
||||
return models.AddressInfo{}, fmt.Errorf("Last name missing.")
|
||||
}
|
||||
|
||||
if address == "" {
|
||||
return models.AddressInfo{}, fmt.Errorf("address missing.")
|
||||
}
|
||||
|
||||
if postalCode == "" {
|
||||
return models.AddressInfo{}, fmt.Errorf("postalCode missing.")
|
||||
}
|
||||
|
||||
if city == "" {
|
||||
return models.AddressInfo{}, fmt.Errorf("city missing.")
|
||||
}
|
||||
|
||||
if country == "" {
|
||||
return models.AddressInfo{}, fmt.Errorf("country missing.")
|
||||
}
|
||||
|
||||
return models.AddressInfo{
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
Address: address,
|
||||
PostalCode: postalCode,
|
||||
City: city,
|
||||
Country: country,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rc *cartItemController) NewOrderFromForm(ctx *gin.Context) (models.Order, error) {
|
||||
sessionId := GetSessionId(ctx)
|
||||
status := models.OrderStatus("AwaitingConfirmation")
|
||||
token := utils.GenerateToken()
|
||||
email := ctx.PostForm("email")
|
||||
comment := ctx.PostForm("comment")
|
||||
firstName := ctx.PostForm("firstName")
|
||||
lastName := ctx.PostForm("lastName")
|
||||
address := ctx.PostForm("address")
|
||||
postalCode := ctx.PostForm("postalCode")
|
||||
city := ctx.PostForm("city")
|
||||
country := ctx.PostForm("country")
|
||||
shippingStr := ctx.PostForm("shippingMethod")
|
||||
|
||||
//address, err := rc.NewAddressFromForm(ctx)
|
||||
//if shippingStr != "pickup" {
|
||||
// if err != nil {
|
||||
// return models.Order{}, fmt.Errorf("Invalid address information.")
|
||||
// }
|
||||
//}
|
||||
|
||||
shipping, err := models.GetShippingMethod(shippingStr)
|
||||
|
||||
if err != nil {
|
||||
return models.Order{}, fmt.Errorf("Invalid shipping method.")
|
||||
}
|
||||
|
||||
cartItems, err := repositories.CartItems.GetAllBySession(sessionId)
|
||||
fmt.Println(sessionId)
|
||||
fmt.Println(cartItems)
|
||||
|
||||
if err != nil {
|
||||
return models.Order{}, err
|
||||
}
|
||||
|
||||
order := models.Order{
|
||||
SessionId: sessionId,
|
||||
Status: status,
|
||||
Token: token,
|
||||
Email: email,
|
||||
Comment: comment,
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
Address: address,
|
||||
PostalCode: postalCode,
|
||||
City: city,
|
||||
Country: country,
|
||||
Shipping: shipping.Id,
|
||||
CartItems: cartItems,
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (rc *cartItemController) Create(c *gin.Context) {
|
||||
cartItem, err := rc.NewCartItemFromForm(c)
|
||||
|
||||
if err != nil {
|
||||
ReplyError(c, fmt.Errorf("cartItem creation failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = repositories.CartItems.Create(cartItem)
|
||||
|
||||
if err != nil {
|
||||
ReplyError(c, fmt.Errorf("cartItem creation failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
ReplyOK(c, "cartItem was created")
|
||||
}
|
||||
|
||||
func (rc *cartItemController) Update(c *gin.Context) {
|
||||
cartItem, err := rc.NewCartItemFromForm(c)
|
||||
|
||||
if err != nil {
|
||||
ReplyError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = repositories.CartItems.Update(cartItem)
|
||||
|
||||
if err != nil {
|
||||
ReplyError(c, fmt.Errorf("cartItem creation failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
ReplyOK(c, "cartItem was updated")
|
||||
}
|
||||
|
||||
//func (rc *cartItemController) Delete(c *gin.Context) {
|
||||
// err := repositories.CartItems.DeleteBySessionId(c.Param("id"))
|
||||
//
|
||||
// if err != nil {
|
||||
// ReplyError(c, fmt.Errorf("cartItem deletion failed: %s", err))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// ReplyOK(c, "cartItem was deleted")
|
||||
//}
|
||||
|
||||
func (rc *cartItemController) CartItemView(c *gin.Context) {
|
||||
sessionId := GetSessionId(c)
|
||||
|
||||
cartItems, err := repositories.CartItems.GetAllBySession(sessionId)
|
||||
//cartItems, err := repositories.CartItems.GetAll()
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "cart.html", gin.H{"data": gin.H{"error": err}})
|
||||
}
|
||||
|
||||
priceTotal := 0.0
|
||||
for _, cartItem := range cartItems {
|
||||
priceTotal += (float64(cartItem.Quantity) * cartItem.ItemVariant.Price)
|
||||
}
|
||||
|
||||
fmt.Println("PRICE TOTAL", priceTotal)
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"cartItems": cartItems,
|
||||
"priceTotal": fmt.Sprintf("%.2f", priceTotal), //round 2 decimals
|
||||
"shipping": models.GetShippingMethods(),
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "cart.html", data)
|
||||
}
|
||||
|
||||
func (rc *cartItemController) AddItemHandler(c *gin.Context) {
|
||||
cartItem, err := rc.NewCartItemFromForm(c)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = repositories.CartItems.Create(cartItem)
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "cart.html", data)
|
||||
return
|
||||
}
|
||||
|
||||
rc.CartItemView(c)
|
||||
}
|
||||
|
||||
func (rc *cartItemController) DeleteItemHandler(c *gin.Context) {
|
||||
err := repositories.CartItems.DeleteById(c.PostForm("id"))
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "index.html", data)
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/cart")
|
||||
}
|
||||
|
||||
func (rc *cartItemController) EditItemHandler(c *gin.Context) {
|
||||
cartItemId := c.PostForm("id")
|
||||
cartItem, err := repositories.CartItems.GetById(cartItemId)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
c.Redirect(http.StatusFound, "/cart")
|
||||
return
|
||||
}
|
||||
|
||||
action := c.PostForm("action")
|
||||
|
||||
if action == "setAmount" {
|
||||
amountStr := c.PostForm("amount")
|
||||
amount, err := strconv.Atoi(amountStr)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
if amount < 0 {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "amount cant be negative"})
|
||||
return
|
||||
}
|
||||
if amount > 500 {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "amount cant be over 500"})
|
||||
return
|
||||
}
|
||||
|
||||
cartItem.Quantity = amount
|
||||
}
|
||||
|
||||
if action == "increase" {
|
||||
cartItem.Quantity += 1
|
||||
}
|
||||
|
||||
if action == "decrease" {
|
||||
cartItem.Quantity -= 1
|
||||
|
||||
if cartItem.Quantity == 0 {
|
||||
cartItem.Quantity = 1
|
||||
}
|
||||
}
|
||||
|
||||
_, err = repositories.CartItems.Update(cartItem)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/cart")
|
||||
}
|
||||
|
||||
func (rc *cartItemController) CheckoutView(c *gin.Context) {
|
||||
shippingMethod := c.Query("shippingMethod")
|
||||
|
||||
if shippingMethod == "" {
|
||||
rc.CartItemView(c)
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "checkout.html", gin.H{
|
||||
"askAddress": (shippingMethod != "pickup"),
|
||||
"shippingMethod": shippingMethod,
|
||||
})
|
||||
}
|
||||
|
||||
func (rc *cartItemController) CheckoutHandler(c *gin.Context) {
|
||||
order, err := rc.NewOrderFromForm(c)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
existingOrder, err := repositories.Orders.GetBySession(order.SessionId)
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
fmt.Println("Creating Order")
|
||||
createdOrder, err := repositories.Orders.Create(order)
|
||||
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "error.html", data)
|
||||
return
|
||||
}
|
||||
|
||||
for _, cartItem := range order.CartItems {
|
||||
cartItem.OrderID = createdOrder.ID
|
||||
repositories.CartItems.Update(cartItem)
|
||||
}
|
||||
} else if err == nil {
|
||||
fmt.Println("Updating Order")
|
||||
order.ID = existingOrder.ID
|
||||
order.CreatedAt = existingOrder.CreatedAt
|
||||
_, err := repositories.Orders.Update(order)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
shipping, err := models.GetShippingMethod(order.Shipping)
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "error.html", data)
|
||||
return
|
||||
}
|
||||
|
||||
priceProducts, priceTotal, err := order.CalculatePrices()
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "error.html", data)
|
||||
return
|
||||
}
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": "",
|
||||
"order": order,
|
||||
"askAddress": (order.Shipping != "pickup"),
|
||||
"isPreview": true,
|
||||
"shipping": shipping,
|
||||
"priceProducts": fmt.Sprintf("%.2f", priceProducts), //round 2 decimals
|
||||
"priceTotal": fmt.Sprintf("%.2f", priceTotal), //round 2 decimals
|
||||
})
|
||||
|
||||
fmt.Println(order)
|
||||
c.HTML(http.StatusOK, "orderpreview.html", data)
|
||||
}
|
||||
|
||||
func (rc *cartItemController) OrderView(c *gin.Context) {
|
||||
orderToken := c.Param("token")
|
||||
|
||||
order, err := repositories.Orders.GetByToken(orderToken)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Order does not exist."})
|
||||
return
|
||||
}
|
||||
|
||||
shipping, err := models.GetShippingMethod(order.Shipping)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Could not get shipping method"})
|
||||
return
|
||||
}
|
||||
|
||||
priceProducts, priceTotal, err := order.CalculatePrices()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Could not calculate final prices"})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Order: %v\n", order)
|
||||
fmt.Printf("PriceTotal: %v\n", priceTotal)
|
||||
fmt.Printf("Amount Items: %v\n", len(order.CartItems))
|
||||
|
||||
for _, item := range order.CartItems {
|
||||
fmt.Printf("Cartitem: %v", item)
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "order.html", CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": "",
|
||||
"order": order,
|
||||
"shipping": shipping,
|
||||
"priceProducts": fmt.Sprintf("%.2f", priceProducts), //round 2 decimals
|
||||
"priceTotal": fmt.Sprintf("%.2f", priceTotal), //round 2 decimals
|
||||
}))
|
||||
}
|
||||
|
||||
func (rc *cartItemController) OrderHandler(c *gin.Context) {
|
||||
confirmation := c.PostForm("confirm-order")
|
||||
|
||||
if confirmation == "" {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Something went wrong, try again later"})
|
||||
return
|
||||
}
|
||||
|
||||
if confirmation != "true" {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Order was not confirmed."})
|
||||
return
|
||||
}
|
||||
|
||||
sessionId := GetSessionId(c)
|
||||
order, err := repositories.Orders.GetBySession(sessionId)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Something went wrong, try again later"})
|
||||
return
|
||||
}
|
||||
|
||||
order.Status = models.AwaitingPayment
|
||||
|
||||
err = order.Validate()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
for idx := range order.CartItems {
|
||||
order.CartItems[idx].SessionId = "0"
|
||||
repositories.CartItems.Update(order.CartItems[idx])
|
||||
}
|
||||
|
||||
_, err = repositories.Orders.Update(order)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: cartItemRepository delete all by session - otherwise items stay in cart after completing order..
|
||||
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("/order/%s", order.Token))
|
||||
}
|
||||
|
||||
func (rc *cartItemController) OrdersView(c *gin.Context) {
|
||||
orders, err := repositories.Orders.GetAll()
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Orders doe not exist."})
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "editorders.html", CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": "",
|
||||
"orders": orders,
|
||||
}))
|
||||
}
|
||||
|
||||
func (rc *cartItemController) OrdersHandler(c *gin.Context) {
|
||||
token := c.Param("token")
|
||||
|
||||
if token == "" {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "EmptyToken"})
|
||||
return
|
||||
}
|
||||
|
||||
action := c.PostForm("action")
|
||||
|
||||
if action == "update" {
|
||||
order, err := repositories.Orders.GetByToken(token)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "No order with given token found"})
|
||||
return
|
||||
}
|
||||
|
||||
status := c.PostForm("order-status")
|
||||
|
||||
//TODO validate status
|
||||
if status == "" {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Invalid Order Status"})
|
||||
return
|
||||
}
|
||||
|
||||
order.Status = models.OrderStatus(status)
|
||||
|
||||
_, err = repositories.Orders.Update(order)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if action == "delete" {
|
||||
fmt.Println("Deleting Order ", token)
|
||||
err := repositories.Orders.DeleteByToken(token)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/orders")
|
||||
}
|
||||
350
controllers/configController.go
Normal file
350
controllers/configController.go
Normal 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)
|
||||
}
|
||||
207
controllers/printController.go
Normal file
207
controllers/printController.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PrintController interface {
|
||||
PrintVariantView(*gin.Context)
|
||||
PrintCartView(*gin.Context)
|
||||
PrintOrderView(*gin.Context)
|
||||
PrintHandler(*gin.Context)
|
||||
}
|
||||
|
||||
type printController struct{}
|
||||
|
||||
func NewPrintController() PrintController {
|
||||
return &printController{}
|
||||
}
|
||||
|
||||
func (rc *printController) PrintVariantView(c *gin.Context) {
|
||||
variant, err := repositories.ShopItems.GetVariantById(c.Param("id"))
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
return
|
||||
}
|
||||
|
||||
shopItem, err := repositories.ShopItems.GetById(fmt.Sprintf("%v", variant.ShopItemID))
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
return
|
||||
}
|
||||
|
||||
type ShopItemVariantPair struct {
|
||||
ShopItem models.ShopItem
|
||||
ItemVariant models.ItemVariant
|
||||
}
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"itemVariants": []ShopItemVariantPair{
|
||||
{ShopItem: shopItem, ItemVariant: variant},
|
||||
},
|
||||
})
|
||||
|
||||
fmt.Println(data)
|
||||
|
||||
c.HTML(http.StatusOK, "printvariant.html", data)
|
||||
}
|
||||
|
||||
func (rc *printController) PrintCartView(c *gin.Context) {
|
||||
sessionId := GetSessionId(c)
|
||||
|
||||
cartItems, err := repositories.CartItems.GetAllBySession(sessionId)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
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{
|
||||
"paper": paper,
|
||||
"cartItems": cartItems,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "printvariant.html", data)
|
||||
}
|
||||
|
||||
func (rc *printController) PrintOrderView(c *gin.Context) {
|
||||
order, err := repositories.Orders.GetByToken(c.Param("token"))
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
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
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"paper": paper,
|
||||
"cartItems": cartItems,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "printvariant.html", data)
|
||||
}
|
||||
|
||||
func (rc *printController) PrintHandler(c *gin.Context) {
|
||||
variantIds := c.PostFormArray("variant-id[]")
|
||||
variantAmounts := c.PostFormArray("variant-amount[]")
|
||||
variantPapertypes := c.PostFormArray("variant-papertype[]")
|
||||
variantCoverPages := c.PostFormArray("variant-coverpage[]")
|
||||
|
||||
if len(variantIds) != len(variantAmounts) || len(variantIds) != len(variantCoverPages) {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": "Invalid arguments"}})
|
||||
return
|
||||
}
|
||||
|
||||
var printJobs []models.PrintJob
|
||||
priceTotal := 0.0
|
||||
for idx := range variantIds {
|
||||
variant, err := repositories.ShopItems.GetVariantById(variantIds[idx])
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
return
|
||||
}
|
||||
|
||||
shopItem, err := repositories.ShopItems.GetById(fmt.Sprintf("%v", variant.ShopItemID))
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
return
|
||||
}
|
||||
|
||||
paperType, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantPapertypes[idx]))
|
||||
if err != nil {
|
||||
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])
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Printing Costs:")
|
||||
|
||||
printJob, err := models.NewPrintJob(shopItem, variant, paperType, coverPage, uint(variantAmount))
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
|
||||
return
|
||||
}
|
||||
printJob.CalculatePrintCosts()
|
||||
priceTotal += printJob.PriceTotal
|
||||
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() {
|
||||
for _, printJob := range printJobs {
|
||||
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()
|
||||
|
||||
fmt.Println(invoice)
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"invoice": invoice,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "printstarted.html", data)
|
||||
}
|
||||
@@ -3,19 +3,20 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
//"example.com/gin/test/services"
|
||||
"example.com/gin/test/repositories"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
//"git.dynamicdiscord.de/kalipso/zineshop/services"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
)
|
||||
|
||||
type CRUDController interface {
|
||||
Create(*gin.Context)
|
||||
GetAll(*gin.Context)
|
||||
GetAll(*gin.Context)
|
||||
GetById(*gin.Context)
|
||||
Update(*gin.Context)
|
||||
Delete(*gin.Context)
|
||||
@@ -26,18 +27,15 @@ type ShopItemController interface {
|
||||
ShopItemView(*gin.Context)
|
||||
AddItemView(*gin.Context)
|
||||
AddItemHandler(*gin.Context)
|
||||
CreateTag(*gin.Context)
|
||||
GetAllTags(*gin.Context)
|
||||
AddItemsView(*gin.Context)
|
||||
AddItemsHandler(*gin.Context)
|
||||
EditItemView(*gin.Context)
|
||||
EditItemHandler(*gin.Context)
|
||||
DeleteItemView(*gin.Context)
|
||||
DeleteItemHandler(*gin.Context)
|
||||
TagView(*gin.Context)
|
||||
TagHandler(*gin.Context)
|
||||
AddTagHandler(*gin.Context)
|
||||
}
|
||||
|
||||
type shopItemController struct {}
|
||||
type shopItemController struct{}
|
||||
|
||||
func NewShopItemController() ShopItemController {
|
||||
return &shopItemController{}
|
||||
@@ -65,41 +63,109 @@ func (rc *shopItemController) GetById(c *gin.Context) {
|
||||
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) {
|
||||
defaultImagePath := "static/img/zine.jpg"
|
||||
name := ctx.PostForm("name")
|
||||
abstract := ctx.PostForm("abstract")
|
||||
description := ctx.PostForm("description")
|
||||
priceStr := ctx.PostForm("price")
|
||||
categoryStr := ctx.PostForm("category")
|
||||
variantNames := ctx.PostFormArray("variant-name[]")
|
||||
variantValues := ctx.PostFormArray("variant-value[]")
|
||||
tagIds := ctx.PostFormArray("tags[]")
|
||||
image, err := ctx.FormFile("image")
|
||||
dst := "static/img/zine.jpg"
|
||||
dstImage := defaultImagePath
|
||||
printMode := ctx.PostForm("print-mode")
|
||||
|
||||
if err == nil {
|
||||
dst = filepath.Join("static/uploads", image.Filename)
|
||||
if err := ctx.SaveUploadedFile(image, dst); err != nil {
|
||||
dstImage = filepath.Join("static/uploads", image.Filename)
|
||||
if err := ctx.SaveUploadedFile(image, dstImage); err != nil {
|
||||
return models.ShopItem{}, fmt.Errorf("Could not save image")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dstPdf := ""
|
||||
pdf, err := ctx.FormFile("pdf")
|
||||
|
||||
if err == nil {
|
||||
dstPdf = filepath.Join("static/uploads", pdf.Filename)
|
||||
fmt.Println("Saving pdf at ", dstPdf)
|
||||
if err := ctx.SaveUploadedFile(pdf, dstPdf); err != nil {
|
||||
return models.ShopItem{}, fmt.Errorf("Could not save PDF")
|
||||
}
|
||||
|
||||
if dstImage == defaultImagePath {
|
||||
dstImage = dstPdf + ".preview.png"
|
||||
cmd := exec.Command("pdftoppm", "-png", "-singlefile", dstPdf, dstPdf+".preview")
|
||||
_, err := cmd.Output()
|
||||
|
||||
if err != nil {
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if name == "" || description == "" {
|
||||
return models.ShopItem{}, fmt.Errorf("Name or description empty")
|
||||
}
|
||||
|
||||
// Convert the price string to float64
|
||||
price, err := strconv.ParseFloat(priceStr, 64)
|
||||
if err != nil {
|
||||
return models.ShopItem{}, fmt.Errorf("Could not parse price")
|
||||
}
|
||||
category, err := models.ParseCategory(categoryStr)
|
||||
if err != nil {
|
||||
return models.ShopItem{}, err
|
||||
}
|
||||
|
||||
var variants []models.ItemVariant
|
||||
|
||||
fmt.Println("VariantNames: ", variantNames)
|
||||
fmt.Println("VariantValues: ", variantValues)
|
||||
if len(variantNames) != len(variantValues) {
|
||||
return models.ShopItem{}, fmt.Errorf("Different number of variant names and values")
|
||||
}
|
||||
|
||||
for idx := range variantNames {
|
||||
if variantValues[idx] == "" || variantNames[idx] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
price, err := strconv.ParseFloat(variantValues[idx], 64)
|
||||
if err != nil {
|
||||
return models.ShopItem{}, fmt.Errorf("Could not variant parse price")
|
||||
}
|
||||
|
||||
variants = append(variants, models.ItemVariant{
|
||||
Name: variantNames[idx],
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
shopItem := models.ShopItem{
|
||||
Name: name,
|
||||
Abstract: abstract,
|
||||
Name: name,
|
||||
Abstract: abstract,
|
||||
Description: description,
|
||||
Price: price,
|
||||
IsPublic: true,
|
||||
Image: dst,
|
||||
Category: category,
|
||||
IsPublic: true,
|
||||
BasePrice: rc.GetBasePrice(variants),
|
||||
Image: dstImage,
|
||||
Pdf: dstPdf,
|
||||
Variants: variants,
|
||||
PrintMode: printMode,
|
||||
WasPrinted: false,
|
||||
}
|
||||
|
||||
fmt.Println("Creating Shopitem: ", shopItem)
|
||||
|
||||
for _, tagId := range tagIds {
|
||||
tag, err := repositories.Tags.GetById(tagId)
|
||||
|
||||
@@ -111,9 +177,24 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop
|
||||
}
|
||||
|
||||
return shopItem, nil
|
||||
//return services.ShopItems.NewShopItem(name, abstract, description, price, tagIds)
|
||||
}
|
||||
|
||||
func (rc *shopItemController) GetBasePrice(variants []models.ItemVariant) float64 {
|
||||
result := 0.0
|
||||
|
||||
for idx, variant := range variants {
|
||||
if idx == 0 {
|
||||
result = variant.Price
|
||||
continue
|
||||
}
|
||||
|
||||
if variant.Price < result {
|
||||
result = variant.Price
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (rc *shopItemController) Create(c *gin.Context) {
|
||||
shopItem, err := rc.NewShopItemFromForm(c)
|
||||
@@ -133,7 +214,6 @@ func (rc *shopItemController) Create(c *gin.Context) {
|
||||
ReplyOK(c, "shopItem was created")
|
||||
}
|
||||
|
||||
|
||||
func (rc *shopItemController) Update(c *gin.Context) {
|
||||
shopItemId, err := strconv.Atoi(c.Param("id"))
|
||||
|
||||
@@ -160,6 +240,7 @@ func (rc *shopItemController) Update(c *gin.Context) {
|
||||
ReplyOK(c, "shopItem was updated")
|
||||
}
|
||||
|
||||
// TODO: delete associated cartitems
|
||||
func (rc *shopItemController) Delete(c *gin.Context) {
|
||||
err := repositories.ShopItems.DeleteById(c.Param("id"))
|
||||
|
||||
@@ -175,25 +256,23 @@ func (rc *shopItemController) ShopItemView(c *gin.Context) {
|
||||
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||
|
||||
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
|
||||
tags, err := repositories.Tags.GetAll()
|
||||
|
||||
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{
|
||||
"shopItem": shopItem,
|
||||
"tags": tags,
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "shopitem.html", data)
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "shopitem.html", data)
|
||||
}
|
||||
|
||||
@@ -201,125 +280,297 @@ func (rc *shopItemController) AddItemView(c *gin.Context) {
|
||||
tags, err := repositories.Tags.GetAll()
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err })
|
||||
c.HTML(http.StatusBadRequest, "additem.html", gin.H{"error": err})
|
||||
}
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"error": "",
|
||||
"success": "",
|
||||
"tags": tags,
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "additem.html", data)
|
||||
}
|
||||
|
||||
|
||||
func (rc *shopItemController) AddItemHandler(c *gin.Context) {
|
||||
shopItem, err := rc.NewShopItemFromForm(c)
|
||||
errorHandler := func(err error, tags []models.Tag) {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err })
|
||||
return
|
||||
c.HTML(http.StatusOK, "additem.html", data)
|
||||
}
|
||||
|
||||
tags, err := repositories.Tags.GetAll()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err })
|
||||
errorHandler(err, tags)
|
||||
return
|
||||
}
|
||||
|
||||
shopItem, err := rc.NewShopItemFromForm(c)
|
||||
if err != nil {
|
||||
errorHandler(err, tags)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = repositories.ShopItems.Create(shopItem)
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "additem.html", data)
|
||||
errorHandler(err, tags)
|
||||
return
|
||||
}
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"error": "",
|
||||
"success": fmt.Sprintf("Item '%s' Registered", shopItem.Name),
|
||||
"tags": tags,
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "additem.html", data)
|
||||
}
|
||||
|
||||
func (rc *shopItemController) AddItemsView(c *gin.Context) {
|
||||
data := CreateSessionData(c, gin.H{})
|
||||
|
||||
func (rc *shopItemController) EditItemView(c *gin.Context) {
|
||||
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||
tags, err := repositories.Tags.GetAll()
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||
}
|
||||
|
||||
fmt.Println(shopItem)
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": "",
|
||||
"shopItem": shopItem,
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "edititem.html", data)
|
||||
c.HTML(http.StatusOK, "batchupload.html", data)
|
||||
}
|
||||
|
||||
func (rc *shopItemController) AddItemsHandler(c *gin.Context) {
|
||||
errorHandler := func(err error) {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
})
|
||||
c.HTML(http.StatusBadRequest, "batchupload.html", data)
|
||||
}
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
|
||||
if err != nil {
|
||||
errorHandler(err)
|
||||
return
|
||||
}
|
||||
|
||||
files := form.File["pdf"]
|
||||
|
||||
var shopItems []models.ShopItem
|
||||
for _, file := range files {
|
||||
dstPdf := filepath.Join("static/uploads", file.Filename)
|
||||
|
||||
if err := c.SaveUploadedFile(file, dstPdf); err != nil {
|
||||
errorHandler(err)
|
||||
return
|
||||
}
|
||||
|
||||
dstImage := dstPdf + ".preview.png"
|
||||
cmd := exec.Command("pdftoppm", "-png", "-singlefile", dstPdf, dstPdf+".preview")
|
||||
_, err := cmd.Output()
|
||||
|
||||
if err != nil {
|
||||
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")
|
||||
if err != nil {
|
||||
errorHandler(err)
|
||||
return
|
||||
}
|
||||
|
||||
variants := []models.ItemVariant{
|
||||
{
|
||||
Name: "B/W",
|
||||
Price: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
shopItem := models.ShopItem{
|
||||
Name: file.Filename,
|
||||
Abstract: file.Filename,
|
||||
Description: file.Filename,
|
||||
Category: category,
|
||||
IsPublic: true,
|
||||
BasePrice: rc.GetBasePrice(variants),
|
||||
Image: dstImage,
|
||||
Pdf: dstPdf,
|
||||
Variants: variants,
|
||||
PrintMode: "CreateBooklet",
|
||||
WasPrinted: false,
|
||||
}
|
||||
|
||||
_, err = repositories.ShopItems.Create(shopItem)
|
||||
if err != nil {
|
||||
errorHandler(err)
|
||||
return
|
||||
}
|
||||
|
||||
shopItems = append(shopItems, shopItem)
|
||||
}
|
||||
|
||||
msg := "The Following items were registered:\n"
|
||||
|
||||
for _, item := range shopItems {
|
||||
msg += fmt.Sprintf("%s\n", item.Name)
|
||||
}
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": msg,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "batchupload.html", data)
|
||||
}
|
||||
|
||||
func GetCheckedTags(item models.ShopItem) ([]models.CheckedTag, error) {
|
||||
allTags, err := repositories.Tags.GetAll()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tags []models.CheckedTag
|
||||
for _, tag := range allTags {
|
||||
tmpTag := models.CheckedTag{
|
||||
Tag: tag,
|
||||
Checked: "",
|
||||
}
|
||||
|
||||
for _, itemTag := range item.Tags {
|
||||
if itemTag.Name == tmpTag.Name {
|
||||
tmpTag.Checked = "checked"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (rc *shopItemController) EditItemHandler(c *gin.Context) {
|
||||
shopItem, err := rc.NewShopItemFromForm(c)
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
newShopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
newShopItem.Name = shopItem.Name
|
||||
newShopItem.Abstract = shopItem.Abstract
|
||||
newShopItem.Description = shopItem.Description
|
||||
newShopItem.Price = shopItem.Price
|
||||
newShopItem.Category = shopItem.Category
|
||||
newShopItem.Variants = shopItem.Variants
|
||||
newShopItem.BasePrice = shopItem.BasePrice
|
||||
newShopItem.IsPublic = shopItem.IsPublic
|
||||
newShopItem.Tags = shopItem.Tags
|
||||
newShopItem.WasPrinted = false
|
||||
|
||||
if len(shopItem.Tags) != 0 {
|
||||
newShopItem.Tags = shopItem.Tags
|
||||
}
|
||||
|
||||
if shopItem.Image != "static/img/zine.jpg" {
|
||||
newShopItem.Image = shopItem.Image
|
||||
}
|
||||
|
||||
if shopItem.Pdf != "" {
|
||||
newShopItem.Pdf = shopItem.Pdf
|
||||
}
|
||||
|
||||
newShopItem.PrintMode = shopItem.PrintMode
|
||||
|
||||
templateData, err := rc.getEditTemplatData(newShopItem)
|
||||
|
||||
tags, err := repositories.Tags.GetAll()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||
return
|
||||
c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": err})
|
||||
}
|
||||
|
||||
_, err = repositories.ShopItems.Update(newShopItem)
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"success": "",
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
templateData["error"] = err
|
||||
data := CreateSessionData(c, templateData)
|
||||
c.HTML(http.StatusOK, "edititem.html", data)
|
||||
return
|
||||
}
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": fmt.Sprintf("Item '%s' Updated", newShopItem.Name),
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
templateData["success"] = fmt.Sprintf("Item '%s' Updated", newShopItem.Name)
|
||||
data := CreateSessionData(c, templateData)
|
||||
c.HTML(http.StatusOK, "edititem.html", data)
|
||||
}
|
||||
|
||||
@@ -328,28 +579,27 @@ func (rc *shopItemController) DeleteItemView(c *gin.Context) {
|
||||
tags, err := repositories.Tags.GetAll()
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusBadRequest, "deleteitem.html", gin.H{ "error": err })
|
||||
c.HTML(http.StatusBadRequest, "deleteitem.html", gin.H{"error": err})
|
||||
}
|
||||
|
||||
|
||||
fmt.Println(shopItem)
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": "",
|
||||
"success": "",
|
||||
"error": "",
|
||||
"success": "",
|
||||
"shopItem": shopItem,
|
||||
"tags": tags,
|
||||
"tags": tags,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "deleteitem.html", data)
|
||||
}
|
||||
|
||||
|
||||
func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
|
||||
err := repositories.ShopItems.DeleteById(c.Param("id"))
|
||||
|
||||
if err != nil {
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"error": err,
|
||||
"error": err,
|
||||
"success": "",
|
||||
})
|
||||
|
||||
@@ -360,7 +610,7 @@ func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
|
||||
fmt.Println(len(shopItems))
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"title": "shopItem Page",
|
||||
"title": "shopItem Page",
|
||||
"shopItems": shopItems,
|
||||
})
|
||||
|
||||
@@ -369,115 +619,8 @@ func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", data)
|
||||
}
|
||||
|
||||
func (rc *shopItemController) TagHandler(ctx *gin.Context) {
|
||||
name := ctx.PostForm("name")
|
||||
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, 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) {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
}
|
||||
|
||||
func ReplyOK(ctx *gin.Context, message any) {
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
package controllers
|
||||
|
||||
import(
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"math/rand"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
"example.com/gin/test/repositories"
|
||||
"example.com/gin/test/services"
|
||||
)
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/services"
|
||||
)
|
||||
|
||||
|
||||
type UserController struct {}
|
||||
type UserController struct{}
|
||||
|
||||
func NewUserController() UserController {
|
||||
return UserController{}
|
||||
}
|
||||
|
||||
|
||||
func (uc *UserController) Register(c *gin.Context) {
|
||||
//Get the email/passwd off req body
|
||||
var body struct {
|
||||
Name string
|
||||
Email string
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
}
|
||||
|
||||
@@ -38,7 +37,7 @@ func (uc *UserController) Register(c *gin.Context) {
|
||||
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 {
|
||||
fmt.Println("Error: ", err)
|
||||
@@ -53,11 +52,10 @@ func (uc *UserController) Register(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
|
||||
func (uc *UserController) Login(c *gin.Context) {
|
||||
//Get the email/passwd off req body
|
||||
var body struct {
|
||||
Email string
|
||||
Email string
|
||||
Password string
|
||||
}
|
||||
|
||||
@@ -83,7 +81,7 @@ func (uc *UserController) Login(c *gin.Context) {
|
||||
|
||||
// send it back
|
||||
c.SetSameSite(http.SameSiteLaxMode)
|
||||
c.SetCookie("Authorization", tokenString, 3600 * 24, "", "", false, true)
|
||||
c.SetCookie("Authorization", tokenString, 3600*24, "", "", false, true)
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
@@ -116,7 +114,6 @@ func (rc *UserController) LoginView(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "login.html", CreateSessionData(c, data))
|
||||
}
|
||||
|
||||
|
||||
func (rc *UserController) LoginHandler(c *gin.Context) {
|
||||
email := c.PostForm("email")
|
||||
password := c.PostForm("password")
|
||||
@@ -139,17 +136,18 @@ func (rc *UserController) LoginHandler(c *gin.Context) {
|
||||
|
||||
// send it back
|
||||
//c.SetSameSite(http.SameSiteLaxMode)
|
||||
c.SetCookie("Authorization", tokenString, 3600 * 24, "", "", false, true)
|
||||
c.SetCookie("Authorization", tokenString, 3600*24, "", "", false, true)
|
||||
c.HTML(http.StatusOK, "login.html", CreateSessionData(c, 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{
|
||||
"test": "HEllo World",
|
||||
"loggedIn": exists,
|
||||
"data": extra,
|
||||
"isAdmin": userImpl.IsAdmin,
|
||||
"data": extra,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,11 +156,34 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
||||
email := c.PostForm("email")
|
||||
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{
|
||||
"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": "",
|
||||
}
|
||||
|
||||
@@ -170,8 +191,45 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
||||
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{
|
||||
"error": "",
|
||||
"error": "",
|
||||
"success": "You successfully registered. Try logging in.",
|
||||
}
|
||||
|
||||
@@ -180,7 +238,40 @@ func (rc *UserController) RegisterHandler(c *gin.Context) {
|
||||
|
||||
func (rc *UserController) RegisterView(c *gin.Context) {
|
||||
data := gin.H{
|
||||
"error": "",
|
||||
"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{
|
||||
"error": "",
|
||||
"success": "",
|
||||
}
|
||||
|
||||
@@ -191,7 +282,7 @@ func (rc *UserController) ResetView(c *gin.Context) {
|
||||
shopItems, _ := repositories.ShopItems.GetAll()
|
||||
|
||||
data := gin.H{
|
||||
"title": "shopItem Page",
|
||||
"title": "shopItem Page",
|
||||
"shopItems": shopItems,
|
||||
}
|
||||
|
||||
@@ -202,88 +293,78 @@ func (rc *UserController) ResetHandler(c *gin.Context) {
|
||||
shopItems, _ := repositories.ShopItems.GetAll()
|
||||
|
||||
data := gin.H{
|
||||
"title": "shopItem Page",
|
||||
"title": "shopItem Page",
|
||||
"shopItems": shopItems,
|
||||
}
|
||||
|
||||
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) {
|
||||
shopItems, _ := repositories.ShopItems.GetAll()
|
||||
fmt.Println(len(shopItems))
|
||||
itemOrder := c.Query("order")
|
||||
|
||||
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{
|
||||
"title": "shopItem Page",
|
||||
"title": "shopItem Page",
|
||||
"shopItems": shopItems,
|
||||
})
|
||||
|
||||
fmt.Println(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 },
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
}
|
||||
func (rc *UserController) TagView(c *gin.Context) {
|
||||
shopItems, _ := repositories.ShopItems.GetByTagId(c.Param("id"))
|
||||
|
||||
data := CreateSessionData(c, gin.H{
|
||||
"title": "shopItem Page",
|
||||
"bookings": bookings,
|
||||
"shopItemcount": len(bookings["head"].([]string)) + 1,
|
||||
"title": "shopItem Page",
|
||||
"shopItems": shopItems,
|
||||
})
|
||||
|
||||
fmt.Println(data)
|
||||
|
||||
c.HTML(http.StatusOK, "calendar.html", data)
|
||||
c.HTML(http.StatusOK, "index.html", data)
|
||||
}
|
||||
|
||||
func (rc *UserController) Logout(c *gin.Context) {
|
||||
|
||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744463964,
|
||||
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
139
flake.nix
Normal file
139
flake.nix
Normal file
@@ -0,0 +1,139 @@
|
||||
{
|
||||
description = "A very basic flake";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
};
|
||||
|
||||
inputs.utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, utils, ... }:
|
||||
|
||||
nixpkgs.lib.attrsets.recursiveUpdate
|
||||
(utils.lib.eachSystem (utils.lib.defaultSystems) ( system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in rec
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
go
|
||||
gotools
|
||||
poppler_utils #get first pdf page to png
|
||||
cups
|
||||
imagemagick
|
||||
tailwindcss
|
||||
];
|
||||
};
|
||||
|
||||
packages.zineshop = nixpkgs.legacyPackages.x86_64-linux.buildGoModule {
|
||||
pname = "zineshop";
|
||||
version = "1.0";
|
||||
vendorHash = "sha256-0M/xblZXVw4xIFZeDewYrFu7VGUCsPTPG13r9ZpTGJo=";
|
||||
src = ./.;
|
||||
|
||||
postInstall = ''
|
||||
cp -r views $out/
|
||||
cp -r static $out/
|
||||
'';
|
||||
};
|
||||
|
||||
packages.default = packages.zineshop;
|
||||
|
||||
checks = let
|
||||
checkArgs = {
|
||||
pkgs = pkgs;
|
||||
inherit self;
|
||||
};
|
||||
in {
|
||||
zineshop = import ./test/test.nix checkArgs;
|
||||
};
|
||||
|
||||
})) {
|
||||
|
||||
nixosModules.zineshop = { config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.services.zineshop;
|
||||
zineshop-pkg = self.packages.x86_64-linux.zineshop;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.zineshop = {
|
||||
enable = lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = lib.mdDoc ''
|
||||
Enables zineshop
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
environment.systemPackages = [
|
||||
zineshop-pkg
|
||||
pkgs.poppler_utils #get first pdf page to png
|
||||
pkgs.cups
|
||||
pkgs.imagemagick
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/var/lib/zineshop' 0750 zineshop zineshop - -"
|
||||
"d '/var/lib/zineshop/views' 0750 zineshop zineshop - -"
|
||||
"d '/var/lib/zineshop/static' 0750 zineshop zineshop - -"
|
||||
"d '/var/lib/zineshop/static/uploads' 0750 zineshop zineshop - -"
|
||||
"z '/var/lib/zineshop' 0750 zineshop zineshop - -"
|
||||
"z '/var/lib/zineshop/views' 0750 zineshop zineshop - -"
|
||||
"z '/var/lib/zineshop/static' 0750 zineshop zineshop - -"
|
||||
"z '/var/lib/zineshop/static/uploads' 0750 zineshop zineshop - -"
|
||||
];
|
||||
|
||||
users = {
|
||||
groups.zineshop = {};
|
||||
users.zineshop = {
|
||||
description = "zineshop user";
|
||||
group = "zineshop";
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.zineshop = {
|
||||
description = "zineshop daemon";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
WorkingDirectory = "/var/lib/zineshop";
|
||||
ExecStart = pkgs.writeScript "start-zineshop" ''
|
||||
#! ${pkgs.bash}/bin/bash
|
||||
PATH="$PATH:${lib.makeBinPath [ pkgs.poppler_utils pkgs.cups pkgs.imagemagick ]}"
|
||||
${zineshop-pkg}/bin/zineshop
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
};
|
||||
|
||||
environment = {
|
||||
SQLITE_DB = "/var/lib/zineshop/zineshop.db";
|
||||
SECRET = "secretforjwt"; #TODO: BAD!
|
||||
PORT = "8080";
|
||||
STATIC = "/var/lib/zineshop/static";
|
||||
VIEWS = "/var/lib/zineshop/views";
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
mkdir -m 0770 -p "/var/lib/zineshop"
|
||||
cp -r ${zineshop-pkg}/views /var/lib/zineshop/
|
||||
cp -r ${zineshop-pkg}/static /var/lib/zineshop/
|
||||
chown zineshop:zineshop "/var/lib/zineshop"
|
||||
'';
|
||||
|
||||
wantedBy = [ "default.target" ];
|
||||
|
||||
environment = {
|
||||
USER = "zineshop";
|
||||
HOME = "/var/lib/zineshop";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
17
go.mod
17
go.mod
@@ -1,4 +1,4 @@
|
||||
module example.com/gin/test
|
||||
module git.dynamicdiscord.de/kalipso/zineshop
|
||||
|
||||
go 1.23.3
|
||||
|
||||
@@ -6,7 +6,8 @@ require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.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/gorm v1.25.12
|
||||
)
|
||||
@@ -22,22 +23,30 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // 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/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.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/ugorji/go/codec v1.2.12 // 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/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // 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
|
||||
)
|
||||
|
||||
31
go.sum
31
go.sum
@@ -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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
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/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-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/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
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/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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
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/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
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/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
110
main.go
110
main.go
@@ -1,23 +1,26 @@
|
||||
package main
|
||||
|
||||
import(
|
||||
"os"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"example.com/gin/test/controllers"
|
||||
"example.com/gin/test/repositories"
|
||||
"example.com/gin/test/middlewares"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/controllers"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/middlewares"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
)
|
||||
|
||||
var(
|
||||
var (
|
||||
shopItemController controllers.ShopItemController = controllers.NewShopItemController()
|
||||
userController controllers.UserController = controllers.UserController{}
|
||||
authValidator middlewares.AuthValidator = middlewares.AuthValidator{}
|
||||
userController controllers.UserController = controllers.UserController{}
|
||||
cartItemController controllers.CartItemController = controllers.NewCartItemController()
|
||||
printController controllers.PrintController = controllers.NewPrintController()
|
||||
configController controllers.ConfigController = controllers.NewConfigController()
|
||||
authValidator middlewares.AuthValidator = middlewares.AuthValidator{}
|
||||
)
|
||||
|
||||
func LoadEnvVariables() {
|
||||
@@ -35,7 +38,7 @@ func setupLogOutput() {
|
||||
|
||||
func SetReply(ctx *gin.Context, err error, message any) {
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
|
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, message)
|
||||
}
|
||||
@@ -46,56 +49,71 @@ func main() {
|
||||
|
||||
repositories.InitRepositories()
|
||||
|
||||
|
||||
server := gin.New()
|
||||
server.Use(gin.Recovery())
|
||||
server.Use(gin.Logger())
|
||||
|
||||
server.Static("/static", "./static")
|
||||
server.LoadHTMLGlob("views/*.html")
|
||||
|
||||
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)
|
||||
}
|
||||
server.Static("/static", os.Getenv("STATIC"))
|
||||
server.LoadHTMLGlob(fmt.Sprintf("%s/*.html", os.Getenv("VIEWS")))
|
||||
|
||||
viewRoutes := server.Group("/", authValidator.OptionalAuth)
|
||||
{
|
||||
viewRoutes.GET("/", userController.MainView)
|
||||
viewRoutes.GET("/shopitems/:id", shopItemController.ShopItemView)
|
||||
viewRoutes.GET("/shopitems/:id/edit", authValidator.RequireAuth, shopItemController.EditItemView)
|
||||
viewRoutes.POST("/shopitems/:id/edit", authValidator.RequireAuth, shopItemController.EditItemHandler)
|
||||
viewRoutes.GET("/shopitems/:id/delete", authValidator.RequireAuth, shopItemController.DeleteItemView)
|
||||
viewRoutes.POST("/shopitems/:id/delete", authValidator.RequireAuth, shopItemController.DeleteItemHandler)
|
||||
viewRoutes.GET("/tags", authValidator.RequireAuth, shopItemController.TagView)
|
||||
viewRoutes.POST("/tags/:id", authValidator.RequireAuth, shopItemController.TagHandler)
|
||||
viewRoutes.POST("/tags", authValidator.RequireAuth, shopItemController.AddTagHandler)
|
||||
viewRoutes.GET("/shopitems/:id/edit", authValidator.RequireAdmin, shopItemController.EditItemView)
|
||||
viewRoutes.POST("/shopitems/:id/edit", authValidator.RequireAdmin, shopItemController.EditItemHandler)
|
||||
viewRoutes.GET("/shopitems/:id/delete", authValidator.RequireAdmin, shopItemController.DeleteItemView)
|
||||
viewRoutes.POST("/shopitems/:id/delete", authValidator.RequireAdmin, shopItemController.DeleteItemHandler)
|
||||
viewRoutes.GET("/variant/:id/print", authValidator.RequireAdmin, printController.PrintVariantView)
|
||||
viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView)
|
||||
viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler)
|
||||
|
||||
viewRoutes.GET("/config", authValidator.RequireAdmin, configController.ConfigView)
|
||||
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.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.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler)
|
||||
viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler)
|
||||
viewRoutes.POST("/cart/edit", authValidator.RequireAuth, cartItemController.EditItemHandler)
|
||||
viewRoutes.GET("/checkout", authValidator.RequireAuth, cartItemController.CheckoutView)
|
||||
viewRoutes.POST("/checkout", authValidator.RequireAuth, cartItemController.CheckoutHandler)
|
||||
viewRoutes.POST("/order", authValidator.RequireAuth, cartItemController.OrderHandler)
|
||||
viewRoutes.GET("/order/:token", authValidator.RequireAuth, cartItemController.OrderView)
|
||||
viewRoutes.GET("/order/:token/print", authValidator.RequireAuth, printController.PrintOrderView)
|
||||
|
||||
viewRoutes.GET("/orders", authValidator.RequireAdmin, cartItemController.OrdersView)
|
||||
viewRoutes.POST("/order/:token/edit", authValidator.RequireAdmin, cartItemController.OrdersHandler)
|
||||
|
||||
//write middleware that redirects to homescreen on register/login/reset for logged in users
|
||||
viewRoutes.GET("/login", userController.LoginView)
|
||||
viewRoutes.GET("/logout", userController.Logout)
|
||||
viewRoutes.GET("/register", userController.RegisterView)
|
||||
viewRoutes.GET("/passwordreset", userController.ResetView)
|
||||
viewRoutes.GET("/additem", authValidator.RequireAuth, shopItemController.AddItemView)
|
||||
viewRoutes.GET("/register", userController.InitAdmin)
|
||||
viewRoutes.GET("/register/:token", userController.RegisterView)
|
||||
viewRoutes.GET("/invites", authValidator.RequireAdmin, userController.InviteView)
|
||||
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("/register", userController.RegisterHandler)
|
||||
viewRoutes.POST("/additem", authValidator.RequireAuth, shopItemController.AddItemHandler)
|
||||
viewRoutes.POST("/passwordreset", userController.ResetHandler)
|
||||
viewRoutes.POST("/additem", authValidator.RequireAdmin, shopItemController.AddItemHandler)
|
||||
viewRoutes.POST("/batchupload", authValidator.RequireAdmin, shopItemController.AddItemsHandler)
|
||||
viewRoutes.POST("/passwordreset", authValidator.RequireAuth, userController.ResetHandler)
|
||||
}
|
||||
|
||||
|
||||
server.Run(":"+os.Getenv("PORT"))
|
||||
server.Run(":" + os.Getenv("PORT"))
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package middlewares
|
||||
|
||||
import(
|
||||
"os"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
//"strconv"
|
||||
"net/http"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"net/http"
|
||||
|
||||
//"example.com/gin/test/models"
|
||||
"example.com/gin/test/repositories"
|
||||
//"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
)
|
||||
|
||||
type AuthValidator struct {
|
||||
@@ -70,7 +70,7 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
//Check Expiration
|
||||
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
||||
@@ -78,7 +78,7 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//Find user
|
||||
user, err := repositories.Users.GetById(claims["sub"])
|
||||
|
||||
@@ -86,15 +86,72 @@ func (av *AuthValidator) RequireAuth(c *gin.Context) {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//Attach to req
|
||||
c.Set("user", user)
|
||||
|
||||
|
||||
// Coninue
|
||||
c.Next()
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -119,19 +176,19 @@ func (av *AuthValidator) OptionalAuth(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
if float64(time.Now().Unix()) > claims["exp"].(float64) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//Find user
|
||||
user, err := repositories.Users.GetById(claims["sub"])
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//Attach to req
|
||||
c.Set("user", user)
|
||||
}
|
||||
|
||||
161
models/cart.go
Normal file
161
models/cart.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OrderStatus string
|
||||
|
||||
const (
|
||||
AwaitingConfirmation OrderStatus = "AwaitingConfirmation"
|
||||
Received OrderStatus = "Received"
|
||||
AwaitingPayment OrderStatus = "AwaitingPayment"
|
||||
Payed OrderStatus = "Payed"
|
||||
ReadyForPickup OrderStatus = "ReadyForPickup"
|
||||
Shipped OrderStatus = "Shipped"
|
||||
Cancelled OrderStatus = "Cancelled"
|
||||
)
|
||||
|
||||
type AddressInfo struct {
|
||||
FirstName string `json:"firstname"`
|
||||
LastName string `json:"lastname"`
|
||||
Address string `json:"address"`
|
||||
PostalCode string `json:"postalcode"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
type Shipping struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
|
||||
func GetShippingMethods() []Shipping {
|
||||
return []Shipping{
|
||||
{Id: "germany", Name: "Germany (DHL)", Price: 3.99},
|
||||
{Id: "international", Name: "International (DHL)", Price: 5.99},
|
||||
{Id: "pickup", Name: "Pickup", Price: 0.00},
|
||||
}
|
||||
}
|
||||
|
||||
func GetShippingMethod(id string) (Shipping, error) {
|
||||
var shipping Shipping
|
||||
found := false
|
||||
for _, shippingMethod := range GetShippingMethods() {
|
||||
if shippingMethod.Id == id {
|
||||
shipping = shippingMethod
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return Shipping{}, fmt.Errorf("Shipping method does not exist.")
|
||||
}
|
||||
|
||||
return shipping, nil
|
||||
}
|
||||
|
||||
type Order struct {
|
||||
gorm.Model
|
||||
SessionId string `json:"sessionid" binding:"required" gorm:"not null"`
|
||||
Status OrderStatus `json:"status"`
|
||||
Token string `json:"token" binding:"required" gorm:"not null"`
|
||||
Email string `json:"email"`
|
||||
Comment string `json:"comment"`
|
||||
FirstName string `json:"firstname"`
|
||||
LastName string `json:"lastname"`
|
||||
Address string `json:"address"`
|
||||
PostalCode string `json:"postalcode"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
Shipping string `json:"shipping"`
|
||||
CartItems []CartItem `json:"cartitems" gorm:"foreignKey:OrderID"`
|
||||
}
|
||||
|
||||
func (o *Order) Validate() error {
|
||||
//TODO: validate sessionId
|
||||
if o.SessionId == "" {
|
||||
return fmt.Errorf("Invalid SessionId")
|
||||
}
|
||||
|
||||
//TODO: validate token
|
||||
if o.Token == "" {
|
||||
return fmt.Errorf("Invalid Token")
|
||||
}
|
||||
|
||||
if o.Status == AwaitingConfirmation {
|
||||
return fmt.Errorf("Order still awaiting confirmation.")
|
||||
}
|
||||
|
||||
if len(o.CartItems) == 0 {
|
||||
return fmt.Errorf("Order is empty.")
|
||||
}
|
||||
|
||||
shipping, err := GetShippingMethod(o.Shipping)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//for pickup no address validation is necessary
|
||||
if shipping.Id == "pickup" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return o.ValidateAddress()
|
||||
}
|
||||
|
||||
func (o *Order) ValidateAddress() error {
|
||||
if o.FirstName == "" {
|
||||
return fmt.Errorf("Firstname missing")
|
||||
}
|
||||
if o.LastName == "" {
|
||||
return fmt.Errorf("Lastname missing")
|
||||
}
|
||||
|
||||
if o.Address == "" {
|
||||
return fmt.Errorf("Address missing")
|
||||
}
|
||||
|
||||
if o.PostalCode == "" {
|
||||
return fmt.Errorf("Postalcode missing")
|
||||
}
|
||||
|
||||
if o.City == "" {
|
||||
return fmt.Errorf("City missing")
|
||||
}
|
||||
|
||||
if o.Country == "" {
|
||||
return fmt.Errorf("Country missing")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Order) CalculatePrices() (float64, float64, error) {
|
||||
shipping, err := GetShippingMethod(o.Shipping)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
priceProducts := 0.0
|
||||
for _, cartItem := range o.CartItems {
|
||||
priceProducts += (float64(cartItem.Quantity) * cartItem.ItemVariant.Price)
|
||||
}
|
||||
|
||||
priceTotal := priceProducts + shipping.Price
|
||||
|
||||
return priceProducts, priceTotal, nil
|
||||
}
|
||||
|
||||
type CartItem struct {
|
||||
gorm.Model
|
||||
SessionId string `json:"sessionid" binding:"required" gorm:"not null"`
|
||||
ShopItemId uint
|
||||
ShopItem ShopItem `json:"shopitem" gorm:"foreignKey:ShopItemId"` //gorm one2one
|
||||
ItemVariantId uint
|
||||
ItemVariant ItemVariant `json:"itemvariant" gorm:"foreignKey:ItemVariantId"` //gorm one2one
|
||||
Quantity int `json:"quantity" binding:"required"`
|
||||
OrderID uint
|
||||
}
|
||||
11
models/config.go
Normal file
11
models/config.go
Normal 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
83
models/paper.go
Normal 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
|
||||
}
|
||||
180
models/printer.go
Normal file
180
models/printer.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/utils"
|
||||
"gorm.io/gorm"
|
||||
"math"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PrintOption string
|
||||
|
||||
const (
|
||||
CoverPage PrintOption = "-o FrontCoverPage=Printed -o FrontCoverTray=BypassTray"
|
||||
Colored PrintOption = "-o SelectColor=Color"
|
||||
Grayscale PrintOption = "-o SelectColor=Grayscale"
|
||||
LongEdge PrintOption = ""
|
||||
ShortEdge PrintOption = "-o Binding=TopBinding"
|
||||
CreateBooklet PrintOption = "-o Combination=Booklet -o PageSize=A5"
|
||||
TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding"
|
||||
)
|
||||
|
||||
type OldPrintJob struct {
|
||||
Pdf string
|
||||
Amount uint
|
||||
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 {
|
||||
if mode == "LongEdge" {
|
||||
return LongEdge
|
||||
}
|
||||
|
||||
if mode == "ShortEdge" {
|
||||
return ShortEdge
|
||||
}
|
||||
|
||||
if mode == "TriFold" {
|
||||
return TriFold
|
||||
}
|
||||
|
||||
return CreateBooklet
|
||||
}
|
||||
|
||||
func NewPrintJob(shopItem ShopItem, variant ItemVariant, paperType Paper, coverPaperType *Paper, amount uint) (PrintJob, error) {
|
||||
if shopItem.Pdf == "" {
|
||||
return PrintJob{}, fmt.Errorf("ShopItem has no PDF assigned")
|
||||
}
|
||||
|
||||
if amount > 100 {
|
||||
return PrintJob{}, fmt.Errorf("Amount to big. This is denied for security reasons")
|
||||
}
|
||||
|
||||
result := PrintJob{
|
||||
ShopItem: shopItem,
|
||||
Variant: variant,
|
||||
PaperType: paperType,
|
||||
CoverPaperType: coverPaperType,
|
||||
Amount: amount,
|
||||
}
|
||||
|
||||
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 {
|
||||
baseCommand := "lp -d KonicaBooklet"
|
||||
baseCommand += fmt.Sprintf(" -n %v ", p.Amount)
|
||||
|
||||
for _, option := range p.GeneratePrintOptions() {
|
||||
baseCommand += fmt.Sprintf(" %v ", option)
|
||||
}
|
||||
|
||||
baseCommand += fmt.Sprintf(" -- %s", p.ShopItem.Pdf)
|
||||
|
||||
parts := strings.Fields(baseCommand)
|
||||
|
||||
// The first part is the command, the rest are the arguments
|
||||
fmt.Println(parts)
|
||||
cmd := exec.Command(parts[0], parts[1:]...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Output:\n%s\n", output)
|
||||
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
|
||||
}
|
||||
@@ -1,17 +1,57 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShopItem struct {
|
||||
gorm.Model
|
||||
Name string `json:"name" binding:"required" gorm:"unique;not null"`
|
||||
Abstract string `json:"Abstract" binding:"required"`
|
||||
Description string `json:"description" binding:"required"`
|
||||
Price float64 `json:"price" binding:"required"`
|
||||
IsPublic bool `json:"isPublic" gorm:"default:true"`
|
||||
Tags []Tag `gorm:"many2many:item_tags;"`
|
||||
Image string
|
||||
/*
|
||||
Sticker
|
||||
- name, abstr, descr, price, tag
|
||||
|
||||
Poster
|
||||
- name, abstr, descr, price bw/colored, tag
|
||||
|
||||
Zines
|
||||
- name, abstr, descr, price bw/colored/coloredcoveronly, tag
|
||||
|
||||
Books
|
||||
- name, abstr, descr, price, tag
|
||||
*/
|
||||
type Category string
|
||||
|
||||
const (
|
||||
Zine Category = "Zine"
|
||||
)
|
||||
|
||||
func ParseCategory(s string) (c Category, err error) {
|
||||
if s == "Zine" {
|
||||
return Zine, nil
|
||||
}
|
||||
|
||||
return c, fmt.Errorf("Cannot parse category %s", s)
|
||||
}
|
||||
|
||||
type ItemVariant struct {
|
||||
gorm.Model
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Price float64 `json:"price" gorm:"not null"`
|
||||
InStock bool `json:"inStock" gorm:"default:true"`
|
||||
ShopItemID uint
|
||||
}
|
||||
|
||||
type ShopItem struct {
|
||||
gorm.Model
|
||||
Name string `json:"name" binding:"required" gorm:"unique;not null"`
|
||||
Abstract string `json:"abstract" binding:"required"`
|
||||
Description string `json:"description" binding:"required"`
|
||||
Category Category `json:"category"`
|
||||
Variants []ItemVariant `json:"variant"`
|
||||
BasePrice float64 `json:"basePrice"`
|
||||
IsPublic bool `json:"isPublic" gorm:"default:true"`
|
||||
Tags []Tag `gorm:"many2many:item_tags;"`
|
||||
Image string
|
||||
Pdf string
|
||||
PrintMode string `json:"printMode" gorm:"default:CreateBooklet"`
|
||||
WasPrinted bool `gorm:"default:false"`
|
||||
}
|
||||
|
||||
@@ -2,22 +2,56 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
gorm.Model
|
||||
Name string `json:"name" binding:"required" gorm:"not null"`
|
||||
Name string `json:"name" binding:"required" gorm:"not null"`
|
||||
Color string `json:"color" binding:"required" gorm:"default:pink"`
|
||||
ShopItems []ShopItem `gorm:"many2many:item_tags;"`
|
||||
}
|
||||
|
||||
type CheckedTag struct {
|
||||
Tag
|
||||
Checked string
|
||||
}
|
||||
|
||||
func NewTag(ctx *gin.Context) (Tag, error) {
|
||||
colors := []string{
|
||||
"red",
|
||||
"orange",
|
||||
"amber",
|
||||
"yellow",
|
||||
"lime",
|
||||
"green",
|
||||
"emerald",
|
||||
"teal",
|
||||
"cyan",
|
||||
"sky",
|
||||
"blue",
|
||||
"indigo",
|
||||
"violet",
|
||||
"purple",
|
||||
"fuchsia",
|
||||
"pink",
|
||||
"rose",
|
||||
"slate",
|
||||
"gray",
|
||||
"zinc",
|
||||
"neutral",
|
||||
"stone",
|
||||
}
|
||||
n := rand.Int() % len(colors)
|
||||
|
||||
name := ctx.PostForm("name")
|
||||
|
||||
// Convert the price string to float64
|
||||
// Convert the price string to float64
|
||||
tag := Tag{
|
||||
Name: name,
|
||||
Name: name,
|
||||
Color: colors[n],
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
@@ -27,14 +61,13 @@ func NewTag(ctx *gin.Context) (Tag, error) {
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
|
||||
func NewTagByJson(ctx *gin.Context) (Tag, error) {
|
||||
var tag Tag
|
||||
err := ctx.ShouldBindJSON(&tag)
|
||||
|
||||
|
||||
if err != nil {
|
||||
return Tag{}, err
|
||||
}
|
||||
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
@@ -4,9 +4,15 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RegisterToken struct {
|
||||
gorm.Model
|
||||
Token string `json:"token" binding:"required" gorm:"unique;not null"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
100
repositories/InvoiceRepository.go
Normal file
100
repositories/InvoiceRepository.go
Normal 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
|
||||
}
|
||||
103
repositories/OrderRepository.go
Normal file
103
repositories/OrderRepository.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
)
|
||||
|
||||
type OrderRepository interface {
|
||||
Create(models.Order) (models.Order, error)
|
||||
GetAll() ([]models.Order, error)
|
||||
GetById(string) (models.Order, error)
|
||||
GetBySession(string) (models.Order, error)
|
||||
GetByToken(string) (models.Order, error)
|
||||
Update(models.Order) (models.Order, error)
|
||||
DeleteById(string) error
|
||||
DeleteByToken(string) error
|
||||
}
|
||||
|
||||
type GORMOrderRepository struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewGORMOrderRepository(db *gorm.DB) OrderRepository {
|
||||
return &GORMOrderRepository{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) Create(order models.Order) (models.Order, error) {
|
||||
//Omit the shopitem so it is not created again in db leading to unique constain fails
|
||||
result := r.DB.Omit("CartItems").Create(&order)
|
||||
if result.Error != nil {
|
||||
return models.Order{}, result.Error
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) GetAll() ([]models.Order, error) {
|
||||
var orders []models.Order
|
||||
result := r.DB.Preload("CartItems").Find(&orders)
|
||||
|
||||
return orders, result.Error
|
||||
}
|
||||
|
||||
func (t *GORMOrderRepository) GetById(id string) (models.Order, error) {
|
||||
orderId, err := strconv.Atoi(id)
|
||||
|
||||
if err != nil {
|
||||
return models.Order{}, err
|
||||
}
|
||||
|
||||
var order models.Order
|
||||
result := t.DB.Preload("CartItems").First(&order, uint(orderId))
|
||||
|
||||
if result.Error != nil {
|
||||
return models.Order{}, result.Error
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) GetBySession(sessionId string) (models.Order, error) {
|
||||
var orders models.Order
|
||||
result := r.DB.Preload("CartItems").Preload("CartItems.ShopItem").Preload("CartItems.ItemVariant").Where("session_id = ?", sessionId).First(&orders)
|
||||
|
||||
return orders, result.Error
|
||||
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) GetByToken(token string) (models.Order, error) {
|
||||
var orders models.Order
|
||||
result := r.DB.Preload("CartItems").Preload("CartItems.ShopItem").Preload("CartItems.ItemVariant").Where("token = ?", token).First(&orders)
|
||||
|
||||
return orders, result.Error
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) Update(order models.Order) (models.Order, error) {
|
||||
result := r.DB.Save(&order)
|
||||
if result.Error != nil {
|
||||
return models.Order{}, result.Error
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) DeleteById(id string) error {
|
||||
orderId, err := strconv.Atoi(id)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := r.DB.Delete(&models.Order{}, orderId)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
func (r *GORMOrderRepository) DeleteByToken(token string) error {
|
||||
result := r.DB.Where("token = ?", token).Delete(&models.Order{})
|
||||
return result.Error
|
||||
}
|
||||
90
repositories/cartItemRepository.go
Normal file
90
repositories/cartItemRepository.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package repositories
|
||||
|
||||
import(
|
||||
"strconv"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
)
|
||||
|
||||
type CartItemRepository interface {
|
||||
Create(models.CartItem) (models.CartItem, error)
|
||||
GetAll() ([]models.CartItem, error)
|
||||
GetById(string) (models.CartItem, error)
|
||||
GetAllBySession(string) ([]models.CartItem, error)
|
||||
Update(models.CartItem) (models.CartItem, error)
|
||||
DeleteById(string) (error)
|
||||
}
|
||||
|
||||
type GORMCartItemRepository struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewGORMCartItemRepository(db *gorm.DB) CartItemRepository {
|
||||
return &GORMCartItemRepository{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *GORMCartItemRepository) Create(cartItem models.CartItem) (models.CartItem, error) {
|
||||
//Omit the shopitem so it is not created again in db leading to unique constain fails
|
||||
result := r.DB.Omit("ShopItem").Create(&cartItem)
|
||||
if result.Error != nil {
|
||||
return models.CartItem{}, result.Error
|
||||
}
|
||||
|
||||
return cartItem, nil
|
||||
}
|
||||
|
||||
func (r *GORMCartItemRepository) GetAll() ([]models.CartItem, error) {
|
||||
var cartItems []models.CartItem
|
||||
result := r.DB.Preload("ShopItem").Preload("ItemVariant").Find(&cartItems)
|
||||
|
||||
return cartItems, result.Error
|
||||
}
|
||||
|
||||
func (t *GORMCartItemRepository) GetById(id string) (models.CartItem, error) {
|
||||
cartItemId, err := strconv.Atoi(id)
|
||||
|
||||
if err != nil {
|
||||
return models.CartItem{}, err
|
||||
}
|
||||
|
||||
var cartItem models.CartItem
|
||||
result := t.DB.Preload("ShopItem").Preload("ItemVariant").First(&cartItem, uint(cartItemId))
|
||||
|
||||
if result.Error != nil {
|
||||
return models.CartItem{}, result.Error
|
||||
}
|
||||
|
||||
return cartItem, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *GORMCartItemRepository) GetAllBySession(sessionId string) ([]models.CartItem, error) {
|
||||
var cartItems []models.CartItem
|
||||
result := r.DB.Preload("ShopItem").Preload("ItemVariant").Where("session_id = ?", sessionId).Find(&cartItems)
|
||||
|
||||
return cartItems, result.Error
|
||||
|
||||
}
|
||||
|
||||
func (r *GORMCartItemRepository) Update(cartItem models.CartItem) (models.CartItem, error) {
|
||||
result := r.DB.Save(&cartItem)
|
||||
if result.Error != nil {
|
||||
return models.CartItem{}, result.Error
|
||||
}
|
||||
|
||||
return cartItem, nil
|
||||
}
|
||||
|
||||
func (r *GORMCartItemRepository) DeleteById(id string) error {
|
||||
cartItemId, err := strconv.Atoi(id)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := r.DB.Omit("ShopItem").Omit("ItemVariant").Delete(&models.CartItem{}, cartItemId)
|
||||
return result.Error
|
||||
}
|
||||
81
repositories/configRepository.go
Normal file
81
repositories/configRepository.go
Normal 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
|
||||
}
|
||||
82
repositories/paperRepository.go
Normal file
82
repositories/paperRepository.go
Normal 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
|
||||
}
|
||||
82
repositories/printJobRepository.go
Normal file
82
repositories/printJobRepository.go
Normal 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
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,17 +1,24 @@
|
||||
package repositories
|
||||
|
||||
import(
|
||||
"os"
|
||||
"gorm.io/gorm"
|
||||
import (
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
)
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
)
|
||||
|
||||
var(
|
||||
ShopItems ShopItemRepository
|
||||
Users UserRepository
|
||||
Tags TagRepository
|
||||
var (
|
||||
ShopItems ShopItemRepository
|
||||
Users UserRepository
|
||||
Tags TagRepository
|
||||
CartItems CartItemRepository
|
||||
Orders OrderRepository
|
||||
Tokens RegisterTokenRepository
|
||||
ConfigOptions ConfigRepository
|
||||
Papers PaperRepository
|
||||
PrintJobs PrintJobRepository
|
||||
Invoices InvoiceRepository
|
||||
)
|
||||
|
||||
func InitRepositories() {
|
||||
@@ -20,7 +27,18 @@ func InitRepositories() {
|
||||
panic("failed to connect to database")
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(&models.ShopItem{}, &models.User{}, &models.Tag{})
|
||||
err = db.AutoMigrate(&models.ShopItem{},
|
||||
&models.ItemVariant{},
|
||||
&models.User{},
|
||||
&models.Tag{},
|
||||
&models.CartItem{},
|
||||
&models.Order{},
|
||||
&models.Config{},
|
||||
&models.Paper{},
|
||||
&models.PrintJob{},
|
||||
&models.Invoice{},
|
||||
&models.RegisterToken{})
|
||||
|
||||
if err != nil {
|
||||
panic("failed to migrate database")
|
||||
}
|
||||
@@ -28,4 +46,11 @@ func InitRepositories() {
|
||||
ShopItems = NewGORMShopItemRepository(db)
|
||||
Users = NewGORMUserRepository(db)
|
||||
Tags = NewGORMTagRepository(db)
|
||||
CartItems = NewGORMCartItemRepository(db)
|
||||
Orders = NewGORMOrderRepository(db)
|
||||
Tokens = NewGORMRegisterTokenRepository(db)
|
||||
ConfigOptions = NewGORMConfigRepository(db)
|
||||
Papers = NewGORMPaperRepository(db)
|
||||
PrintJobs = NewGORMPrintJobRepository(db)
|
||||
Invoices = NewGORMInvoiceRepository(db)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
package repositories
|
||||
|
||||
import(
|
||||
"strconv"
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
)
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
)
|
||||
|
||||
type ShopItemRepository interface {
|
||||
Create(models.ShopItem) (models.ShopItem, error)
|
||||
Create(models.ShopItem) (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)
|
||||
GetById(string) (models.ShopItem, error)
|
||||
//GetByTagId(string) ([]models.ShopItem, error)
|
||||
GetNextOfId(string) (models.ShopItem, error)
|
||||
GetPreviousOfId(string) (models.ShopItem, error)
|
||||
GetByTagId(string) ([]models.ShopItem, error)
|
||||
GetVariantById(string) (models.ItemVariant, error)
|
||||
Update(models.ShopItem) (models.ShopItem, error)
|
||||
DeleteById(string) error
|
||||
}
|
||||
@@ -30,7 +39,7 @@ func NewGORMShopItemRepository(db *gorm.DB) ShopItemRepository {
|
||||
func (r *GORMShopItemRepository) Create(shopItem models.ShopItem) (models.ShopItem, error) {
|
||||
result := r.DB.Create(&shopItem)
|
||||
if result.Error != nil {
|
||||
return models.ShopItem{}, result.Error
|
||||
return models.ShopItem{}, result.Error
|
||||
}
|
||||
|
||||
return shopItem, nil
|
||||
@@ -38,17 +47,39 @@ func (r *GORMShopItemRepository) Create(shopItem models.ShopItem) (models.ShopIt
|
||||
|
||||
func (r *GORMShopItemRepository) GetAll() ([]models.ShopItem, error) {
|
||||
var shopItems []models.ShopItem
|
||||
result := r.DB.Preload("Tags").Find(&shopItems)
|
||||
result := r.DB.Preload("Tags").Preload("Variants").Find(&shopItems)
|
||||
|
||||
return shopItems, result.Error
|
||||
}
|
||||
|
||||
func (r *GORMShopItemRepository) GetAllPublic() ([]models.ShopItem, error) {
|
||||
func (r *GORMShopItemRepository) GetAllSorted(sortString string) ([]models.ShopItem, error) {
|
||||
var shopItems []models.ShopItem
|
||||
result := r.DB.Preload("Tags").Where("is_public = 1").Find(&shopItems)
|
||||
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) {
|
||||
var shopItems []models.ShopItem
|
||||
result := r.DB.Preload("Tags").Preload("Variants").Where("is_public = 1").Find(&shopItems)
|
||||
|
||||
return shopItems, result.Error
|
||||
}
|
||||
|
||||
func (r *GORMShopItemRepository) GetById(id string) (models.ShopItem, error) {
|
||||
@@ -59,7 +90,7 @@ func (r *GORMShopItemRepository) GetById(id string) (models.ShopItem, error) {
|
||||
}
|
||||
|
||||
var shopItem models.ShopItem
|
||||
result := r.DB.Preload("Tags").First(&shopItem, uint(shopItemId))
|
||||
result := r.DB.Preload("Tags").Preload("Variants").First(&shopItem, uint(shopItemId))
|
||||
|
||||
if result.Error != nil {
|
||||
return models.ShopItem{}, result.Error
|
||||
@@ -68,12 +99,75 @@ func (r *GORMShopItemRepository) GetById(id string) (models.ShopItem, error) {
|
||||
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) {
|
||||
tagId, err := strconv.Atoi(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var shopItems []models.ShopItem
|
||||
result := r.DB.Joins("JOIN item_tags ON item_tags.shop_item_id = shop_items.id").Where("item_tags.tag_id = ?", tagId).Preload("Tags").Preload("Variants").Find(&shopItems)
|
||||
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return shopItems, nil
|
||||
}
|
||||
|
||||
func (r *GORMShopItemRepository) GetVariantById(id string) (models.ItemVariant, error) {
|
||||
itemVariantId, err := strconv.Atoi(id)
|
||||
|
||||
if err != nil {
|
||||
return models.ItemVariant{}, err
|
||||
}
|
||||
|
||||
var itemVariant models.ItemVariant
|
||||
result := r.DB.First(&itemVariant, uint(itemVariantId))
|
||||
|
||||
if result.Error != nil {
|
||||
return models.ItemVariant{}, result.Error
|
||||
}
|
||||
|
||||
return itemVariant, nil
|
||||
}
|
||||
|
||||
func (r *GORMShopItemRepository) Update(shopItem models.ShopItem) (models.ShopItem, error) {
|
||||
err := r.DB.Model(&shopItem).Association("Tags").Replace(shopItem.Tags)
|
||||
if err != nil {
|
||||
return models.ShopItem{}, err
|
||||
}
|
||||
|
||||
err = r.DB.Model(&shopItem).Association("Variants").Replace(shopItem.Variants)
|
||||
if err != nil {
|
||||
return models.ShopItem{}, err
|
||||
}
|
||||
|
||||
result := r.DB.Save(&shopItem)
|
||||
if result.Error != nil {
|
||||
return models.ShopItem{}, result.Error
|
||||
|
||||
@@ -5,7 +5,7 @@ import(
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
)
|
||||
|
||||
type TagRepository interface {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package repositories
|
||||
|
||||
import(
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
)
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
Create(models.User) (models.User, error)
|
||||
Create(models.User) (models.User, error)
|
||||
GetByEmail(string) (models.User, error)
|
||||
GetById(interface{}) (models.User, error)
|
||||
IsEmpty() (bool, error)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if result.Error != nil {
|
||||
@@ -53,3 +54,18 @@ func (u *GORMUserRepository) GetById(id interface{}) (models.User, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package services
|
||||
import(
|
||||
"fmt"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
"example.com/gin/test/repositories"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
)
|
||||
|
||||
var(
|
||||
@@ -18,7 +18,7 @@ func (u *ShopItemService) NewShopItem(name string, abstract string, description
|
||||
Name: name,
|
||||
Abstract: abstract,
|
||||
Description: description,
|
||||
Price: price,
|
||||
BasePrice: price,
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package services
|
||||
|
||||
import(
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"time"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"example.com/gin/test/models"
|
||||
"example.com/gin/test/repositories"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/models"
|
||||
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
|
||||
)
|
||||
|
||||
var(
|
||||
var (
|
||||
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, 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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
@@ -35,7 +35,7 @@ func (u *UserService) Register(name string, email string, password string) (mode
|
||||
return user, nil
|
||||
}
|
||||
|
||||
//return jwt tokenstring on success
|
||||
// return jwt tokenstring on success
|
||||
func (u *UserService) Login(email string, password string) (string, error) {
|
||||
//lookup requested user
|
||||
user, err := repositories.Users.GetByEmail(email)
|
||||
|
||||
BIN
static/img/logo-black.png
Normal file
BIN
static/img/logo-black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
static/img/logo-white.png
Normal file
BIN
static/img/logo-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
1244
static/output.css
1244
static/output.css
File diff suppressed because it is too large
Load Diff
21
test/lib.nix
Normal file
21
test/lib.nix
Normal file
@@ -0,0 +1,21 @@
|
||||
# tests/lib.nix
|
||||
# based on https://blog.thalheim.io/2023/01/08/how-to-use-nixos-testing-framework-with-flakes/
|
||||
# The first argument to this function is the test module itself
|
||||
test:
|
||||
# These arguments are provided by `flake.nix` on import, see checkArgs
|
||||
{ pkgs, self}:
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
# this imports the nixos library that contains our testing framework
|
||||
nixos-lib = import (pkgs.path + "/nixos/lib") {};
|
||||
in
|
||||
(nixos-lib.runTest {
|
||||
hostPkgs = pkgs;
|
||||
# This speeds up the evaluation by skipping evaluating documentation (optional)
|
||||
defaults.documentation.enable = lib.mkDefault false;
|
||||
# This makes `self` available in the NixOS configuration of our virtual machines.
|
||||
# This is useful for referencing modules or packages from your own flake
|
||||
# as well as importing from other flakes.
|
||||
node.specialArgs = { inherit self; };
|
||||
imports = [ test ];
|
||||
}).config.result
|
||||
21
test/test.nix
Normal file
21
test/test.nix
Normal file
@@ -0,0 +1,21 @@
|
||||
# ./tests/hello-world-server.nix
|
||||
(import ./lib.nix) {
|
||||
name = "from-nixos";
|
||||
nodes = {
|
||||
# `self` here is set by using specialArgs in `lib.nix`
|
||||
node1 = { self, pkgs, ... }: {
|
||||
imports = [ self.nixosModules.zineshop ];
|
||||
|
||||
services.zineshop.enable = true;
|
||||
environment.systemPackages = [ pkgs.curl ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all() # wait for our service to start
|
||||
node1.wait_for_unit("zineshop.service")
|
||||
output = node1.succeed("curl localhost:8080")
|
||||
# Check if our webserver returns the expected result
|
||||
assert "Zine Shop" in output, f"'{output}' does not contain 'Zine Shop'"
|
||||
'';
|
||||
}
|
||||
49
tester.go
49
tester.go
@@ -1,37 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
func testFunc() {
|
||||
|
||||
url := "http://localhost:8080/test"
|
||||
method := "GET"
|
||||
method := "GET"
|
||||
|
||||
client := &http.Client {
|
||||
}
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
req.Header.Add("Authorization", "Basic dXNlcjpwYXNzd29yZA==")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
req.Header.Add("Authorization", "Basic dXNlcjpwYXNzd29yZA==")
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
32
utils/utils.go
Normal file
32
utils/utils.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/pdfcpu/pdfcpu/pkg/api"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<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/circlea.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">Add an Item</h2>
|
||||
</div>
|
||||
|
||||
@@ -55,11 +55,25 @@
|
||||
-->
|
||||
|
||||
<div>
|
||||
<input type="hidden" name="category" value="Zine" required>
|
||||
<input type="hidden" id="variant-name1" name="variant-name[]" value="B/W" required>
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="price" class="block text-sm/6 font-medium text-gray-900">Price</label>
|
||||
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price B/W</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<input type="number" name="price" id="price" 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" 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>
|
||||
<input type="hidden" id="variant-name2" name="variant-name[]" value="Colored" required>
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="variant-value2" class="block text-sm/6 font-medium text-gray-900">Price Colored</label>
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,6 +100,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-900">
|
||||
PDF
|
||||
</label>
|
||||
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
||||
<div class="space-y-1 text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-900" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="pdf-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<span class="">Upload a file</span>
|
||||
<input id="pdf-upload" name="pdf" type="file" accept="application/pdf" class="sr-only">
|
||||
</label>
|
||||
<p class="pl-1 text-gray-900">or drag and drop</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-900">
|
||||
PDF up to 50MB
|
||||
</p>
|
||||
</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>
|
||||
<option value="TriFold">Tri-Fold (Flyer)</option>
|
||||
</select>
|
||||
|
||||
|
||||
<p class="text-sm">Choose 'Create Booklet' if its just a normal PDF (not converted to booklet already)</p>
|
||||
|
||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||
{{ .data.error }}
|
||||
</p>
|
||||
@@ -101,5 +152,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
|
||||
35
views/addtag.html
Normal file
35
views/addtag.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{{ 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">Add Tag</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form class="space-y-6" action="#" method="POST" enctype="multipart/form-data">
|
||||
<div>
|
||||
<label for="name" class="block text-sm/6 font-medium text-gray-900">Name</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>
|
||||
|
||||
<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>
|
||||
<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">Add Tag</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
53
views/batchupload.html
Normal file
53
views/batchupload.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{{ 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">Batch Upload</h2>
|
||||
Select multiple files on the upload. For each a new Shop Item will be created with the Filename as Name, Abstract
|
||||
and Description.<br>
|
||||
Afterwards you should edit every item and set the proper Values for that, price and so on.
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form class="space-y-6" action="/batchupload" method="POST" enctype="multipart/form-data">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-900">
|
||||
PDF
|
||||
</label>
|
||||
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
||||
<div class="space-y-1 text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-900" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="pdf-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<span class="">Upload a file</span>
|
||||
<input id="pdf-upload" name="pdf" type="file" multiple="multiple" accept="application/pdf" class="sr-only">
|
||||
</label>
|
||||
<p class="pl-1 text-gray-900">or drag and drop</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-900">
|
||||
PDF up to 50MB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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">Add Item</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
91
views/cart.html
Normal file
91
views/cart.html
Normal file
@@ -0,0 +1,91 @@
|
||||
{{ template "header.html" . }}
|
||||
|
||||
<section class="py-24 relative">
|
||||
<div class="w-full max-w-7xl px-4 md:px-5 lg-6 mx-auto">
|
||||
<h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Shopping Cart
|
||||
</h2>
|
||||
|
||||
{{ range .data.cartItems }}
|
||||
|
||||
<div class="rounded-3xl border-2 border-gray-200 p-4 lg:p-8 grid grid-cols-12 mb-8 max-lg:max-w-lg max-lg:mx-auto gap-y-4 ">
|
||||
<div class="col-span-12 lg:col-span-2 img box">
|
||||
<img src="/{{ .ShopItem.Image }}" alt="speaker image" class="max-lg:w-full lg:w-[180px] rounded-lg object-cover">
|
||||
</div>
|
||||
<div class="col-span-12 lg:col-span-10 detail w-full lg:pl-3">
|
||||
<div class="flex items-center justify-between w-full mb-4">
|
||||
<h5 class="font-manrope font-bold text-2xl leading-9 text-gray-900">{{ .ShopItem.Name }} - {{ .ItemVariant.Name}}</h5>
|
||||
<form action="/cart/delete" method="POST">
|
||||
<input type="hidden" id="{{ .ID }}" name="id" value="{{ .ID }}">
|
||||
<button type="submit" class="rounded-full group flex items-center justify-center focus-within:outline-red-500">
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<circle class="fill-red-50 transition-all duration-500 group-hover:fill-red-400"
|
||||
cx="17" cy="17" r="17" fill="" />
|
||||
<path class="stroke-red-500 transition-all duration-500 group-hover:stroke-white"
|
||||
d="M14.1673 13.5997V12.5923C14.1673 11.8968 14.7311 11.333 15.4266 11.333H18.5747C19.2702 11.333 19.834 11.8968 19.834 12.5923V13.5997M19.834 13.5997C19.834 13.5997 14.6534 13.5997 11.334 13.5997C6.90804 13.5998 27.0933 13.5998 22.6673 13.5997C21.5608 13.5997 19.834 13.5997 19.834 13.5997ZM12.4673 13.5997H21.534V18.8886C21.534 20.6695 21.534 21.5599 20.9807 22.1131C20.4275 22.6664 19.5371 22.6664 17.7562 22.6664H16.2451C14.4642 22.6664 13.5738 22.6664 13.0206 22.1131C12.4673 21.5599 12.4673 20.6695 12.4673 18.8886V13.5997Z"
|
||||
stroke="#EF4444" stroke-width="1.6" stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="font-normal text-base leading-7 text-gray-500 mb-6">
|
||||
{{ .ShopItem.Abstract }}
|
||||
</p>
|
||||
<form action="/cart/edit" method="POST">
|
||||
<div class="flex justify-between items-center">
|
||||
<input type="hidden" id="{{ .ID }}" name="id" value="{{ .ID }}">
|
||||
<div class="flex items-center gap-4">
|
||||
<input type="number" name="amount" value="{{ .Quantity }}" step="1" min="0" 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">
|
||||
<button type="submit" name="action" value="setAmount"
|
||||
class="block w-full bg-indigo-500 rounded-md px-3 py-1.5 text-base text-white placeholder:text-gray-400 sm:text-sm/6 duration-500 hover:bg-indigo-700">
|
||||
Set Amount
|
||||
</button>
|
||||
</div>
|
||||
<h6 class="text-indigo-600 font-manrope font-bold text-2xl leading-9 text-right">{{ .Quantity}} x {{ .ItemVariant.Price }}€</h6>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
|
||||
<div class="flex flex-col md:flex-row items-center md:items-center justify-between lg:px-6 pb-6 max-lg:max-w-lg max-lg:mx-auto">
|
||||
<h5 class="text-gray-900 font-manrope font-semibold text-2xl leading-9 w-full max-md:text-center max-md:mb-4">Subtotal</h5>
|
||||
|
||||
<div class="flex items-center justify-between gap-5 ">
|
||||
<h6 class="font-manrope font-bold text-3xl lead-10 text-indigo-600">{{ .data.priceTotal }}€</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<form action="/checkout" method="GET">
|
||||
<div class="flex flex-col md:flex-row items-center md:items-center justify-between lg:px-6 pb-6 border-b border-gray-200 max-lg:max-w-lg max-lg:mx-auto">
|
||||
<h2 class="text-gray-900 font-manrope font-semibold leading-9 w-full max-md:text-center max-md:mb-4">Select shipping method</h2>
|
||||
<select name="shippingMethod" id="shippingMethod" 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="">Shipping</option>
|
||||
{{ range .data.shipping }}
|
||||
<option value="{{ .Id }}">{{ .Name }} - {{ .Price }}€</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-lg:max-w-lg max-lg:mx-auto">
|
||||
<p class="font-normal text-base leading-7 text-gray-500 text-center mb-5 mt-6">Shipping calculated at checkout</p>
|
||||
<button type="submit"
|
||||
class="rounded py-4 px-6 bg-indigo-500 text-white font-semibold text-lg w-full text-center transition-all duration-500 hover:bg-indigo-700 ">Checkout</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
92
views/checkout.html
Normal file
92
views/checkout.html
Normal file
@@ -0,0 +1,92 @@
|
||||
{{ 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">Checkout</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form class="space-y-6" action="/checkout" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="shippingMethod" value="{{ .shippingMethod }}" required>
|
||||
{{ if .askAddress }}
|
||||
<div>
|
||||
<label for="firstName" class="block text-sm/6 font-medium text-gray-900">First Name</label>
|
||||
<div class="mt-2">
|
||||
<input type="text" name="firstName" id="firstName" 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="lastName" class="block text-sm/6 font-medium text-gray-900">Last Name</label>
|
||||
<div class="mt-2">
|
||||
<input type="text" name="lastName" id="lastName" 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="Address" class="block text-sm/6 font-medium text-gray-900">Streetname and Number</label>
|
||||
<div class="mt-2">
|
||||
<input type="text" name="address" id="address" 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="postalCode" class="block text-sm/6 font-medium text-gray-900">Postal Code</label>
|
||||
<div class="mt-2">
|
||||
<input type="text" name="postalCode" id="postalCode" 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="city" class="block text-sm/6 font-medium text-gray-900">City</label>
|
||||
<div class="mt-2">
|
||||
<input type="text" name="city" id="city" 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="country" class="block text-sm/6 font-medium text-gray-900">Country</label>
|
||||
<div class="mt-2">
|
||||
<input type="text" name="country" id="country" 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>
|
||||
|
||||
{{ end }}
|
||||
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm/6 font-medium text-gray-900">E-Mail</label>
|
||||
<p class="text-sm/8 font-small text-gray-600">Without E-Mail you wont receive an order confirmation.</p>
|
||||
<div class="mt-2">
|
||||
<input type="email" name="email" id="email" 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="comment" class="block text-sm/6 font-medium text-gray-900" for="passwordConfirmation">Comment</label>
|
||||
<textarea id="comment" name="comment" type="textarea" class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring"></textarea>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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">Preview Order</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
23
views/colors.html
Normal file
23
views/colors.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<span class="bg-red-100 text-red-800 bg-red-900 text-red-300">FOO</span>
|
||||
<span class="bg-orange-100 text-orange-800 bg-orange-900 text-orange-300">FOO</span>
|
||||
<span class="bg-amber-100 text-amber-800 bg-amber-900 text-amber-300">FOO</span>
|
||||
<span class="bg-yellow-100 text-yellow-800 bg-yellow-900 text-yellow-300">FOO</span>
|
||||
<span class="bg-lime-100 text-lime-800 bg-lime-900 text-lime-300">FOO</span>
|
||||
<span class="bg-green-100 text-green-800 bg-green-900 text-green-300">FOO</span>
|
||||
<span class="bg-emerald-100 text-emerald-800 bg-emerald-900 text-emerald-300">FOO</span>
|
||||
<span class="bg-teal-100 text-teal-800 bg-teal-900 text-teal-300">FOO</span>
|
||||
<span class="bg-cyan-100 text-cyan-800 bg-cyan-900 text-cyan-300">FOO</span>
|
||||
<span class="bg-sky-100 text-sky-800 bg-sky-900 text-sky-300">FOO</span>
|
||||
<span class="bg-blue-100 text-blue-800 bg-blue-900 text-blue-300">FOO</span>
|
||||
<span class="bg-indigo-100 text-indigo-800 bg-indigo-900 text-indigo-300">FOO</span>
|
||||
<span class="bg-violet-100 text-violet-800 bg-violet-900 text-violet-300">FOO</span>
|
||||
<span class="bg-purple-100 text-purple-800 bg-purple-900 text-purple-300">FOO</span>
|
||||
<span class="bg-fuchsia-100 text-fuchsia-800 bg-fuchsia-900 text-fuchsia-300">FOO</span>
|
||||
<span class="bg-pink-100 text-pink-800 bg-pink-900 text-pink-300">FOO</span>
|
||||
<span class="bg-rose-100 text-rose-800 bg-rose-900 text-rose-300">FOO</span>
|
||||
<span class="bg-slate-100 text-slate-800 bg-slate-900 text-slate-300">FOO</span>
|
||||
<span class="bg-gray-100 text-gray-800 bg-gray-900 text-gray-300">FOO</span>
|
||||
<span class="bg-zinc-100 text-zinc-800 bg-zinc-900 text-zinc-300">FOO</span>
|
||||
<span class="bg-neutral-100 text-neutral-800 bg-neutral-900 text-neutral-300">FOO</span>
|
||||
<span class="bg-stone-100 text-stone-800 bg-stone-900 text-stone-300">FOO</span>
|
||||
|
||||
33
views/configview.html
Normal file
33
views/configview.html
Normal 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" . }}
|
||||
@@ -4,8 +4,8 @@
|
||||
<div class="bg-gray-200 rounded m-4 p-4">
|
||||
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
||||
<i class="text-xs">{{ .data.shopItem.Description }}</i>
|
||||
<p class="">Price: {{ .data.shopItem.Price }}</p>
|
||||
{{ if .loggedIn }}
|
||||
<p class="">Price: {{ .data.shopItem.BasePrice }}</p>
|
||||
{{ if .isAdmin }}
|
||||
|
||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||
Do you really want to delete this item??
|
||||
|
||||
@@ -1,14 +1,51 @@
|
||||
{{ 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/circlea.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>
|
||||
|
||||
<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">
|
||||
</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 class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form class="space-y-6" action="#" method="POST">
|
||||
<form class="space-y-6" action="#" method="POST" enctype="multipart/form-data">
|
||||
<div>
|
||||
<label for="name" class="block text-sm/6 font-medium text-gray-900">Name</label>
|
||||
<div class="mt-2">
|
||||
@@ -28,12 +65,25 @@
|
||||
<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>
|
||||
|
||||
<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="TriFold">Tri-Fold (Flyer)</option>
|
||||
</select>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm/6 text-gray-900">Select Categories</label>
|
||||
<div class="space-y-2">
|
||||
{{ range .data.tags }}
|
||||
<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>
|
||||
</label>
|
||||
{{ end }}
|
||||
@@ -55,14 +105,28 @@
|
||||
-->
|
||||
|
||||
<div>
|
||||
<input type="hidden" name="category" value="Zine" required>
|
||||
<input type="hidden" id="variant-name1" name="variant-name[]" value="B/W" required>
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="price" class="block text-sm/6 font-medium text-gray-900">Price</label>
|
||||
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price B/W</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<input type="number" name="price" id="price" value="{{ .data.shopItem.Price }}" 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>
|
||||
<input type="hidden" id="variant-name2" name="variant-name[]" value="Colored" required>
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="variant-value2" class="block text-sm/6 font-medium text-gray-900">Price Colored</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<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>
|
||||
<label class="block text-sm font-medium text-gray-900">
|
||||
Image
|
||||
@@ -73,9 +137,9 @@
|
||||
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<label for="image-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<span class="">Upload a file</span>
|
||||
<input id="file-upload" name="file-upload" type="file" class="sr-only">
|
||||
<input id="image-upload" name="image" type="file" accept="image/*" class="sr-only">
|
||||
</label>
|
||||
<p class="pl-1 text-gray-900">or drag and drop</p>
|
||||
</div>
|
||||
@@ -86,13 +150,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<label class="block text-sm font-medium text-gray-900">
|
||||
PDF
|
||||
</label>
|
||||
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
||||
<div class="space-y-1 text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-900" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="pdf-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<span class="">Upload a file</span>
|
||||
<input id="pdf-upload" name="pdf" type="file" accept="application/pdf" class="sr-only">
|
||||
</label>
|
||||
<p class="pl-1 text-gray-900">or drag and drop</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-900">
|
||||
PDF up to 50MB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
38
views/editorders.html
Normal file
38
views/editorders.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{{ 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 Orders</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
{{ range .data.orders }}
|
||||
<form action="/order/{{ .Token }}/edit" method="POST">
|
||||
<div class="max-w-md mx-auto mt-4">
|
||||
<div class="flex">
|
||||
<input type="text" id="name" name="name" value="{{ .Token }}" readonly="readonly" class="flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
|
||||
<div>
|
||||
<select name="order-status" required class="bg-gray-50 border ml-4 border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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="{{ .Status }}">{{ .Status }}</option>
|
||||
<option value="AwaitingConfirmation">AwaitingConfirmation</option>
|
||||
<option value="Received">Received</option>
|
||||
<option value="AwaitingPayment">AwaitingPayment</option>
|
||||
<option value="Payed">Payed</option>
|
||||
<option value="ReadyForPickup">ReadyForPickup</option>
|
||||
<option value="Shipped">Shipped</option>
|
||||
<option value="Cancelled">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" name="action" value="update" class="bg-blue-600 text-white ml-4 rounded px-4 hover:bg-blue-700">Update</button>
|
||||
<button type="button" class="bg-blue-600 text-white ml-4 mr-4 rounded px-4 hover:bg-blue-700"><a
|
||||
href="/order/{{ .Token }}/print">Print</a></button>
|
||||
<button type="button" class="bg-blue-600 text-white mr-4 rounded px-4 hover:bg-blue-700"><a
|
||||
href="/order/{{ .Token }}">View</a></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 }}
|
||||
</div>
|
||||
{{ template "footer.html" . }}
|
||||
11
views/error.html
Normal file
11
views/error.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{{ template "header.html" . }}
|
||||
|
||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||
{{ .error }}
|
||||
</p>
|
||||
|
||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||
{{ .data.error }}
|
||||
</p>
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>FreiRaum</title>
|
||||
<title>Zines</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/static/output.css" rel="stylesheet">
|
||||
@@ -12,8 +12,8 @@
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-8 lg:px-8">
|
||||
<div class="relative flex h-16 items-center justify-between">
|
||||
<div class="flex flex-1 items-center">
|
||||
<a href="/"><div class="flex shrink-0">
|
||||
<img class="h-8 w-auto" src="/static/img/circlea.png" alt="Your Company">
|
||||
<a href="/"><div class="flex-shrink-0 w-full h-full">
|
||||
<img class="h-8 w-auto" src="/static/img/logo-white.png" alt="Your Company">
|
||||
</div></a>
|
||||
<!--
|
||||
{{ if .loggedIn }}
|
||||
@@ -25,16 +25,31 @@
|
||||
{{ end }}
|
||||
-->
|
||||
</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">
|
||||
<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
|
||||
hover:bg-gray-700 hover:text-white">Batch Upload</a>
|
||||
<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>
|
||||
<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
|
||||
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
|
||||
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>
|
||||
</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">
|
||||
<a href="/login" 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">Login</a>
|
||||
<a href="/register" 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">Register</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>
|
||||
{{ end }}
|
||||
</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" . }}
|
||||
55
views/invoice.html
Normal file
55
views/invoice.html
Normal 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
29
views/invoiceview.html
Normal 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" . }}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<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="https://tailwindui.com/plus/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company">
|
||||
<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">Login to your account</h2>
|
||||
</div>
|
||||
|
||||
|
||||
106
views/order.html
Normal file
106
views/order.html
Normal file
@@ -0,0 +1,106 @@
|
||||
{{ template "header.html" . }}
|
||||
|
||||
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16">
|
||||
<div class="mx-auto max-w-screen-xl px-4 2xl:px-0">
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">Order summary</h2>
|
||||
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
Thanks for your order! As soon as your payment arrived we will print your Order.
|
||||
</dd>
|
||||
|
||||
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8">
|
||||
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Order status: {{ .data.order.Status }}</h4>
|
||||
<dl>
|
||||
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
Order Code: {{ .data.order.Token }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8">
|
||||
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Payment information</h4>
|
||||
|
||||
<dl>
|
||||
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
Either you transfer money to our bank account, or you come by and pay in cash.<br><br>
|
||||
|
||||
Miteinander Dresden e.V.*<br>
|
||||
IBAN: DE66500310001076201001<br>
|
||||
BIC: TRODDEF1 (Triodos Bank)<br>
|
||||
Subject: {{ .data.order.Token }}<br>
|
||||
Amount: {{ .data.priceTotal }}€
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8">
|
||||
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Delivery information</h4>
|
||||
|
||||
<dl>
|
||||
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
<p><b>Shipping:</b> {{ .data.shipping.Name }}</p>
|
||||
{{ if .data.askAddress }}
|
||||
<p><b>First Name:</b> {{ .data.order.FirstName }}</p>
|
||||
<p><b>Last Name:</b> {{ .data.order.LastName }}</p>
|
||||
<p><b>Address:</b> {{ .data.order.Address }}</p>
|
||||
<p><b>Postal Code:</b> {{ .data.order.PostalCode }}</p>
|
||||
<p><b>City:</b> {{ .data.order.City }}</p>
|
||||
<p><b>Country:</b> {{ .data.order.Country }}</p>
|
||||
{{ end }}
|
||||
<p><b>Email:</b> {{ .data.order.Email }}</p>
|
||||
<p><b>Comment:</b> {{ .data.order.Comment }}</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 sm:mt-8">
|
||||
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800">
|
||||
<table class="w-full text-left font-medium text-gray-900 dark:text-white ">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-800">
|
||||
{{ range .data.order.CartItems }}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap py-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="#" 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>
|
||||
<a href="/shopitems/{{ .ShopItem.ID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 text-base font-normal text-gray-900 dark:text-white">x{{ .Quantity }}</td>
|
||||
<td class="p-4 text-right text-base font-bold text-gray-900 dark:text-white">{{ .ItemVariant.Price }}€</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-6">
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-gray-500 dark:text-gray-400">Original price</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.priceProducts }}€</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-gray-500 dark:text-gray-400">Shipping</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.shipping.Price }}€</dd>
|
||||
</dl>
|
||||
|
||||
</div>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700">
|
||||
<dt class="text-lg font-bold text-gray-900 dark:text-white">Total</dt>
|
||||
<dd class="text-lg font-bold text-gray-900 dark:text-white">{{ .data.priceTotal }}€</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
88
views/orderpreview.html
Normal file
88
views/orderpreview.html
Normal file
@@ -0,0 +1,88 @@
|
||||
{{ template "header.html" . }}
|
||||
|
||||
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16">
|
||||
<form action="/order" method="POST" class="mx-auto max-w-screen-xl px-4 2xl:px-0">
|
||||
<input type="hidden" name="confirm-order" value="true" required>
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">Order summary</h2>
|
||||
|
||||
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8">
|
||||
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Delivery information</h4>
|
||||
|
||||
<dl>
|
||||
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
<p><b>Shipping:</b> {{ .data.shipping.Name }}</p>
|
||||
{{ if .data.askAddress }}
|
||||
<p><b>First Name:</b> {{ .data.order.FirstName }}</p>
|
||||
<p><b>Last Name:</b> {{ .data.order.LastName }}</p>
|
||||
<p><b>Address:</b> {{ .data.order.Address }}</p>
|
||||
<p><b>Postal Code:</b> {{ .data.order.PostalCode }}</p>
|
||||
<p><b>City:</b> {{ .data.order.City }}</p>
|
||||
<p><b>Country:</b> {{ .data.order.Country }}</p>
|
||||
{{ end }}
|
||||
<p><b>Email:</b> {{ .data.order.Email }}</p>
|
||||
<p><b>Comment:</b> {{ .data.order.Comment }}</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<a href="/checkout?shippingMethod={{ .data.order.Shipping }}" data-modal-target="billingInformationModal" data-modal-toggle="billingInformationModal" class="text-base font-medium text-primary-700 hover:underline dark:text-primary-500">Edit</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 sm:mt-8">
|
||||
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800">
|
||||
<table class="w-full text-left font-medium text-gray-900 dark:text-white ">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-800">
|
||||
{{ range .data.order.CartItems }}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap py-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="#" 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>
|
||||
<a href="/shopitems/{{ .ShopItem.ID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 text-base font-normal text-gray-900 dark:text-white">x{{ .Quantity }}</td>
|
||||
<td class="p-4 text-right text-base font-bold text-gray-900 dark:text-white">{{ .ItemVariant.Price }}€</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-6">
|
||||
<h4 class="text-xl font-semibold text-gray-900 dark:text-white">Order summary</h4>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-gray-500 dark:text-gray-400">Original price</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.priceProducts }}€</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-gray-500 dark:text-gray-400">Shipping</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.shipping.Price }}€</dd>
|
||||
</dl>
|
||||
|
||||
</div>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700">
|
||||
<dt class="text-lg font-bold text-gray-900 dark:text-white">Total</dt>
|
||||
<dd class="text-lg font-bold text-gray-900 dark:text-white">{{ .data.priceTotal }}€</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="gap-4 sm:flex sm:items-center">
|
||||
<button type="button" class="w-full rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"><a href="/">Return to Shopping</a></button>
|
||||
|
||||
<button type="submit" class="w-full bg-gray-900 dark:bg-gray-600 rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700">Place binding order</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
82
views/paperview.html
Normal file
82
views/paperview.html
Normal 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
22
views/printstarted.html
Normal 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" . }}
|
||||
88
views/printvariant.html
Normal file
88
views/printvariant.html
Normal file
@@ -0,0 +1,88 @@
|
||||
{{ template "header.html" . }}
|
||||
|
||||
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<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">Zineshop Print
|
||||
Service
|
||||
</h2>
|
||||
<p class="font-normal text-base leading-7 text-gray-900 text-left mb-5 mt-6">
|
||||
Pressing Print will automatically print the given Zines for you.<br>
|
||||
Add Zines for printing simply by adding them to the Cart
|
||||
</p>
|
||||
|
||||
<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
|
||||
example you can put colored paper there to have a nice looking front page, and the rest will be normal paper.
|
||||
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.
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
<form action="/print" method="POST">
|
||||
<table class="w-full text-left font-medium text-gray-900 dark:text-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-800">
|
||||
{{ range .data.cartItems }}
|
||||
<tr>
|
||||
<input type="hidden" name="variant-id[]" value="{{ .ItemVariant.ID }}" required>
|
||||
<td class="whitespace-nowrap py-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<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" />
|
||||
</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>
|
||||
</div>
|
||||
</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">
|
||||
<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">CoverPage: None</option>
|
||||
{{ range $.data.paper }}
|
||||
<option value="{{ .ID }}">{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2</option>
|
||||
{{ end}}
|
||||
</select>
|
||||
</td>
|
||||
<td class="whitespace-nowrap py-4">
|
||||
Amount:
|
||||
</td>
|
||||
<td class="p-4 text-right text-base font-bold text-gray-900 dark:text-white">
|
||||
<div>
|
||||
<div class="mt-2">
|
||||
<input type="number" name="variant-amount[]" value="{{ .Quantity }}" step="1" min="0" 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>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="max-lg:max-w-lg max-lg:mx-auto">
|
||||
<p class="font-normal text-base leading-7 text-gray-500 text-center mb-5 mt-6">If CoverPage selected, make sure you put paper in the BypassTray</p>
|
||||
<button type="submit" class="rounded-full py-4 px-6 bg-indigo-600 text-white font-semibold text-lg w-full text-center transition-all duration-500 hover:bg-indigo-700">Print</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ template "footer.html" . }}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<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="https://tailwindui.com/plus/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company">
|
||||
<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>
|
||||
|
||||
|
||||
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" . }}
|
||||
@@ -1,24 +1,32 @@
|
||||
{{ template "header.html" . }}
|
||||
|
||||
<div class="bg-gray-100 dark:bg-gray-800 py-8">
|
||||
<form action="/cart" method="POST">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-col md:flex-row -mx-4">
|
||||
<div class="md:flex-1 px-4">
|
||||
<div class="h-[460px] rounded-lg bg-gray-300 dark:bg-gray-700 mb-4">
|
||||
<div class="rounded-lg bg-gray-300 dark:bg-gray-700 mb-4">
|
||||
<img class="w-full h-full object-cover" src="/{{ .data.shopItem.Image}}" alt="Product Image">
|
||||
</div>
|
||||
<div class="flex -mx-2 mb-4">
|
||||
<div class="w-1/3 px-2">
|
||||
<button class="w-full bg-gray-900 dark:bg-gray-600 text-white py-2 px-4 rounded-full font-bold hover:bg-gray-800 dark:hover:bg-gray-700">Add to Cart</button>
|
||||
</div>
|
||||
|
||||
{{ if .loggedIn }}
|
||||
<input type="hidden" id="{{ .data.shopItem.ID}}" name="ShopItemId" value="{{ .data.shopItem.ID }}">
|
||||
<div class="w-1/3 px-2">
|
||||
<a href="{{ .data.shopItem.ID }}/edit"><button class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-full font-bold hover:bg-gray-800 dark:hover:bg-gray-700">Edit</button></a>
|
||||
<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>
|
||||
{{ end }}
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{{ if .isAdmin }}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="w-1/3 px-2">
|
||||
<a href="{{ .data.shopItem.ID }}/delete"><button class="w-full bg-red-900 dark:bg-red-600 text-white py-2 px-4 rounded-full font-bold hover:bg-gray-800 dark:hover:bg-gray-700">Delete</button></a>
|
||||
<button type="button" class="w-full bg-red-900 dark:bg-red-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 }}/delete">Delete</a></button>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -28,21 +36,27 @@
|
||||
<p class="text-gray-600 dark:text-gray-300 text-sm mb-4">
|
||||
{{ .data.shopItem.Abstract }}
|
||||
</p>
|
||||
|
||||
{{ if .loggedIn }}
|
||||
<div class="flex mb-4">
|
||||
<div class="mr-4">
|
||||
<span class="font-bold text-gray-700 dark:text-gray-300">Price:</span>
|
||||
<span class="text-gray-600 dark:text-gray-300">{{ .data.shopItem.Price }}€</span>
|
||||
</div>
|
||||
<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">
|
||||
<option selected value="">Choose a variant</option>
|
||||
{{ range .data.shopItem.Variants }}
|
||||
<option value="{{ .ID }}">{{ .Name }} - {{ .Price }}€</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="flex mb-4">
|
||||
<div class="mr-4">
|
||||
<span class="font-bold text-gray-700 dark:text-gray-300">Tags:</span>
|
||||
<span class="text-gray-600 dark:text-gray-300">
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ range .data.shopItem.Tags }}
|
||||
{{ .Name }}
|
||||
<a href="/tags/{{ .ID }}"><span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-{{ .Color }}-900 dark:text-{{ .Color }}-300">{{ .Name }}</span></a>
|
||||
{{ end }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
@@ -64,28 +78,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<div class="bg-gray-200 rounded m-4 p-4">
|
||||
<div class="relative flex rounded-lg justify-center items-center">
|
||||
<img src="/{{ .data.shopItem.Image }}" alt="shopping image"
|
||||
class="object-cover items-center justify-center rounded">
|
||||
</div>
|
||||
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
||||
<i class="text-xs">{{ .data.shopItem.Abstract }}</i>
|
||||
<p class="text-xs">{{ .data.shopItem.Description }}</p>
|
||||
<p class="">Price: {{ .data.shopItem.Price }}</p>
|
||||
{{ 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">
|
||||
<a href="{{ .data.shopItem.ID }}/edit" 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">Edit</a>
|
||||
<a href="{{ .data.shopItem.ID }}/delete" 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">Delete</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
-->
|
||||
{{ template "footer.html" . }}
|
||||
|
||||
@@ -1,27 +1,61 @@
|
||||
<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 lg:px-8">
|
||||
<h2 class="text-2xl font-bold tracking-tight text-gray-900">Available Zines</h2>
|
||||
<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>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-8">
|
||||
<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">
|
||||
{{ range .data.shopItems }}
|
||||
|
||||
<div class="group relative">
|
||||
<img src="/{{ .Image }}" alt="Product Image" class="aspect-square w-full rounded-md bg-gray-200 object-cover group-hover:opacity-75 lg:aspect-auto lg:h-80">
|
||||
<div class="myClass group relative">
|
||||
<a href="/shopitems/{{ .ID }}">
|
||||
<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>
|
||||
<div class="mt-4 flex justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm text-gray-700">
|
||||
<a href="/shopitems/{{ .ID }}">
|
||||
<span aria-hidden="true" class="absolute inset-0"></span>
|
||||
{{ .Name }}
|
||||
</a>
|
||||
</h3>
|
||||
<!--<p class="mt-1 text-sm text-gray-500">{{ .Abstract }}</p>-->
|
||||
<p class="mt-1 text-sm text-gray-500">{{ .Abstract }}</p>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ range .Tags }}
|
||||
<a href="/tags/{{ .ID }}"><span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-{{ .Color }}-900 dark:text-{{ .Color }}-300">{{ .Name }}</span></a>
|
||||
{{ end }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ .Price }}€</p>
|
||||
{{ if $.loggedIn }}
|
||||
<p class="text-sm font-medium text-gray-900">{{ .BasePrice }}€</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
|
||||
<script>
|
||||
function myFunction() {
|
||||
// Declare variables
|
||||
var input, filter, ul, li, a, i, txtValue;
|
||||
input = document.getElementById('myInput');
|
||||
filter = input.value.toUpperCase();
|
||||
//ul = document.getElementById("myUL");
|
||||
//li = ul.getElementsByTagName('li');
|
||||
li = document.getElementsByClassName("myClass");
|
||||
|
||||
console.log(li)
|
||||
// Loop through all list items, and hide those who don't match the search query
|
||||
for (i = 0; i < li.length; i++) {
|
||||
a = li[i].getElementsByTagName("a")[1];
|
||||
txtValue = a.textContent || a.innerText;
|
||||
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||
li[i].style.display = "";
|
||||
} else {
|
||||
li[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<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/circlea.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 Tags</h2>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,36 @@
|
||||
<form action="/tags/{{ .ID }}" method="POST">
|
||||
<div class="max-w-md mx-auto mt-4">
|
||||
<div class="flex">
|
||||
|
||||
<span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm
|
||||
dark:bg-{{ .Color }}-900 dark:text-{{ .Color }}-300">Preview</span>
|
||||
<input type="text" id="name" name="name" value="{{ .Name }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
|
||||
<select name="color" required>
|
||||
<option selected value="{{ .Color }}">{{ .Color }}</option>
|
||||
<option value="1">Default</option>
|
||||
<option value="red">red</option>
|
||||
<option value="orange">orange</option>
|
||||
<option value="amber">amber</option>
|
||||
<option value="yellow">yellow</option>
|
||||
<option value="lime">lime</option>
|
||||
<option value="green">green</option>
|
||||
<option value="emerald">emerald</option>
|
||||
<option value="teal">teal</option>
|
||||
<option value="cyan">cyan</option>
|
||||
<option value="sky">sky</option>
|
||||
<option value="blue">blue</option>
|
||||
<option value="indigo">indigo</option>
|
||||
<option value="violet">violet</option>
|
||||
<option value="purple">purple</option>
|
||||
<option value="fuchsia">fuchsia</option>
|
||||
<option value="pink">pink</option>
|
||||
<option value="rose">rose</option>
|
||||
<option value="slate">slate</option>
|
||||
<option value="gray">gray</option>
|
||||
<option value="zinc">zinc</option>
|
||||
<option value="neutral">neutral</option>
|
||||
<option value="stone">stone</option>
|
||||
</select>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user