Initial Commit :3

This commit is contained in:
nikurasu 2023-10-16 21:28:25 +02:00
commit ae014d1950
No known key found for this signature in database
26 changed files with 661 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
go.sum
.env
pretix-proxy
main.db

View file

@ -0,0 +1,11 @@
FROM golang:alpine3.17 AS build
WORKDIR /build
COPY ./src ./src
WORKDIR /build/src
RUN ls
RUN go get .
RUN go build -o /build/pretix-proxy
FROM alpine:3.17 AS final
COPY --from=build /build/pretix-proxy /bin/pretix-proxy
CMD pretix-proxy

View file

@ -0,0 +1,9 @@
version: '3'
services:
static-hoster:
images: dev.cat-enby.club/nikurasu/pretix-proxy:latest
environment:
- DOMAIN="reg.ulmer-furs.de"
- API_KEY=""
ports:
- 3000:3000

3
src/.env.template Normal file
View file

@ -0,0 +1,3 @@
DOMAIN="reg.ulmer-furs.de"
API_KEY=""
DEBUG="true"

View file

@ -0,0 +1,45 @@
package controller
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/gofiber/fiber/v2"
"ulmer-furs.de/pretix-proxy/v2/app/service"
"ulmer-furs.de/pretix-proxy/v2/app/util"
"ulmer-furs.de/pretix-proxy/v2/config"
"ulmer-furs.de/pretix-proxy/v2/entities"
)
// @Description Get the attendies of an event by the name
func GetAttendiesByEvent(c *fiber.Ctx) error {
name := c.Params("name")
event, err := service.Get_db_event_by_name(name)
if err != nil {
return c.Status(404).SendString(err.Error())
}
req, err := http.NewRequest("GET", fmt.Sprintf("https://%s/api/v1/organizers/%s/events/%s/orders", config.Env.Domain, event.Organizer, event.Event), nil)
if err != nil {
return c.Status(500).SendString("Internal Server Error")
}
req.Header.Add("Authorization", fmt.Sprintf("Token %s", config.Env.Domain))
resp, err := config.Client.Do(req)
if err != nil {
return c.Status(500).SendString("Internal Server Error")
}
respbody, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return c.Status(500).SendString("Internal Server Error")
}
var unmarshaled_resp entities.Pretix_Event
if err := json.Unmarshal(respbody, &unmarshaled_resp); err != nil {
return c.Status(500).SendString("Internal Server Error")
}
attendiesAtEvent := util.GetAttendiesFromPretixJson(unmarshaled_resp, event)
c.JSON(attendiesAtEvent)
return c.SendStatus(200)
}

View file

