From 03e1e564f686887b2d88002e0b27b633644f0c41 Mon Sep 17 00:00:00 2001 From: Luca Date: Sun, 24 Jul 2022 21:41:01 +0200 Subject: [PATCH] Implement order placed webhook --- internal/config/config.go | 2 + internal/pretix/order_placed.go | 98 +++++++++++++++++++++++++++++++++ internal/pretix/server.go | 64 +++++++++++++++------ main.go | 4 +- 4 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 internal/pretix/order_placed.go diff --git a/internal/config/config.go b/internal/config/config.go index 400dc32..6150b59 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,6 +26,8 @@ type MatrixConfig struct { } type ServerConfig struct { + APIToken string `json:"api_token"` + BaseURL string `json:"base_url"` ListenAddress string `json:"listen_address"` } diff --git a/internal/pretix/order_placed.go b/internal/pretix/order_placed.go new file mode 100644 index 0000000..cf13a3c --- /dev/null +++ b/internal/pretix/order_placed.go @@ -0,0 +1,98 @@ +package pretix + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "time" +) + +const ( + eventOrderPlaced = "pretix.event.order.placed" + + urlIndividualOrder = "%s/api/v1/organizers/%s/events/%s/orders/%s/" +) + +func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Content-Type") != "application/json" { + writeStatus(w, http.StatusBadRequest) + return + } + + data := struct{ + Action string + Code string + Event string + Organizer string + }{} + decoder := json.NewDecoder(r.Body) + + err := decoder.Decode(&data) + if err != nil { + writeStatus(w, http.StatusBadRequest) + return + } + + if data.Action != eventOrderPlaced { + writeStatus(w, http.StatusBadRequest) + return + } + + req, err := http.NewRequest(http.MethodGet, s.buildURL(urlIndividualOrder, data.Organizer, data.Event, data.Code), nil) + if err != nil { + writeStatus(w, http.StatusInternalServerError) + return + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", fmt.Sprint("Token ", s.config.APIToken)) + + resp, err := s.client.Do(req) + if err != nil { + writeStatus(w, http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + writeStatus(w, http.StatusInternalServerError) + return + } + + if resp.Header.Get("Content-Type") != "application/json" { + writeStatus(w, http.StatusInternalServerError) + return + } + + decoder = json.NewDecoder(resp.Body) + order := struct{ + Code string + Datetime time.Time + Positions []struct{ + Price string + } + }{} + + err = decoder.Decode(&order) + if err != nil { + writeStatus(w, http.StatusInternalServerError) + return + } + + result, err := s.db.Exec("INSERT INTO orders VALUES(?);", order.Code) + if err != nil { + writeStatus(w, http.StatusInternalServerError) + return + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + writeStatus(w, http.StatusInternalServerError) + return + } + + if rowsAffected > 0 && order.Datetime.Add(72 * time.Hour).After(time.Now()) { // pretix retries failed webhooks for up to three days + log.Print(order) + } +} diff --git a/internal/pretix/server.go b/internal/pretix/server.go index 599b8ce..1e257eb 100644 --- a/internal/pretix/server.go +++ b/internal/pretix/server.go @@ -2,10 +2,12 @@ package pretix import ( "context" - "encoding/json" + "database/sql" + "fmt" "io" "log" "net/http" + "strings" "sync" "git.luj0ga.de/franconian/matrix-pretix/internal/config" @@ -14,21 +16,30 @@ import ( type Server struct { client http.Client + config *config.ServerConfig + db *sql.DB matrix *matrix.Client } -func NewServer(matrix *matrix.Client) *Server { +func NewServer(config *config.ServerConfig, db *sql.DB, matrix *matrix.Client) *Server { return &Server{ + config: config, + db: db, matrix: matrix, } } -func (s Server) ListenAndServe(config *config.ServerConfig, ctx context.Context, wg *sync.WaitGroup) error { +func (s Server) ListenAndServe(ctx context.Context, wg *sync.WaitGroup) error { + err := s.createTables() + if err != nil { + return err + } + http.HandleFunc("/", http.NotFound) http.HandleFunc("/order_placed", s.orderPlaced) server := http.Server{ - Addr: config.ListenAddress, + Addr: s.config.ListenAddress, } go func() { @@ -43,9 +54,9 @@ func (s Server) ListenAndServe(config *config.ServerConfig, ctx context.Context, } }() - log.Print("listening on ", config.ListenAddress) + log.Print("listening on ", s.config.ListenAddress) - err := server.ListenAndServe() + err = server.ListenAndServe() if err != http.ErrServerClosed { return err } @@ -53,22 +64,41 @@ func (s Server) ListenAndServe(config *config.ServerConfig, ctx context.Context, return nil } -func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Content-Type") != "application/json" { - writeStatus(w, http.StatusBadRequest) - return +func (s Server) createTables() error { + tables := []string{ + `CREATE TABLE IF NOT EXISTS orders ( + code TEXT PRIMARY KEY ON CONFLICT IGNORE + ); + `, } - decoder := json.NewDecoder(r.Body) - - var data map[string]interface{} - err := decoder.Decode(&data) + tx, err := s.db.Begin() if err != nil { - writeStatus(w, http.StatusBadRequest) - return + return err } - log.Print(data) + for _, table := range tables { + _, err := tx.Exec(table) + if err != nil { + if err := tx.Rollback(); err != nil { + log.Print(err) + } + + return err + } + } + + err = tx.Commit() + if err != nil { + return err + } + + return nil +} + +func (s Server) buildURL(endpoint string, args ...any) string { + baseURL := strings.TrimSuffix(s.config.BaseURL, "/") + return fmt.Sprintf(endpoint, append([]any{baseURL}, args...)...) } func writeStatus(w http.ResponseWriter, code int) { diff --git a/main.go b/main.go index b6308b8..01a1b43 100644 --- a/main.go +++ b/main.go @@ -60,9 +60,9 @@ func main() { go client.Sync(ctx, &wg) - server := pretix.NewServer(client) + server := pretix.NewServer(&config.Server, db, client) - err = server.ListenAndServe(&config.Server, ctx, &wg) + err = server.ListenAndServe(ctx, &wg) if err != nil { stop() wg.Wait()