Implement order placed webhook

This commit is contained in:
Luca 2022-07-24 21:41:01 +02:00
parent 36d98f365a
commit 03e1e564f6
4 changed files with 149 additions and 19 deletions

View File

@ -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"`
}

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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()