@ -0,0 +1,110 @@
package controller
import (
"strconv"
"github.com/gofiber/fiber/v2"
"ulmer-furs.de/pretix-proxy/v2/app/service"
"ulmer-furs.de/pretix-proxy/v2/app/util"
"ulmer-furs.de/pretix-proxy/v2/entities"
)
func ReturnEventsTableView(c *fiber.Ctx) error {
events, err := service.Get_all_db_event()
if err != nil {
return c.Status(500).Render("app/index/index", fiber.Map{
"Error": err.Error(),
})
}
return c.Render("app/views/index", fiber.Map{
"Title": "Ulmer Furs Events",
"Events": events,
"Count": len(events),
})
}
func ReturnAllEvents(c *fiber.Ctx) error {
events, err := service.Get_all_db_event()
if err != nil {
return c.Status(500).SendString("Internal Server Error")
}
c.JSON(events)
return c.SendStatus(200)
}
func ReturnEventById(c *fiber.Ctx) error {
id := c.Params("id")
id_int, err := strconv.Atoi(id)
if err != nil {
return c.Status(400).SendString("Bad Request")
}
event, err := service.Get_db_event_by_id(id_int)
if err != nil {
return c.Status(404).SendString(err.Error())
}
c.JSON(event)
return c.SendStatus(200)
}
func DeleteEventById(c *fiber.Ctx) error {
id := c.Params("id")
id_int, err := strconv.Atoi(id)
if err != nil {
return c.Status(400).SendString("Bad Request")
}
if _, err := service.Get_db_event_by_id(id_int); err != nil {
return &fiber.Error{
Code: fiber.ErrNotFound.Code,
Message: "Event not found",
}
}
if err := service.Delete_db_event_by_id(id_int); err != nil {
return c.Status(404).SendString(err.Error())
}
return c.SendStatus(200)
}
func CreateEvent(c *fiber.Ctx) error {
event := new(entities.Db_Event)
if err := c.BodyParser(event); err != nil {
return c.Status(fiber.ErrBadRequest.Code).SendString("Bad Request")
}
event.EnforceRequired = true
if err, errMsg := util.Validate(event); err {
return &fiber.Error{
Code: fiber.ErrBadRequest.Code,
Message: errMsg,
}
}
if err := service.Create_new_db_event(*event); err != nil {
return c.Status(fiber.ErrInternalServerError.Code).SendString(err.Error())
}
return c.SendStatus(fiber.StatusOK)
}
func UpdateEventById(c *fiber.Ctx) error {
event := new(entities.Db_Event)
id := c.Params("id")
id_int, parseErr := strconv.Atoi(id)
if err := c.BodyParser(event); err != nil || parseErr != nil {
return c.Status(400).SendString("Bad Request")
}
if err, errMsg := util.Validate(event); err {
return &fiber.Error{
Code: fiber.ErrBadRequest.Code,
Message: errMsg,
}
}
if _, err := service.Get_db_event_by_id(id_int); err != nil {
return &fiber.Error{
Code: fiber.ErrNotFound.Code,
Message: "Event not found",
}
}
event.ID = uint(id_int)
if err := service.Update_db_event(*event); err != nil {
return c.Status(500).SendString(err.Error())
}
return c.SendStatus(200)
}

View file

@ -0,0 +1,9 @@
package controller
import (
"github.com/gofiber/fiber/v2"
)
func Ping(c *fiber.Ctx) error {
return c.SendString("Pong")
}

View file

@ -0,0 +1,16 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"ulmer-furs.de/pretix-proxy/v2/app/controller"
)
func PrivateRoutes(app *fiber.App) {
apiv1 := app.Group("/api/v1")
//app.Get("/events", controller.ReturnEventsTableView)
apiv1.Get("/event", controller.ReturnAllEvents)
apiv1.Get("/event/:id", controller.ReturnEventById)
apiv1.Delete("/event/:id", controller.DeleteEventById)
apiv1.Put("/event/:id", controller.UpdateEventById)
apiv1.Put("/event", controller.CreateEvent)
}

View file

@ -0,0 +1,12 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"ulmer-furs.de/pretix-proxy/v2/app/controller"
)
func PublicRoutes(app *fiber.App) {
apiv1 := app.Group("/api/v1")
apiv1.Get("/ping", controller.Ping)
apiv1.Get("/attendies/:name", controller.GetAttendiesByEvent)
}

View file

@ -0,0 +1,61 @@
package service
import (
"errors"
"gorm.io/gorm"
"ulmer-furs.de/pretix-proxy/v2/config"
"ulmer-furs.de/pretix-proxy/v2/entities"
)
func Get_db_event_by_id(id int) (entities.Db_Event, error) {
var db_event entities.Db_Event
result := config.Database.Find(&db_event, id)
if result.RowsAffected == 0 {
return db_event, errors.New("event not found")
}
return db_event, nil
}
func Get_db_event_by_name(name string) (entities.Db_Event, error) {
var db_event entities.Db_Event
result := config.Database.First(&db_event, "name = ?", name)
if result.RowsAffected == 0 {
return db_event, errors.New("event not found")
}
return db_event, nil
}
func Get_all_db_event() ([]entities.Db_Event, error) {
var db_events []entities.Db_Event
result := config.Database.Find(&db_events)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return db_events, errors.New("events not found")
}
return db_events, nil
}
func Create_new_db_event(event entities.Db_Event) error {
result := config.Database.Create(&event)
if result.Error != nil {
return result.Error
}
return nil
}
func Update_db_event(event entities.Db_Event) error {
result := config.Database.Save(event)
if result.Error != nil {
return result.Error
}
return nil
}
func Delete_db_event_by_id(id int) error {
result := config.Database.Delete(&entities.Db_Event{}, id)
if result.Error != nil {
return result.Error
}
return nil
}

