From 6e5198831e960adb1196c643ee9e133a4d15ca8e Mon Sep 17 00:00:00 2001 From: Andre Medeiros Date: Wed, 23 Jun 2021 22:19:43 -0400 Subject: [PATCH] send notifications and worker stuff --- Procfile | 1 - cmd/apollo-api/main.go | 31 ++--- cmd/apollo-worker/main.go | 126 ++++++++++++++++-- go.mod | 2 + go.sum | 30 +++++ internal/reddit/client.go | 28 ++-- internal/reddit/request.go | 13 +- internal/reddit/types.go | 34 +++++ .../20210509191431_create_accounts.up.sql | 8 +- 9 files changed, 224 insertions(+), 49 deletions(-) create mode 100644 internal/reddit/types.go diff --git a/Procfile b/Procfile index f0efab7..2bc5498 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,3 @@ postgres: postgres -D tmp/postgresql -faktory: faktory server: go run github.com/andremedeiros/apollo/cmd/apollo-api worker: go run github.com/andremedeiros/apollo/cmd/apollo-worker diff --git a/cmd/apollo-api/main.go b/cmd/apollo-api/main.go index 81822c1..e80c747 100644 --- a/cmd/apollo-api/main.go +++ b/cmd/apollo-api/main.go @@ -8,12 +8,11 @@ import ( "net/http" "os" - "github.com/andremedeiros/apollo/internal/data" - "github.com/andremedeiros/apollo/internal/reddit" - - faktory "github.com/contribsys/faktory/client" "github.com/joho/godotenv" _ "github.com/lib/pq" + + "github.com/andremedeiros/apollo/internal/data" + "github.com/andremedeiros/apollo/internal/reddit" ) type config struct { @@ -21,12 +20,11 @@ type config struct { } type application struct { - cfg config - logger *log.Logger - db *sql.DB - faktory *faktory.Client - models *data.Models - client *reddit.Client + cfg config + logger *log.Logger + db *sql.DB + models *data.Models + client *reddit.Client } func main() { @@ -47,19 +45,12 @@ func main() { } defer db.Close() - faktory, err := faktory.Open() - if err != nil { - log.Fatal(err) - } - defer faktory.Close() - rc := reddit.NewClient(os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET")) app := &application{ cfg, logger, db, - faktory, data.NewModels(db), rc, } @@ -72,10 +63,4 @@ func main() { logger.Printf("starting server on %s", srv.Addr) err = srv.ListenAndServe() logger.Fatal(err) - - /* - rc := reddit.NewClient("C7MjYkx1czyRDA", "I2AsVWbrf8h4vdQxVa5Pvf84vScF1w") - rac := rc.NewAuthenticatedClient("2532458-kGp6OeR-LMoQNrUXRL-7UNfyBbViRA", "2532458-UE7IvJK3-VuTdMJB0bgMv58fxKQhww") - rac.MessageInbox() - */ } diff --git a/cmd/apollo-worker/main.go b/cmd/apollo-worker/main.go index 08f8a99..0408079 100644 --- a/cmd/apollo-worker/main.go +++ b/cmd/apollo-worker/main.go @@ -4,9 +4,16 @@ import ( "database/sql" "log" "os" + "os/signal" + "runtime" + "syscall" + "time" - worker "github.com/contribsys/faktory_worker_go" "github.com/joho/godotenv" + _ "github.com/lib/pq" + "github.com/sideshow/apns2" + "github.com/sideshow/apns2/payload" + "github.com/sideshow/apns2/token" "github.com/andremedeiros/apollo/internal/data" "github.com/andremedeiros/apollo/internal/reddit" @@ -19,6 +26,95 @@ type application struct { client *reddit.Client } +var workers int = runtime.NumCPU() * 2 + +func accountWorker(id int, rc *reddit.Client, db *sql.DB, logger *log.Logger, quit chan bool) { + authKey, err := token.AuthKeyFromFile("./tmp/authkey.p8") + token := &token.Token{ + AuthKey: authKey, + KeyID: "T88A7G9LZ8", + TeamID: "XG3L8T56DK", + } + + if err != nil { + log.Fatal("token error:", err) + } + + client := apns2.NewTokenClient(token) + + for { + select { + case <-quit: + return + default: + now := time.Now().UTC().Unix() + + tx, err := db.Begin() + + if err != nil { + log.Fatal(err) + continue + } + + query := ` + SELECT id, access_token, refresh_token, expires_at, last_message_id FROM accounts + WHERE last_checked_at <= $1 - 5 + ORDER BY last_checked_at + LIMIT 1 + FOR UPDATE SKIP LOCKED` + args := []interface{}{now} + + account := &data.Account{} + err = tx.QueryRow(query, args...).Scan(&account.ID, &account.AccessToken, &account.RefreshToken, &account.ExpiresAt, &account.LastMessageID) + + if account.ID == 0 { + time.Sleep(100 * time.Millisecond) + tx.Commit() + continue + } + + logger.Printf("Worker #%d, account %d", id, account.ID) + + _, err = tx.Exec(`UPDATE accounts SET last_checked_at = $1 WHERE id = $2`, now, account.ID) + + rac := rc.NewAuthenticatedClient(account.RefreshToken, account.AccessToken) + if account.ExpiresAt < now { + tokens, _ := rac.RefreshTokens() + tx.Exec(`UPDATE accounts SET access_token = $1, refresh_token = $2, expires_at = $3 WHERE id = $4`, + tokens.AccessToken, tokens.RefreshToken, now+3500, account.ID) + } + + msgs, err := rac.MessageInbox(account.LastMessageID) + if err != nil { + log.Fatal(err) + } + + if len(msgs.MessageListing.Messages) == 0 { + tx.Commit() + continue + } + + // Set latest message we alerted on + latestMsg := msgs.MessageListing.Messages[0] + + _, err = tx.Exec(`UPDATE accounts SET last_message_id = $1 WHERE id = $2`, latestMsg.FullName(), account.ID) + if err != nil { + log.Fatal(err) + } + + for _, msg := range msgs.MessageListing.Messages { + notification := &apns2.Notification{} + notification.DeviceToken = "9e1eb4c68d24a8f43eb92ed0a65f46aadbfbdbfe0a15ef4b5c34a9a4deb9ca49" + notification.Topic = "com.christianselig.Apollo" + notification.Payload = payload.NewPayload().AlertTitle(msg.Subject).AlertBody(msg.Body) + client.Push(notification) + } + + tx.Commit() + } + } +} + func main() { err := godotenv.Load() if err != nil { @@ -27,23 +123,31 @@ func main() { logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) + rc := reddit.NewClient(os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET")) + db, err := sql.Open("postgres", os.Getenv("DATABASE_URL")) if err != nil { log.Fatal(err) } defer db.Close() - rc := reddit.NewClient(os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET")) + db.SetMaxOpenConns(workers) - app := &application{ - logger, - db, - data.NewModels(db), - rc, + // This is a very conservative value -- seen as most of the work that is done in these jobs is + // + runtime.GOMAXPROCS(workers) + quitCh := make(chan bool, workers) + for i := 0; i < workers; i++ { + go accountWorker(i, rc, db, logger, quitCh) } - mgr := worker.NewManager() - mgr.ProcessStrictPriorityQueues("critical", "default", "bulk") - mgr.Concurrency = 20 - mgr.Run() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + <-sigs + + for i := 0; i < workers; i++ { + quitCh <- true + } + os.Exit(0) } diff --git a/go.mod b/go.mod index bb44903..fd34dab 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,10 @@ require ( github.com/contribsys/faktory v1.5.1 github.com/contribsys/faktory_worker_go v1.4.2 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/joho/godotenv v1.3.0 github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.1 + github.com/sideshow/apns2 v0.20.0 // indirect github.com/stretchr/testify v1.5.1 // indirect ) diff --git a/go.sum b/go.sum index bc314e3..aa0125f 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/benbjohnson/ego v0.4.0 h1:dJJoyR+HVi0CAyg6Eo/j3bLozdK91p/5WkAZ7ul99Uc= github.com/benbjohnson/ego v0.4.0/go.mod h1:6Qzj43pKX58WdlHDZKZS4Q/XXYwz6toIqXqNjzt4mNA= github.com/contribsys/faktory v1.3.0-1/go.mod h1:qCp3bcPrIT7mb/aR5KendsK/Nyg7yE3JOg4sETojQN8= github.com/contribsys/faktory v1.5.1 h1:Ac22RjbR8USVEwRV7jvz0+ytp2NquL5j7HC0IRM1K34= @@ -8,9 +10,14 @@ github.com/contribsys/faktory_worker_go v1.4.2/go.mod h1:v53dJh0lrk18B/3zFJjAWM6 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -18,45 +25,60 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/justinas/nosurf v1.1.0 h1:qqV6FJmnDBJ6F9pOzhZgZitAZWBYonMOXglof7TtdZw= github.com/justinas/nosurf v1.1.0/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo= github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= 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/sideshow/apns2 v0.20.0 h1:5Lzk4DUq+waVc6/BkKzpDTpQjtk/BZOP0YsayBpY1NE= +github.com/sideshow/apns2 v0.20.0/go.mod h1:f7dArLPLbiZ3qPdzzrZXdCSlMp8FD0p6z7tHssDOLvk= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -66,24 +88,32 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/reddit/client.go b/internal/reddit/client.go index 25577cc..e3db5f2 100644 --- a/internal/reddit/client.go +++ b/internal/reddit/client.go @@ -2,7 +2,6 @@ package reddit import ( "encoding/json" - "fmt" "io/ioutil" "net/http" "strings" @@ -51,7 +50,7 @@ func (rac *AuthenticatedClient) request(r *Request) ([]byte, error) { return ioutil.ReadAll(resp.Body) } -func (rac *AuthenticatedClient) refreshTokens() (string, string, error) { +func (rac *AuthenticatedClient) RefreshTokens() (*RefreshTokenResponse, error) { req := NewRequest( WithMethod("POST"), WithURL(tokenURL), @@ -61,20 +60,33 @@ func (rac *AuthenticatedClient) refreshTokens() (string, string, error) { ) body, err := rac.request(req) - fmt.Println(string(body)) - return "", "", err + + if err != nil { + return nil, err + } + + rtr := &RefreshTokenResponse{} + json.Unmarshal([]byte(body), rtr) + return rtr, nil } -func (rac *AuthenticatedClient) MessageInbox() error { +func (rac *AuthenticatedClient) MessageInbox(from string) (*MessageListingResponse, error) { req := NewRequest( WithMethod("GET"), WithToken(rac.accessToken), WithURL("https://oauth.reddit.com/message/inbox.json"), + WithQuery("before", from), ) - body, _ := rac.request(req) - fmt.Println(string(body)) - return nil + body, err := rac.request(req) + + if err != nil { + return nil, err + } + + mlr := &MessageListingResponse{} + json.Unmarshal([]byte(body), mlr) + return mlr, nil } type MeResponse struct { diff --git a/internal/reddit/request.go b/internal/reddit/request.go index 8c03b0e..5ed7ca1 100644 --- a/internal/reddit/request.go +++ b/internal/reddit/request.go @@ -8,10 +8,11 @@ import ( "strings" ) -const userAgent = "server:test-api:v0.0.1 (by /u/changelog)" +const userAgent = "server:test-api:v0.0.2 (by /u/changelog)" type Request struct { body url.Values + query url.Values method string token string url string @@ -21,7 +22,7 @@ type Request struct { type RequestOption func(*Request) func NewRequest(opts ...RequestOption) *Request { - req := &Request{url.Values{}, "GET", "", "", ""} + req := &Request{url.Values{}, url.Values{}, "GET", "", "", ""} for _, opt := range opts { opt(req) } @@ -31,6 +32,8 @@ func NewRequest(opts ...RequestOption) *Request { func (r *Request) HTTPRequest() (*http.Request, error) { req, err := http.NewRequest(r.method, r.url, strings.NewReader(r.body.Encode())) + req.URL.RawQuery = r.query.Encode() + req.Header.Add("User-Agent", userAgent) if r.token != "" { @@ -74,3 +77,9 @@ func WithBody(key, val string) RequestOption { req.body.Set(key, val) } } + +func WithQuery(key, val string) RequestOption { + return func(req *Request) { + req.query.Set(key, val) + } +} diff --git a/internal/reddit/types.go b/internal/reddit/types.go new file mode 100644 index 0000000..7165ff7 --- /dev/null +++ b/internal/reddit/types.go @@ -0,0 +1,34 @@ +package reddit + +import "fmt" + +type Message struct { + ID string `json:"id"` + Kind string `json:"kind"` + Author string `json:"author"` + Subject string `json:"subject"` + Body string `json:"body"` + CreatedAt float64 `json:"created_utc"` +} + +type MessageData struct { + Message `json:"data"` + Kind string `json:"kind"` +} + +func (md MessageData) FullName() string { + return fmt.Sprintf("%s_%s", md.Kind, md.ID) +} + +type MessageListing struct { + Messages []MessageData `json:"children"` +} + +type MessageListingResponse struct { + MessageListing MessageListing `json:"data"` +} + +type RefreshTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` +} diff --git a/migrations/20210509191431_create_accounts.up.sql b/migrations/20210509191431_create_accounts.up.sql index 42c6ada..795757a 100644 --- a/migrations/20210509191431_create_accounts.up.sql +++ b/migrations/20210509191431_create_accounts.up.sql @@ -1,10 +1,10 @@ CREATE TABLE IF NOT EXISTS accounts ( id SERIAL PRIMARY KEY, - username character(20), - access_token character(64), - refresh_token character(64), + username character varying(20), + access_token character varying(64), + refresh_token character varying(64), expires_at integer, - last_message_id character(32), + last_message_id character varying(32), device_count integer, last_checked_at integer );