From c143311608154eb75d598858759fce94dcf29bf5 Mon Sep 17 00:00:00 2001 From: Vicente Ferrari Smith Date: Sun, 10 May 2026 16:42:37 +0200 Subject: [PATCH] . --- Makefile | 5 +- cmd/party/api/device_tokens.go | 18 +++++ cmd/party/main.go | 4 ++ cmd/party/send_test_notification.go | 100 ++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 cmd/party/send_test_notification.go diff --git a/Makefile b/Makefile index 2a5f4b5..d8ff1b8 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ confirm: current_time = $(shell date -Iseconds) git_description = $(shell git describe --always --dirty) +git_commit_count = $(shell git rev-list --count HEAD) linker_flags = '-s -X main.buildTime=${current_time} -X main.version=${git_description}' production_host = 152.53.236.243 @@ -46,8 +47,8 @@ build/package: cp ./deploy/DEBIAN/prerm /tmp/party-pkg/DEBIAN/prerm cp ./deploy/DEBIAN/postrm /tmp/party-pkg/DEBIAN/postrm chmod 755 /tmp/party-pkg/DEBIAN/postinst /tmp/party-pkg/DEBIAN/prerm /tmp/party-pkg/DEBIAN/postrm - printf 'Package: party\nVersion: 0+%s\nArchitecture: amd64\nMaintainer: Vicente Ferrari Smith \nDescription: Party\n' \ - '${git_description}' > /tmp/party-pkg/DEBIAN/control + printf 'Package: party\nVersion: 0+%s.%s\nArchitecture: amd64\nMaintainer: Vicente Ferrari Smith \nDescription: Party\n' \ + '${git_commit_count}' '${git_description}' > /tmp/party-pkg/DEBIAN/control dpkg-deb --build --root-owner-group /tmp/party-pkg ./party.deb @echo 'Package ready: ./party.deb' diff --git a/cmd/party/api/device_tokens.go b/cmd/party/api/device_tokens.go index 617a3a0..0eabf1d 100644 --- a/cmd/party/api/device_tokens.go +++ b/cmd/party/api/device_tokens.go @@ -2,29 +2,47 @@ package api import ( "errors" + "fmt" "net/http" ) func (api *Api) RegisterDeviceToken(w http.ResponseWriter, r *http.Request) { user := GetUser(r) + api.App.Logger.PrintInfo("register device token: request", map[string]string{ + "user_id": fmt.Sprint(user.ID), + }) + var input struct { Token string `json:"token"` } if err := api.readJSON(w, r, &input); err != nil { + api.App.Logger.PrintInfo("register device token: bad request", map[string]string{ + "user_id": fmt.Sprint(user.ID), + "error": err.Error(), + }) api.BadRequestResponse(w, r, err) return } if input.Token == "" { + api.App.Logger.PrintInfo("register device token: empty token", map[string]string{ + "user_id": fmt.Sprint(user.ID), + }) api.BadRequestResponse(w, r, errors.New("token must be provided")) return } if err := api.App.Models.DeviceTokens.Insert(user.ID, input.Token); err != nil { + api.App.Logger.PrintError(err, map[string]string{ + "user_id": fmt.Sprint(user.ID), + }) api.ServerErrorResponse(w, r, err) return } + api.App.Logger.PrintInfo("register device token: ok", map[string]string{ + "user_id": fmt.Sprint(user.ID), + }) w.WriteHeader(http.StatusNoContent) } diff --git a/cmd/party/main.go b/cmd/party/main.go index 739461f..04efbbd 100644 --- a/cmd/party/main.go +++ b/cmd/party/main.go @@ -45,6 +45,10 @@ func main() { runCreateAdmin(os.Args[2:]) return } + if len(os.Args) > 1 && os.Args[1] == "send-test-notification" { + runSendTestNotification(os.Args[2:]) + return + } var cfg common.Config diff --git a/cmd/party/send_test_notification.go b/cmd/party/send_test_notification.go new file mode 100644 index 0000000..e181c74 --- /dev/null +++ b/cmd/party/send_test_notification.go @@ -0,0 +1,100 @@ +package main + +import ( + "flag" + "fmt" + "os" + + apns "github.com/sideshow/apns2" + "github.com/sideshow/apns2/token" + "party.at/party/cmd/party/common" + "party.at/party/internal/data" +) + +func runSendTestNotification(args []string) { + fs := flag.NewFlagSet("send-test-notification", flag.ExitOnError) + email := fs.String("email", "", "Send to all device tokens for this user") + deviceToken := fs.String("token", "", "Send to this specific device token") + message := fs.String("message", "Test notification", "Notification body") + fs.Parse(args) + + if *email == "" && *deviceToken == "" { + fmt.Fprintln(os.Stderr, "usage: party send-test-notification [-email EMAIL | -token TOKEN] [-message TEXT]") + os.Exit(1) + } + + keyPath := os.Getenv("APNS_KEY_PATH") + keyID := os.Getenv("APNS_KEY_ID") + teamID := os.Getenv("APNS_TEAM_ID") + bundleID := os.Getenv("APNS_BUNDLE_ID") + + if keyPath == "" || keyID == "" || teamID == "" || bundleID == "" { + fmt.Fprintln(os.Stderr, "missing APNS_KEY_PATH, APNS_KEY_ID, APNS_TEAM_ID or APNS_BUNDLE_ID") + os.Exit(1) + } + + authKey, err := token.AuthKeyFromFile(keyPath) + if err != nil { + fmt.Fprintf(os.Stderr, "apns key: %v\n", err) + os.Exit(1) + } + client := apns.NewTokenClient(&token.Token{ + AuthKey: authKey, + KeyID: keyID, + TeamID: teamID, + }).Production() + + var targets []string + + if *deviceToken != "" { + targets = []string{*deviceToken} + } else { + var cfg common.Config + cfg.DB.DSN = os.Getenv("PARTY_DB_DSN") + cfg.DB.MaxOpenConns = 5 + cfg.DB.MaxIdleConns = 5 + cfg.DB.MaxIdleTime = "15m" + + db, err := openDB(cfg) + if err != nil { + fmt.Fprintf(os.Stderr, "db: %v\n", err) + os.Exit(1) + } + defer db.Close() + + models := data.NewModels(db) + user, err := models.Users.GetByEmail(*email) + if err != nil { + fmt.Fprintf(os.Stderr, "user: %v\n", err) + os.Exit(1) + } + deviceTokens, err := models.DeviceTokens.GetForUser(user.ID) + if err != nil { + fmt.Fprintf(os.Stderr, "device tokens: %v\n", err) + os.Exit(1) + } + if len(deviceTokens) == 0 { + fmt.Fprintln(os.Stderr, "no device tokens registered for this user") + os.Exit(1) + } + for _, dt := range deviceTokens { + targets = append(targets, dt.Token) + } + } + + payload := fmt.Sprintf(`{"aps":{"alert":%q,"sound":"default"}}`, *message) + + for _, t := range targets { + notification := &apns.Notification{ + DeviceToken: t, + Topic: bundleID, + Payload: []byte(payload), + } + resp, err := client.Push(notification) + if err != nil { + fmt.Fprintf(os.Stderr, "push failed for %s: %v\n", t, err) + continue + } + fmt.Printf("token=%s status=%d apns_id=%s\n", t, resp.StatusCode, resp.ApnsID) + } +}