View file

@ -0,0 +1,46 @@
package util
import (
"golang.org/x/exp/slices"
"ulmer-furs.de/pretix-proxy/v2/entities"
)
func GetAttendiesFromPretixJson(pretixEvent entities.Pretix_Event, event entities.Db_Event) entities.Attendies {
var attendies entities.Attendies
var name string
for _, order := range pretixEvent.Results {
indexParticipation := slices.IndexFunc(order.Positions, func(p entities.Pretix_Position) bool { return p.Item == event.ItemIdParticipation })
indexBadge := slices.IndexFunc(order.Positions, func(p entities.Pretix_Position) bool { return p.Item == event.ItemIdBadge })
indexRestaurant := slices.IndexFunc(order.Positions, func(p entities.Pretix_Position) bool { return p.Item == event.ItemIdRestaurant })
// Get the Name of the Attendie. We take the Name from the Badge, if none Badge ordered == anonymous
if indexBadge != -1 {
indexName := slices.IndexFunc(order.Positions[indexBadge].Answers, func(a entities.Pretix_Answer) bool { return a.QuestionsIdentififer == event.QuestionIdName })
name = order.Positions[indexBadge].Answers[indexName].Answer
} else {
name = "Anonymous"
}
indexRole := slices.IndexFunc(order.Positions[indexParticipation].Answers, func(a entities.Pretix_Answer) bool { return a.QuestionsIdentififer == event.QuestionIdRole })
role := order.Positions[indexParticipation].Answers[indexRole].OptionIdentifiers
if slices.Contains(role, event.OptionIdSuiter) {
attendies.Suiter = append(attendies.Suiter, name)
} else if slices.Contains(role, event.OptionIdSpotter) {
attendies.Spotter = append(attendies.Spotter, name)
} else if slices.Contains(role, event.OptionIdPhotograph) {
attendies.Photographers = append(attendies.Photographers, name)
} else if slices.Contains(role, event.OptionIdGuest) {
attendies.Guests = append(attendies.Guests, name)
} else if slices.Contains(role, event.OptionIdSpecialAnimal) {
attendies.Suiter = append(attendies.Suiter, name)
attendies.Photographers = append(attendies.Photographers, name)
}
if indexRestaurant != -1 {
attendies.Restaurant = append(attendies.Restaurant, name)
}
}
return attendies
}

24
src/app/util/validate.go Normal file
View file

@ -0,0 +1,24 @@
package util
import (
"fmt"
"strings"
"ulmer-furs.de/pretix-proxy/v2/config"
)
func Validate(data interface{}) (bool, string) {
if errs := config.Validator.Validate(data); len(errs) > 0 && errs[0].Error {
errMsgs := make([]string, 0)
for _, err := range errs {
errMsgs = append(errMsgs, fmt.Sprintf(
"[%s]: '%v' | Needs to implement '%s'",
err.FailedField,
err.Value,
err.Tag,
))
return true, strings.Join(errMsgs, " and ")
}
}
return false, ""
}

11
src/app/views/error.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error: {{.Error}}</title>
</head>
<body>
<h1>{{.Error}}</h1>
</body>
</html>

17
src/app/views/index.html Normal file
View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
<h1>{{.Title}}</h1>
<p>Events count: <span id="count">{{.Count}}</span></p>
<script>
counter = parseInt(document.getElementById("count").innerHTML)
console.log(counter + 1)
</script>
{{template "table" .}}
</body>
</html>

36
src/app/views/table.html Normal file
View file

@ -0,0 +1,36 @@
{{define "table"}}
<table>
<tr>
<th>Name</th>
<th>Organizer</th>
<th>Event</th>
<th>ItemIdBadge</th>
<th>ItemIdRestaurant</th>
<th>ItemIdParticipation</th>
<th>QuestionIdRole</th>
<th>QuestionIdName</th>
<th>OptionIdSuiter</th>
<th>OptionIdGuest</th>
<th>OptionIdSpotter</th>
<th>OptionIdPhotograph</th>
<th>OptionIdSpecialAnimal</th>
</tr>
{{range .Events}}
<tr>
<td>{{.Name}}</td>
<td>{{.Organizer}}</td>
<td>{{.Event}}</td>
<td>{{.ItemIdBadge}}</td>
<td>{{.ItemIdRestaurant}}</td>
<td>{{.ItemIdParticipation}}</td>
<td>{{.QuestionIdRole}}</td>
<td>{{.QuestionIdName}}</td>
<td>{{.OptionIdSuiter}}</td>
<td>{{.OptionIdGuest}}</td>
<td>{{.OptionIdSpotter}}</td>
<td>{{.OptionIdPhotograph}}</td>
<td>{{.OptionIdSpecialAnimal}}</td>
</tr>
{{end}}
</table>
{{end}}

22
src/config/database.go Normal file
View file

@ -0,0 +1,22 @@
package config
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"ulmer-furs.de/pretix-proxy/v2/entities"
)
var Database *gorm.DB
func Connect() error {
var err error
Database, err = gorm.Open(sqlite.Open(Env.DatabasePath), &gorm.Config{})
if err != nil {
panic(err)
}
Database.AutoMigrate(&entities.Db_Event{})
return nil
}

22
src/config/environment.go Normal file
View file

@ -0,0 +1,22 @@
package config
import (
"log"
"github.com/caarlos0/env"
"github.com/joho/godotenv"
"ulmer-furs.de/pretix-proxy/v2/entities"
)
var Env entities.Environment
func LoadEnv() {
err := godotenv.Load()
if err != nil {
log.Fatalf("unable to load .env file: %e", err)
}
err = env.Parse(&Env)
if err != nil {
log.Fatalf("unable to parse ennvironment variables: %e", err)
}
}

18
src/config/fiber.go Normal file
View file

@ -0,0 +1,18 @@
package config
import (
"embed"
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
)
var App *fiber.App
func SetupFiber(viewFS embed.FS) {
engine := html.NewFileSystem(http.FS(viewFS), ".html")
App = fiber.New(fiber.Config{
Views: engine,
})
}

9
src/config/http.go Normal file
View file

@ -0,0 +1,9 @@
package config
import "net/http"
var Client *http.Client
func Create() {
Client = &http.Client{}
}

38
src/config/validator.go Normal file
View file

@ -0,0 +1,38 @@
package config
import "github.com/go-playground/validator/v10"
type XValidator struct {
validator *validator.Validate
}
type ErrorResponse struct {
Error bool
FailedField string
Tag string
Value interface{}
}
var Validator *XValidator
func SetupValidator() {
Validator = &XValidator{
validator: validator.New(),
}
}
func (v XValidator) Validate(data interface{}) []ErrorResponse {
validationErrors := []ErrorResponse{}
errs := Validator.validator.Struct(data)
if errs != nil {
for _, err := range errs.(validator.ValidationErrors) {
var elem ErrorResponse
elem.FailedField = err.Field()
elem.Tag = err.Tag()
elem.Value = err.Value()
elem.Error = true
validationErrors = append(validationErrors, elem)
}
}
return validationErrors
}

View file

@ -0,0 +1,9 @@
package entities
type Attendies struct {
Suiter []string
Spotter []string
Photographers []string
Guests []string
Restaurant []string
}

21
src/entities/db_event.go Normal file
View file

@ -0,0 +1,21 @@
package entities
import "gorm.io/gorm"
type Db_Event struct {
gorm.Model
Name string `gorm:"column:name;unique;not null" validate:"required_if=EnforceRequired true"`
Organizer string `gorm:"column:organizer;not null" validate:"required_if=EnforceRequired true"`
Event string `gorm:"column:event;not null" validate:"required_if=EnforceRequired true"`
ItemIdBadge int `gorm:"column:item_id_badge;not null" validate:"required_if=EnforceRequired true"`
ItemIdRestaurant int `gorm:"column:item_id_restaurant;not null" validate:"required_if=EnforceRequired true"`
ItemIdParticipation int `gorm:"column:item_id_participation;not null" validate:"required_if=EnforceRequired true"`
QuestionIdRole string `gorm:"column:question_id_role;not null" validate:"required_if=EnforceRequired true"`
QuestionIdName string `gorm:"column:question_id_name;not null" validate:"required_if=EnforceRequired true"`
OptionIdSuiter string `gorm:"column:option_id_suiter;not null" validate:"required_if=EnforceRequired true"`
OptionIdGuest string `gorm:"column:option_id_guest;not null" validate:"required_if=EnforceRequired true"`
OptionIdSpotter string `gorm:"column:option_id_spotter;not null" validate:"required_if=EnforceRequired true"`
OptionIdPhotograph string `gorm:"column:option_id_photograph;not null" validate:"required_if=EnforceRequired true"`
OptionIdSpecialAnimal string `gorm:"column:option_id_special_animal;not null" validate:"required_if=EnforceRequired true"`
EnforceRequired bool `gorm:"-"`
}

View file

@ -0,0 +1,8 @@
package entities
type Environment struct {
Domain string `env:"DOMAIN,required"`
ApiKey string `env:"API_KEY,required"`
DatabasePath string `env:"DATABASE_PATH,required"`
Debug bool `env:"DEBUG"`
}

22
src/entities/pretix.go Normal file
View file

@ -0,0 +1,22 @@
package entities
type Pretix_Event struct {
Count int `json:"count"`
Results []Pretix_Result `json:"results"`
}
type Pretix_Result struct {
Positions []Pretix_Position `json:"positions"`
}
type Pretix_Position struct {
Item int `json:"item"`
AttendieName string `json:"attendee_name"`
Answers []Pretix_Answer `json:"answers"`
}
type Pretix_Answer struct {
QuestionsIdentififer string `json:"question_identifier"`
Answer string `json:"answer"`
OptionIdentifiers []string `json:"option_identifiers"`
}

44
src/go.mod Normal file
View file

@ -0,0 +1,44 @@
module ulmer-furs.de/pretix-proxy/v2
go 1.19
require (
github.com/go-playground/validator/v10 v10.15.4
github.com/gofiber/fiber/v2 v2.48.0
github.com/joho/godotenv v1.5.1
gorm.io/driver/sqlite v1.5.3
gorm.io/gorm v1.25.4
)
require (
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/caarlos0/env v3.5.0+incompatible
github.com/gofiber/template v1.8.2 // indirect
github.com/gofiber/template/html/v2 v2.0.5
github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.48.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
golang.org/x/sys v0.11.0 // indirect
)

34
src/main.go Normal file
View file

@ -0,0 +1,34 @@
package main
import (
"embed"
"log"
"github.com/gofiber/fiber/v2/middleware/cors"
"ulmer-furs.de/pretix-proxy/v2/app/routes"
"ulmer-furs.de/pretix-proxy/v2/config"
)
//go:embed app/views/*
var viewFS embed.FS
func main() {
config.LoadEnv()
config.Connect()
config.SetupValidator()
config.SetupFiber(viewFS)
if config.Env.Debug {
config.App.Use(cors.New(cors.Config{
AllowHeaders: "Origin,Content-Type,Accept,Content-Length,Accept-Language,Accept-Encoding,Connection,Access-Control-Allow-Origin",
AllowOrigins: "*",
AllowCredentials: true,
AllowMethods: "GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS",
}))
}
routes.PublicRoutes(config.App)
routes.PrivateRoutes(config.App)
log.Fatal(config.App.Listen(":3000"))
}