From f9b9c595cfeca0c4730013f285ef9601d1a4a732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Medeiros?= Date: Sat, 7 May 2022 12:37:21 -0400 Subject: [PATCH] Better testing (#62) * some tests * more tests * tidy up go.mod * more tests * add postgres * beep * again * Set up schema * fix device test --- .env.test | 4 + .github/workflows/test.yml | 22 ++ .golangci.yml | 28 +++ Brewfile | 8 - Brewfile.lock.json | 218 ------------------ Makefile | 19 ++ Procfile | 3 - env.example | 4 - go.mod | 36 ++- go.sum | 18 -- internal/api/accounts.go | 14 +- internal/api/contact.go | 3 +- internal/api/errors.go | 2 +- internal/api/watcher.go | 4 +- internal/cmd/scheduler.go | 12 +- internal/cmd/worker.go | 2 +- internal/domain/subreddit_test.go | 18 +- internal/itunes/receipt.go | 7 +- internal/reddit/client.go | 67 +++--- internal/reddit/client_test.go | 45 ++-- internal/reddit/request.go | 13 +- internal/reddit/types_test.go | 67 ++++-- internal/repository/connection.go | 14 ++ internal/repository/postgres_account.go | 26 +-- internal/repository/postgres_device.go | 24 +- internal/repository/postgres_device_test.go | 135 +++++++++++ internal/repository/postgres_subreddit.go | 11 +- internal/repository/postgres_user.go | 14 +- internal/repository/postgres_watcher.go | 20 +- internal/repository/postgres_watcher_test.go | 33 +++ internal/testhelper/pgxpool.go | 34 +++ internal/worker/notifications.go | 37 +-- internal/worker/stuck_notifications.go | 17 +- internal/worker/subreddits.go | 25 +- internal/worker/trending.go | 21 +- internal/worker/users.go | 25 +- internal/worker/worker.go | 4 +- ...wn.sql => 000001_create_accounts.down.sql} | 0 migrations/000001_create_accounts.up.sql | 20 ++ migrations/000002_create_devices.down.sql | 0 migrations/000002_create_devices.up.sql | 10 + .../000003_create_devices_accounts.down.sql | 0 .../000003_create_devices_accounts.up.sql | 15 ++ migrations/000004_create_subreddits.down.sql | 0 migrations/000004_create_subreddits.up.sql | 9 + migrations/000005_create_users.down.sql | 0 migrations/000005_create_users.up.sql | 9 + migrations/000006_create_watchers.down.sql | 0 migrations/000006_create_watchers.up.sql | 24 ++ .../20210509150615_create_devices.down.sql | 1 - .../20210509150615_create_devices.up.sql | 8 - .../20210509191431_create_accounts.down.sql | 1 - .../20210509191431_create_accounts.up.sql | 13 -- ...10509200230_create_devices_accounts.up.sql | 7 - script/bootstrap | 32 --- script/migrate | 10 - script/server | 11 - 57 files changed, 674 insertions(+), 550 deletions(-) create mode 100644 .env.test create mode 100644 .golangci.yml delete mode 100644 Brewfile delete mode 100644 Brewfile.lock.json create mode 100644 Makefile delete mode 100644 Procfile delete mode 100644 env.example create mode 100644 internal/repository/connection.go create mode 100644 internal/repository/postgres_device_test.go create mode 100644 internal/repository/postgres_watcher_test.go create mode 100644 internal/testhelper/pgxpool.go rename migrations/{20210509200230_create_devices_accounts.down.sql => 000001_create_accounts.down.sql} (100%) create mode 100644 migrations/000001_create_accounts.up.sql create mode 100644 migrations/000002_create_devices.down.sql create mode 100644 migrations/000002_create_devices.up.sql create mode 100644 migrations/000003_create_devices_accounts.down.sql create mode 100644 migrations/000003_create_devices_accounts.up.sql create mode 100644 migrations/000004_create_subreddits.down.sql create mode 100644 migrations/000004_create_subreddits.up.sql create mode 100644 migrations/000005_create_users.down.sql create mode 100644 migrations/000005_create_users.up.sql create mode 100644 migrations/000006_create_watchers.down.sql create mode 100644 migrations/000006_create_watchers.up.sql delete mode 100644 migrations/20210509150615_create_devices.down.sql delete mode 100644 migrations/20210509150615_create_devices.up.sql delete mode 100644 migrations/20210509191431_create_accounts.down.sql delete mode 100644 migrations/20210509191431_create_accounts.up.sql delete mode 100644 migrations/20210509200230_create_devices_accounts.up.sql delete mode 100755 script/bootstrap delete mode 100755 script/migrate delete mode 100755 script/server diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..d533693 --- /dev/null +++ b/.env.test @@ -0,0 +1,4 @@ +DATABASE_URL=postgres://andremedeiros@localhost/apollo_test?sslmode=disable +DATABASE_CONNECTION_POOL_URL=postgres://andremedeiros@localhost/apollo_test?sslmode=disable +REDIS_URL=redis://127.0.0.1:6379 +STATSD_URL=127.0.0.1:8125 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4588d1..f033335 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,13 +8,35 @@ jobs: go-version: [1.18] platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} + env: + DATABASE_URL: postgres://postgres:postgres@localhost/apollo_test + services: + postgres: + image: postgres + env: + POSTGRES_DB: apollo_test + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: - uses: actions/checkout@v2 + - name: Setup Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} + - name: Lint uses: golangci/golangci-lint-action@v2 + + - name: Setup database schema + run: psql -f docs/schema.sql $DATABASE_URL + - name: Test run: go test ./... -v -race -timeout 5s diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..df7f259 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,28 @@ +linters: + enable: + - bodyclose # checks whether HTTP response body is closed successfully + - errcheck # checks for unchecked errors in go programs + - errname # checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error` + - exportloopref # checks for pointers to enclosing loop variables + - gochecknoinits # checks that no init functions are present in Go code + - ifshort # checks that your code uses short syntax for if-statements whenever possible + - importas # enforces consistent import aliases + - ineffassign # detects when assignments to existing variables are not used + - noctx # finds sending http request without context.Context + - paralleltest # detects missing usage of t.Parallel() method in go tests + - prealloc # finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - tenv # detects using os.Setenv instead of t.Setenv + - testpackage # makes you use a separate _test package + - thelper # detects golang test helpers without t.Helper() call and checks consistency of test helpers + - unconvert # removes unnecessary type conversions + - unparam # removes unused function parameters + fast: true + +issues: + exclude-rules: + # False positive: https://github.com/kunwardeep/paralleltest/issues/8. + - linters: + - paralleltest + text: "does not use range value in test Run" diff --git a/Brewfile b/Brewfile deleted file mode 100644 index 27e0883..0000000 --- a/Brewfile +++ /dev/null @@ -1,8 +0,0 @@ -tap 'heroku/brew' - -brew 'heroku' -brew 'foreman' -brew 'golang' -brew 'golang-migrate' -brew 'postgres' -brew 'pgbouncer' diff --git a/Brewfile.lock.json b/Brewfile.lock.json deleted file mode 100644 index fa19385..0000000 --- a/Brewfile.lock.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "entries": { - "brew": { - "postgres": { - "version": "13.3", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_big_sur": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:7c0e1b76d60b428facd521c729323221712d7f6d9954e21da389aeeb2c62348e", - "sha256": "7c0e1b76d60b428facd521c729323221712d7f6d9954e21da389aeeb2c62348e" - }, - "big_sur": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:eaf28965ead970ecfb327b121ec6a07f0a4e39865797a1a0383605a17e5911e3", - "sha256": "eaf28965ead970ecfb327b121ec6a07f0a4e39865797a1a0383605a17e5911e3" - }, - "catalina": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:74e946503c73cd0efc55ad4b373efbd8f4fb8a9e26a670b878c6db25794aea4a", - "sha256": "74e946503c73cd0efc55ad4b373efbd8f4fb8a9e26a670b878c6db25794aea4a" - }, - "mojave": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:36c7bde4788571e5b66ffe05b6174b62c69781d61c53c3ebcd9d278e8f148197", - "sha256": "36c7bde4788571e5b66ffe05b6174b62c69781d61c53c3ebcd9d278e8f148197" - } - } - } - }, - "golang": { - "version": "1.16.5", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_big_sur": { - "cellar": "/opt/homebrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/go/blobs/sha256:dde21eedfa67da23db70cf977ae82c0cadd5acf2a326cb91853ff54d0cf5886f", - "sha256": "dde21eedfa67da23db70cf977ae82c0cadd5acf2a326cb91853ff54d0cf5886f" - }, - "big_sur": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/go/blobs/sha256:416c5e2b7247c78482a5465f79d83c0240ee0c9098c8c7429f9c7af073402cc9", - "sha256": "416c5e2b7247c78482a5465f79d83c0240ee0c9098c8c7429f9c7af073402cc9" - }, - "catalina": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/go/blobs/sha256:8a7564fab7f715feed7506e3cc30f20295fd62914418fb636a5a4c4ca1fc7398", - "sha256": "8a7564fab7f715feed7506e3cc30f20295fd62914418fb636a5a4c4ca1fc7398" - }, - "mojave": { - "cellar": "/usr/local/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/go/blobs/sha256:a232e1f840525ab1e9411ba4edaa74c2bb73705e8e6feb7506649a7d608f9292", - "sha256": "a232e1f840525ab1e9411ba4edaa74c2bb73705e8e6feb7506649a7d608f9292" - }, - "x86_64_linux": { - "cellar": "/home/linuxbrew/.linuxbrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/go/blobs/sha256:1434dfa5cbe0fd0edb34eab477e156640f3f07599d33105958fe18b329bcfb7d", - "sha256": "1434dfa5cbe0fd0edb34eab477e156640f3f07599d33105958fe18b329bcfb7d" - } - } - } - }, - "foreman": { - "version": "0.87.2", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/foreman/blobs/sha256:575f9fbc16eca16cf479196ce44d87bb817ddb1e2eed59869ffe158d98d08a9f", - "sha256": "575f9fbc16eca16cf479196ce44d87bb817ddb1e2eed59869ffe158d98d08a9f" - }, - "big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/foreman/blobs/sha256:70c762dd642d8f5aa3ca5a28e420b6c9f7befaf7699de073b7d62e174fdee88f", - "sha256": "70c762dd642d8f5aa3ca5a28e420b6c9f7befaf7699de073b7d62e174fdee88f" - }, - "catalina": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/foreman/blobs/sha256:5c2b39c1f7e9667b9ebc6b7228b6cf31f06c2261c85019028272cfdda7073ea5", - "sha256": "5c2b39c1f7e9667b9ebc6b7228b6cf31f06c2261c85019028272cfdda7073ea5" - }, - "mojave": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/foreman/blobs/sha256:674b5fc005986f47294acedccba6b2a2bcdc1d423e392a356f8d58cc88a2c81a", - "sha256": "674b5fc005986f47294acedccba6b2a2bcdc1d423e392a356f8d58cc88a2c81a" - }, - "high_sierra": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/foreman/blobs/sha256:b0d289ff31caf33f3d549af6dd615e37588aadb243355395380c4df5b0e52d63", - "sha256": "b0d289ff31caf33f3d549af6dd615e37588aadb243355395380c4df5b0e52d63" - } - } - } - }, - "faktory": { - "version": "1.5.1-1", - "bottle": false - }, - "redis": { - "version": "6.2.3", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_big_sur": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:b2b3cfeca2d5f110507e9e7a7af8918786f2853e39e49b0b39de68762e5b5030", - "sha256": "b2b3cfeca2d5f110507e9e7a7af8918786f2853e39e49b0b39de68762e5b5030" - }, - "big_sur": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:d891c5b376746c3895098fd384fb4edba972b532848f63cbad5be20e611458ac", - "sha256": "d891c5b376746c3895098fd384fb4edba972b532848f63cbad5be20e611458ac" - }, - "catalina": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:a269e87b26515775a7034d9d6cb996ed63d783b5a6d681b64bab92ce93bed55b", - "sha256": "a269e87b26515775a7034d9d6cb996ed63d783b5a6d681b64bab92ce93bed55b" - }, - "mojave": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:3373d834552eef5f6c71889299124693de6b5d5b887e520d6db96ab51da81020", - "sha256": "3373d834552eef5f6c71889299124693de6b5d5b887e520d6db96ab51da81020" - } - } - } - }, - "golang-migrate": { - "version": "4.14.1", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/golang-migrate/blobs/sha256:3565f7a03cfd1eeec3110aa8d56f03baa79b0de2718103c0095e51187ecd37ee", - "sha256": "3565f7a03cfd1eeec3110aa8d56f03baa79b0de2718103c0095e51187ecd37ee" - }, - "big_sur": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/golang-migrate/blobs/sha256:5c61a106d9970b0f9b14e78e1523894d57b50cd0473f7d5a1fb1a9161dbff159", - "sha256": "5c61a106d9970b0f9b14e78e1523894d57b50cd0473f7d5a1fb1a9161dbff159" - }, - "catalina": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/golang-migrate/blobs/sha256:a77af5282af35e0d073e82140b091eedf0b478c19aea36f1b06738690989cebb", - "sha256": "a77af5282af35e0d073e82140b091eedf0b478c19aea36f1b06738690989cebb" - }, - "mojave": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/golang-migrate/blobs/sha256:8fa3758e979f09c171388887c831a6518e3f8df67b07668b6c8cebf76b19a653", - "sha256": "8fa3758e979f09c171388887c831a6518e3f8df67b07668b6c8cebf76b19a653" - } - } - } - }, - "pgbouncer": { - "version": "1.15.0", - "bottle": { - "rebuild": 1, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_big_sur": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/pgbouncer/blobs/sha256:8107249d240e1a53f6ae84587c08129acf5c294c4022f92d5f1c731ea6956ea3", - "sha256": "8107249d240e1a53f6ae84587c08129acf5c294c4022f92d5f1c731ea6956ea3" - }, - "big_sur": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/pgbouncer/blobs/sha256:09f21ff3e7b2c125d793da2ba64110392227650ae8157ef987f041959af8fe7c", - "sha256": "09f21ff3e7b2c125d793da2ba64110392227650ae8157ef987f041959af8fe7c" - }, - "catalina": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/pgbouncer/blobs/sha256:fad76f523bac43aaf7859fa0085ab7c6582f9d4aeb682e677db8f5acd9c4159a", - "sha256": "fad76f523bac43aaf7859fa0085ab7c6582f9d4aeb682e677db8f5acd9c4159a" - }, - "mojave": { - "cellar": ":any", - "url": "https://ghcr.io/v2/homebrew/core/pgbouncer/blobs/sha256:4187ceded551fad5801a26f790e61dd7d654acc675de73a1b4bf2858920d0734", - "sha256": "4187ceded551fad5801a26f790e61dd7d654acc675de73a1b4bf2858920d0734" - } - } - } - }, - "heroku": { - "version": "7.56.0", - "bottle": false - } - }, - "tap": { - "contribsys/faktory": { - "revision": "ada2f8b18fe79a40a906a7371d36c1d750422ca3" - }, - "heroku/brew": { - "revision": "2a5c611fc8204f37fbe1a8be955c1fc6103edd5a" - } - } - }, - "system": { - "macos": { - "big_sur": { - "HOMEBREW_VERSION": "3.2.1", - "HOMEBREW_PREFIX": "/usr/local", - "Homebrew/homebrew-core": "bd8b72f3f6453d9c55c73c36830b61a34b677bb0", - "CLT": "12.5.0.22.11", - "Xcode": "12.5.1", - "macOS": "11.4" - } - } - } -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4014d3d --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +BREW_PREFIX ?= $(shell brew --prefix) +DATABASE_URL ?= "postgres://$(USER)@localhost/apollo_test?sslmode=disable" + +test: + @DATABASE_URL=$(DATABASE_URL) go test -race -v -timeout 1s ./... + +test-setup: $(BREW_PREFIX)/bin/migrate + migrate -path migrations/ -database $(DATABASE_URL) up + +build: + @go build ./cmd/apollo + +lint: + @golangci-lint run + +$(BREW_PREFIX)/bin/migrate: + @brew install golang-migrate + +.PHONY: all build deps lint test diff --git a/Procfile b/Procfile deleted file mode 100644 index f654619..0000000 --- a/Procfile +++ /dev/null @@ -1,3 +0,0 @@ -web: apollo api -scheduler: apollo scheduler -worker-notifications: apollo worker --queue notifications --multiplier 128 diff --git a/env.example b/env.example deleted file mode 100644 index b770841..0000000 --- a/env.example +++ /dev/null @@ -1,4 +0,0 @@ -DATABASE_URL=postgres://apollo:@localhost/apollo?sslmode=disable -FAKTORY_URL=tcp://localhost:7419 -REDDIT_CLIENT_ID=C7MjYkx1czyRDA -REDDIT_CLIENT_SECRET=I2AsVWbrf8h4vdQxVa5Pvf84vScF1w diff --git a/go.mod b/go.mod index 6241748..df60f0c 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,9 @@ module github.com/christianselig/apollo-backend -// +heroku goVersion go1.16 -go 1.16 +go 1.18 require ( github.com/DataDog/datadog-go v4.8.3+incompatible - github.com/Microsoft/go-winio v0.5.0 // indirect github.com/adjust/rmq/v4 v4.0.5 github.com/bugsnag/bugsnag-go/v2 v2.1.2 github.com/dustin/go-humanize v1.0.0 @@ -15,12 +13,42 @@ require ( github.com/go-redis/redismock/v8 v8.0.6 github.com/gorilla/mux v1.8.0 github.com/heroku/x v0.0.50 + github.com/jackc/pgconn v1.12.0 github.com/jackc/pgx/v4 v4.16.0 github.com/joho/godotenv v1.4.0 github.com/sideshow/apns2 v0.23.0 github.com/sirupsen/logrus v1.8.1 - github.com/smtp2go-oss/smtp2go-go v1.0.1 // indirect + github.com/smtp2go-oss/smtp2go-go v1.0.1 github.com/spf13/cobra v1.4.0 github.com/stretchr/testify v1.7.1 github.com/valyala/fastjson v1.6.3 ) + +require ( + github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/bugsnag/panicwrap v1.3.4 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.4.1 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.11.0 // indirect + github.com/jackc/puddle v1.2.1 // indirect + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum index 4a5576e..23a2e3c 100644 --- a/go.sum +++ b/go.sum @@ -32,14 +32,10 @@ github.com/bugsnag/panicwrap v1.3.4 h1:A6sXFtDGsgU/4BLf5JT0o5uYg3EeKgGx3Sfs+/uk3 github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -92,7 +88,6 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq github.com/go-redis/redismock/v8 v8.0.6 h1:rtuijPgGynsRB2Y7KDACm09WvjHWS4RaG44Nm7rcj4Y= github.com/go-redis/redismock/v8 v8.0.6/go.mod h1:sDIF73OVsmaKzYe/1FJXGiCQ4+oHYbzjpaL9Vor0sS4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -133,7 +128,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gops v0.3.22/go.mod h1:7diIdLsqpCihPSX3fQagksT/Ku/y4RL9LHTlKyEUDl8= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -150,10 +144,8 @@ github.com/heroku/x v0.0.50 h1:CA0AXkSumucVJD+T+x+6c7X1iDEb+40F8GNgH5UjJwo= github.com/heroku/x v0.0.50/go.mod h1:vr+jORZ6sG3wgEq2FAS6UbOUrz9/DxpQGN/xPHVgbSM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -174,7 +166,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -241,23 +232,17 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 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.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 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.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -411,7 +396,6 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0= golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -445,7 +429,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -454,7 +437,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/api/accounts.go b/internal/api/accounts.go index b3fa35e..d73b4f7 100644 --- a/internal/api/accounts.go +++ b/internal/api/accounts.go @@ -143,7 +143,7 @@ func (a *api) upsertAccountsHandler(w http.ResponseWriter, r *http.Request) { delete(accsMap, acc.NormalizedUsername()) ac := a.reddit.NewAuthenticatedClient(reddit.SkipRateLimiting, acc.RefreshToken, acc.AccessToken) - tokens, err := ac.RefreshTokens() + tokens, err := ac.RefreshTokens(ctx) if err != nil { a.errorResponse(w, r, 422, err.Error()) return @@ -155,7 +155,7 @@ func (a *api) upsertAccountsHandler(w http.ResponseWriter, r *http.Request) { acc.AccessToken = tokens.AccessToken ac = a.reddit.NewAuthenticatedClient(reddit.SkipRateLimiting, acc.RefreshToken, acc.AccessToken) - me, err := ac.Me() + me, err := ac.Me(ctx) if err != nil { a.errorResponse(w, r, 422, err.Error()) @@ -186,9 +186,9 @@ func (a *api) upsertAccountsHandler(w http.ResponseWriter, r *http.Request) { _ = a.accountRepo.Disassociate(ctx, &acc, &dev) } - go func(apns string) { + go func(ctx context.Context, apns string) { url := fmt.Sprintf("https://apollopushserver.xyz/api/new-server-addition?apns_token=%s", apns) - req, err := http.NewRequest("POST", url, nil) + req, err := http.NewRequestWithContext(ctx, "POST", url, nil) req.Header.Set("Authentication", "Bearer 98g5j89aurqwfcsp9khlnvgd38fa15") if err != nil { @@ -200,7 +200,7 @@ func (a *api) upsertAccountsHandler(w http.ResponseWriter, r *http.Request) { resp, _ := a.httpClient.Do(req) resp.Body.Close() - }(apns) + }(ctx, apns) w.WriteHeader(http.StatusOK) } @@ -221,7 +221,7 @@ func (a *api) upsertAccountHandler(w http.ResponseWriter, r *http.Request) { // Here we check whether the account is supplied with a valid token. ac := a.reddit.NewAuthenticatedClient(reddit.SkipRateLimiting, acct.RefreshToken, acct.AccessToken) - tokens, err := ac.RefreshTokens() + tokens, err := ac.RefreshTokens(ctx) if err != nil { a.logger.WithFields(logrus.Fields{ "err": err, @@ -236,7 +236,7 @@ func (a *api) upsertAccountHandler(w http.ResponseWriter, r *http.Request) { acct.AccessToken = tokens.AccessToken ac = a.reddit.NewAuthenticatedClient(reddit.SkipRateLimiting, acct.RefreshToken, acct.AccessToken) - me, err := ac.Me() + me, err := ac.Me(ctx) if err != nil { a.logger.WithFields(logrus.Fields{ diff --git a/internal/api/contact.go b/internal/api/contact.go index 9a0d62f..82f5e73 100644 --- a/internal/api/contact.go +++ b/internal/api/contact.go @@ -26,8 +26,7 @@ func (a *api) contactHandler(w http.ResponseWriter, r *http.Request) { TextBody: smr.Body, } - _, err := smtp2go.Send(msg) - if err != nil { + if _, err := smtp2go.Send(msg); err != nil { a.errorResponse(w, r, 500, err.Error()) return } diff --git a/internal/api/errors.go b/internal/api/errors.go index 16b2ba7..83815b5 100644 --- a/internal/api/errors.go +++ b/internal/api/errors.go @@ -2,7 +2,7 @@ package api import "net/http" -func (a *api) errorResponse(w http.ResponseWriter, r *http.Request, status int, message string) { +func (a *api) errorResponse(w http.ResponseWriter, _ *http.Request, status int, message string) { w.Header().Set("X-Apollo-Error", message) http.Error(w, message, status) } diff --git a/internal/api/watcher.go b/internal/api/watcher.go index 8882fb4..e37a0df 100644 --- a/internal/api/watcher.go +++ b/internal/api/watcher.go @@ -104,7 +104,7 @@ func (a *api) createWatcherHandler(w http.ResponseWriter, r *http.Request) { } if cwr.Type == "subreddit" || cwr.Type == "trending" { - srr, err := ac.SubredditAbout(cwr.Subreddit) + srr, err := ac.SubredditAbout(ctx, cwr.Subreddit) if err != nil { a.errorResponse(w, r, 422, err.Error()) return @@ -133,7 +133,7 @@ func (a *api) createWatcherHandler(w http.ResponseWriter, r *http.Request) { watcher.WatcheeID = sr.ID } else if cwr.Type == "user" { - urr, err := ac.UserAbout(cwr.User) + urr, err := ac.UserAbout(ctx, cwr.User) if err != nil { a.errorResponse(w, r, 500, err.Error()) return diff --git a/internal/cmd/scheduler.go b/internal/cmd/scheduler.go index debede5..9cc16aa 100644 --- a/internal/cmd/scheduler.go +++ b/internal/cmd/scheduler.go @@ -89,9 +89,9 @@ func SchedulerCmd(ctx context.Context) *cobra.Command { _, _ = s.Every(200).Milliseconds().SingletonMode().Do(func() { enqueueAccounts(ctx, logger, statsd, db, redis, luaSha, notifQueue) }) _, _ = s.Every(200).Milliseconds().SingletonMode().Do(func() { enqueueSubreddits(ctx, logger, statsd, db, []rmq.Queue{subredditQueue, trendingQueue}) }) _, _ = s.Every(200).Milliseconds().SingletonMode().Do(func() { enqueueUsers(ctx, logger, statsd, db, userQueue) }) - _, _ = s.Every(1).Second().Do(func() { cleanQueues(ctx, logger, queue) }) + _, _ = s.Every(1).Second().Do(func() { cleanQueues(logger, queue) }) _, _ = s.Every(1).Second().Do(func() { enqueueStuckAccounts(ctx, logger, statsd, db, stuckNotificationsQueue) }) - _, _ = s.Every(1).Minute().Do(func() { reportStats(ctx, logger, statsd, db, redis) }) + _, _ = s.Every(1).Minute().Do(func() { reportStats(ctx, logger, statsd, db) }) _, _ = s.Every(1).Minute().Do(func() { pruneAccounts(ctx, logger, db) }) _, _ = s.Every(1).Minute().Do(func() { pruneDevices(ctx, logger, db) }) s.StartAsync() @@ -146,9 +146,7 @@ func pruneAccounts(ctx context.Context, logger *logrus.Logger, pool *pgxpool.Poo return } - count := stale + orphaned - - if count > 0 { + if count := stale + orphaned; count > 0 { logger.WithFields(logrus.Fields{ "stale": stale, "orphaned": orphaned, @@ -175,7 +173,7 @@ func pruneDevices(ctx context.Context, logger *logrus.Logger, pool *pgxpool.Pool } } -func cleanQueues(ctx context.Context, logger *logrus.Logger, jobsConn rmq.Connection) { +func cleanQueues(logger *logrus.Logger, jobsConn rmq.Connection) { cleaner := rmq.NewCleaner(jobsConn) count, err := cleaner.Clean() if err != nil { @@ -192,7 +190,7 @@ func cleanQueues(ctx context.Context, logger *logrus.Logger, jobsConn rmq.Connec } } -func reportStats(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, pool *pgxpool.Pool, redisConn *redis.Client) { +func reportStats(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, pool *pgxpool.Pool) { var ( count int64 diff --git a/internal/cmd/worker.go b/internal/cmd/worker.go index 97416f0..babfdfb 100644 --- a/internal/cmd/worker.go +++ b/internal/cmd/worker.go @@ -68,7 +68,7 @@ func WorkerCmd(ctx context.Context) *cobra.Command { return fmt.Errorf("invalid queue: %s", queueID) } - worker := workerFn(logger, statsd, db, redis, queue, consumers) + worker := workerFn(ctx, logger, statsd, db, redis, queue, consumers) if err := worker.Start(); err != nil { return err } diff --git a/internal/domain/subreddit_test.go b/internal/domain/subreddit_test.go index 658b33d..fbebe40 100644 --- a/internal/domain/subreddit_test.go +++ b/internal/domain/subreddit_test.go @@ -1,4 +1,4 @@ -package domain +package domain_test import ( "errors" @@ -6,21 +6,27 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/christianselig/apollo-backend/internal/domain" ) func TestValidate(t *testing.T) { + t.Parallel() + tests := map[string]struct { - subreddit Subreddit + subreddit domain.Subreddit err error }{ - "invalid subreddit prefix": {Subreddit{Name: "u_iamthatis"}, errors.New("invalid subreddit format")}, - "valid subreddit": {Subreddit{Name: "pics", SubredditID: "abcd"}, nil}, - "valid subreddit starting with u": {Subreddit{Name: "urcool", SubredditID: "abcd"}, nil}, - "valid subreddit with _": {Subreddit{Name: "p_i_x_a_r", SubredditID: "abcd"}, nil}, + "invalid subreddit prefix": {domain.Subreddit{Name: "u_iamthatis"}, errors.New("invalid subreddit format")}, + "valid subreddit": {domain.Subreddit{Name: "pics", SubredditID: "abcd"}, nil}, + "valid subreddit starting with u": {domain.Subreddit{Name: "urcool", SubredditID: "abcd"}, nil}, + "valid subreddit with _": {domain.Subreddit{Name: "p_i_x_a_r", SubredditID: "abcd"}, nil}, } for scenario, tc := range tests { t.Run(scenario, func(t *testing.T) { + t.Parallel() + err := tc.subreddit.Validate() if tc.err == nil { diff --git a/internal/itunes/receipt.go b/internal/itunes/receipt.go index 185c458..8a609af 100644 --- a/internal/itunes/receipt.go +++ b/internal/itunes/receipt.go @@ -2,6 +2,7 @@ package itunes import ( "bytes" + "context" "encoding/json" "fmt" "net" @@ -168,6 +169,8 @@ type ( ) func NewIAPResponse(receipt string, production bool) (*IAPResponse, error) { + ctx := context.Background() + // Send the receipt data string off to Apple's servers to verify appleVerificationURL := "https://buy.itunes.apple.com/verifyReceipt" @@ -186,7 +189,7 @@ func NewIAPResponse(receipt string, production bool) (*IAPResponse, error) { return nil, err } - request, requestErr := http.NewRequest("POST", appleVerificationURL, bytes.NewBuffer(bb)) + request, requestErr := http.NewRequestWithContext(ctx, "POST", appleVerificationURL, bytes.NewBuffer(bb)) if requestErr != nil { fmt.Println(requestErr) @@ -364,7 +367,7 @@ func (iapr *IAPResponse) handleAppleResponse() { mostRecentTransactionUnixTimestamp := mostRecentTransaction.ExpiresDateMS / 1000 // Check if it's not active - currentTimeUnixTimestamp := int64(time.Now().Unix()) + currentTimeUnixTimestamp := time.Now().Unix() if mostRecentTransactionUnixTimestamp < currentTimeUnixTimestamp { if len(iapr.PendingRenewalInfo) > 0 && iapr.PendingRenewalInfo[0].SubscriptionAutoRenewStatus == "0" { diff --git a/internal/reddit/client.go b/internal/reddit/client.go index 6d4d023..e40f665 100644 --- a/internal/reddit/client.go +++ b/internal/reddit/client.go @@ -127,17 +127,22 @@ func (rc *Client) NewAuthenticatedClient(redditId, refreshToken, accessToken str return &AuthenticatedClient{rc, redditId, refreshToken, accessToken} } -func (rc *Client) doRequest(r *Request) ([]byte, *RateLimitingInfo, error) { - req, err := r.HTTPRequest() +func (rc *Client) doRequest(ctx context.Context, r *Request) ([]byte, *RateLimitingInfo, error) { + req, err := r.HTTPRequest(ctx) if err != nil { return nil, nil, err } - req = req.WithContext(httptrace.WithClientTrace(req.Context(), rc.tracer)) + req = req.WithContext(httptrace.WithClientTrace(ctx, rc.tracer)) start := time.Now() - resp, err := rc.client.Do(req) + client := r.client + if client == nil { + client = rc.client + } + + resp, err := client.Do(req) _ = rc.statsd.Incr("reddit.api.calls", r.tags, 0.1) _ = rc.statsd.Histogram("reddit.api.latency", float64(time.Since(start).Milliseconds()), r.tags, 0.1) @@ -173,7 +178,7 @@ func (rc *Client) doRequest(r *Request) ([]byte, *RateLimitingInfo, error) { return bb, rli, nil } -func (rac *AuthenticatedClient) request(r *Request, rh ResponseHandler, empty interface{}) (interface{}, error) { +func (rac *AuthenticatedClient) request(ctx context.Context, r *Request, rh ResponseHandler, empty interface{}) (interface{}, error) { if rac.isRateLimited() { return nil, ErrRateLimited } @@ -182,7 +187,7 @@ func (rac *AuthenticatedClient) request(r *Request, rh ResponseHandler, empty in return nil, err } - bb, rli, err := rac.doRequest(r) + bb, rli, err := rac.doRequest(ctx, r) if err != nil && r.retry { for _, backoff := range backoffSchedule { @@ -196,7 +201,7 @@ func (rac *AuthenticatedClient) request(r *Request, rh ResponseHandler, empty in return } - bb, rli, err = rac.doRequest(r) + bb, rli, err = rac.doRequest(ctx, r) done <- struct{}{} }) @@ -281,7 +286,7 @@ func (rac *AuthenticatedClient) markRateLimited(rli *RateLimitingInfo) error { return err } -func (rac *AuthenticatedClient) RefreshTokens(opts ...RequestOption) (*RefreshTokenResponse, error) { +func (rac *AuthenticatedClient) RefreshTokens(ctx context.Context, opts ...RequestOption) (*RefreshTokenResponse, error) { opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ WithTags([]string{"url:/api/v1/access_token"}), @@ -293,7 +298,7 @@ func (rac *AuthenticatedClient) RefreshTokens(opts ...RequestOption) (*RefreshTo }...) req := NewRequest(opts...) - rtr, err := rac.request(req, NewRefreshTokenResponse, nil) + rtr, err := rac.request(ctx, req, NewRefreshTokenResponse, nil) if err != nil { switch rerr := err.(type) { case ServerError: @@ -313,7 +318,7 @@ func (rac *AuthenticatedClient) RefreshTokens(opts ...RequestOption) (*RefreshTo return ret, nil } -func (rac *AuthenticatedClient) AboutInfo(fullname string, opts ...RequestOption) (*ListingResponse, error) { +func (rac *AuthenticatedClient) AboutInfo(ctx context.Context, fullname string, opts ...RequestOption) (*ListingResponse, error) { opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ WithMethod("GET"), @@ -323,7 +328,7 @@ func (rac *AuthenticatedClient) AboutInfo(fullname string, opts ...RequestOption }...) req := NewRequest(opts...) - lr, err := rac.request(req, NewListingResponse, nil) + lr, err := rac.request(ctx, req, NewListingResponse, nil) if err != nil { return nil, err } @@ -331,7 +336,7 @@ func (rac *AuthenticatedClient) AboutInfo(fullname string, opts ...RequestOption return lr.(*ListingResponse), nil } -func (rac *AuthenticatedClient) UserPosts(user string, opts ...RequestOption) (*ListingResponse, error) { +func (rac *AuthenticatedClient) UserPosts(ctx context.Context, user string, opts ...RequestOption) (*ListingResponse, error) { url := fmt.Sprintf("https://oauth.reddit.com/u/%s/submitted", user) opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ @@ -341,7 +346,7 @@ func (rac *AuthenticatedClient) UserPosts(user string, opts ...RequestOption) (* }...) req := NewRequest(opts...) - lr, err := rac.request(req, NewListingResponse, nil) + lr, err := rac.request(ctx, req, NewListingResponse, nil) if err != nil { return nil, err } @@ -349,7 +354,7 @@ func (rac *AuthenticatedClient) UserPosts(user string, opts ...RequestOption) (* return lr.(*ListingResponse), nil } -func (rac *AuthenticatedClient) UserAbout(user string, opts ...RequestOption) (*UserResponse, error) { +func (rac *AuthenticatedClient) UserAbout(ctx context.Context, user string, opts ...RequestOption) (*UserResponse, error) { url := fmt.Sprintf("https://oauth.reddit.com/u/%s/about", user) opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ @@ -358,7 +363,7 @@ func (rac *AuthenticatedClient) UserAbout(user string, opts ...RequestOption) (* WithURL(url), }...) req := NewRequest(opts...) - ur, err := rac.request(req, NewUserResponse, nil) + ur, err := rac.request(ctx, req, NewUserResponse, nil) if err != nil { return nil, err @@ -368,7 +373,7 @@ func (rac *AuthenticatedClient) UserAbout(user string, opts ...RequestOption) (* } -func (rac *AuthenticatedClient) SubredditAbout(subreddit string, opts ...RequestOption) (*SubredditResponse, error) { +func (rac *AuthenticatedClient) SubredditAbout(ctx context.Context, subreddit string, opts ...RequestOption) (*SubredditResponse, error) { url := fmt.Sprintf("https://oauth.reddit.com/r/%s/about", subreddit) opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ @@ -377,7 +382,7 @@ func (rac *AuthenticatedClient) SubredditAbout(subreddit string, opts ...Request WithURL(url), }...) req := NewRequest(opts...) - sr, err := rac.request(req, NewSubredditResponse, nil) + sr, err := rac.request(ctx, req, NewSubredditResponse, nil) if err != nil { return nil, err @@ -386,7 +391,7 @@ func (rac *AuthenticatedClient) SubredditAbout(subreddit string, opts ...Request return sr.(*SubredditResponse), nil } -func (rac *AuthenticatedClient) subredditPosts(subreddit string, sort string, opts ...RequestOption) (*ListingResponse, error) { +func (rac *AuthenticatedClient) subredditPosts(ctx context.Context, subreddit string, sort string, opts ...RequestOption) (*ListingResponse, error) { url := fmt.Sprintf("https://oauth.reddit.com/r/%s/%s", subreddit, sort) opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ @@ -396,7 +401,7 @@ func (rac *AuthenticatedClient) subredditPosts(subreddit string, sort string, op }...) req := NewRequest(opts...) - lr, err := rac.request(req, NewListingResponse, nil) + lr, err := rac.request(ctx, req, NewListingResponse, nil) if err != nil { return nil, err } @@ -404,19 +409,19 @@ func (rac *AuthenticatedClient) subredditPosts(subreddit string, sort string, op return lr.(*ListingResponse), nil } -func (rac *AuthenticatedClient) SubredditHot(subreddit string, opts ...RequestOption) (*ListingResponse, error) { - return rac.subredditPosts(subreddit, "hot", opts...) +func (rac *AuthenticatedClient) SubredditHot(ctx context.Context, subreddit string, opts ...RequestOption) (*ListingResponse, error) { + return rac.subredditPosts(ctx, subreddit, "hot", opts...) } -func (rac *AuthenticatedClient) SubredditTop(subreddit string, opts ...RequestOption) (*ListingResponse, error) { - return rac.subredditPosts(subreddit, "top", opts...) +func (rac *AuthenticatedClient) SubredditTop(ctx context.Context, subreddit string, opts ...RequestOption) (*ListingResponse, error) { + return rac.subredditPosts(ctx, subreddit, "top", opts...) } -func (rac *AuthenticatedClient) SubredditNew(subreddit string, opts ...RequestOption) (*ListingResponse, error) { - return rac.subredditPosts(subreddit, "new", opts...) +func (rac *AuthenticatedClient) SubredditNew(ctx context.Context, subreddit string, opts ...RequestOption) (*ListingResponse, error) { + return rac.subredditPosts(ctx, subreddit, "new", opts...) } -func (rac *AuthenticatedClient) MessageInbox(opts ...RequestOption) (*ListingResponse, error) { +func (rac *AuthenticatedClient) MessageInbox(ctx context.Context, opts ...RequestOption) (*ListingResponse, error) { opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ WithTags([]string{"url:/api/v1/message/inbox"}), @@ -427,7 +432,7 @@ func (rac *AuthenticatedClient) MessageInbox(opts ...RequestOption) (*ListingRes }...) req := NewRequest(opts...) - lr, err := rac.request(req, NewListingResponse, EmptyListingResponse) + lr, err := rac.request(ctx, req, NewListingResponse, EmptyListingResponse) if err != nil { switch rerr := err.(type) { case ServerError: @@ -441,7 +446,7 @@ func (rac *AuthenticatedClient) MessageInbox(opts ...RequestOption) (*ListingRes return lr.(*ListingResponse), nil } -func (rac *AuthenticatedClient) MessageUnread(opts ...RequestOption) (*ListingResponse, error) { +func (rac *AuthenticatedClient) MessageUnread(ctx context.Context, opts ...RequestOption) (*ListingResponse, error) { opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ WithTags([]string{"url:/api/v1/message/unread"}), @@ -453,7 +458,7 @@ func (rac *AuthenticatedClient) MessageUnread(opts ...RequestOption) (*ListingRe req := NewRequest(opts...) - lr, err := rac.request(req, NewListingResponse, EmptyListingResponse) + lr, err := rac.request(ctx, req, NewListingResponse, EmptyListingResponse) if err != nil { switch rerr := err.(type) { case ServerError: @@ -467,7 +472,7 @@ func (rac *AuthenticatedClient) MessageUnread(opts ...RequestOption) (*ListingRe return lr.(*ListingResponse), nil } -func (rac *AuthenticatedClient) Me(opts ...RequestOption) (*MeResponse, error) { +func (rac *AuthenticatedClient) Me(ctx context.Context, opts ...RequestOption) (*MeResponse, error) { opts = append(rac.defaultOpts, opts...) opts = append(opts, []RequestOption{ WithTags([]string{"url:/api/v1/me"}), @@ -477,7 +482,7 @@ func (rac *AuthenticatedClient) Me(opts ...RequestOption) (*MeResponse, error) { }...) req := NewRequest(opts...) - mr, err := rac.request(req, NewMeResponse, nil) + mr, err := rac.request(ctx, req, NewMeResponse, nil) if err != nil { switch rerr := err.(type) { case ServerError: diff --git a/internal/reddit/client_test.go b/internal/reddit/client_test.go index e68598a..8e3b714 100644 --- a/internal/reddit/client_test.go +++ b/internal/reddit/client_test.go @@ -1,12 +1,14 @@ -package reddit +package reddit_test import ( "bytes" + "context" "io/ioutil" "net/http" "testing" "github.com/DataDog/datadog-go/statsd" + "github.com/christianselig/apollo-backend/internal/reddit" "github.com/go-redis/redismock/v8" "github.com/stretchr/testify/assert" ) @@ -21,35 +23,37 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { //NewTestClient returns *http.Client with Transport replaced to avoid making real calls func NewTestClient(fn RoundTripFunc) *http.Client { - return &http.Client{ - Transport: RoundTripFunc(fn), - } + return &http.Client{Transport: fn} } func TestErrorResponse(t *testing.T) { + t.Parallel() + + ctx := context.Background() + db, _ := redismock.NewClientMock() - rc := NewClient("", "", &statsd.NoOpClient{}, db, 1, WithRetry(false)) - rac := rc.NewAuthenticatedClient(SkipRateLimiting, "", "") - - errortests := []struct { - name string - call func() error + errortests := map[string]struct { + call func(*reddit.AuthenticatedClient) error status int body string err error }{ - {"/api/v1/me 500 returns ServerError", func() error { _, err := rac.Me(); return err }, 500, "", ServerError{500}}, - {"/api/v1/access_token 400 returns ErrOauthRevoked", func() error { _, err := rac.RefreshTokens(); return err }, 400, "", ErrOauthRevoked}, - {"/api/v1/message/inbox 403 returns ErrOauthRevoked", func() error { _, err := rac.MessageInbox(); return err }, 403, "", ErrOauthRevoked}, - {"/api/v1/message/unread 403 returns ErrOauthRevoked", func() error { _, err := rac.MessageUnread(); return err }, 403, "", ErrOauthRevoked}, - {"/api/v1/me 403 returns ErrOauthRevoked", func() error { _, err := rac.Me(); return err }, 403, "", ErrOauthRevoked}, + "/api/v1/me 500 returns ServerError": {func(rac *reddit.AuthenticatedClient) error { _, err := rac.Me(ctx); return err }, 500, "", reddit.ServerError{500}}, + "/api/v1/access_token 400 returns ErrOauthRevoked": {func(rac *reddit.AuthenticatedClient) error { _, err := rac.RefreshTokens(ctx); return err }, 400, "", reddit.ErrOauthRevoked}, + "/api/v1/message/inbox 403 returns ErrOauthRevoked": {func(rac *reddit.AuthenticatedClient) error { _, err := rac.MessageInbox(ctx); return err }, 403, "", reddit.ErrOauthRevoked}, + "/api/v1/message/unread 403 returns ErrOauthRevoked": {func(rac *reddit.AuthenticatedClient) error { _, err := rac.MessageUnread(ctx); return err }, 403, "", reddit.ErrOauthRevoked}, + "/api/v1/me 403 returns ErrOauthRevoked": {func(rac *reddit.AuthenticatedClient) error { _, err := rac.Me(ctx); return err }, 403, "", reddit.ErrOauthRevoked}, } - for _, tt := range errortests { - t.Run(tt.name, func(t *testing.T) { - rac.client = NewTestClient(func(req *http.Request) *http.Response { + for scenario, tt := range errortests { + tt := tt + + t.Run(scenario, func(t *testing.T) { + t.Parallel() + + tc := NewTestClient(func(req *http.Request) *http.Response { return &http.Response{ StatusCode: tt.status, Body: ioutil.NopCloser(bytes.NewBufferString(tt.body)), @@ -57,7 +61,10 @@ func TestErrorResponse(t *testing.T) { } }) - err := tt.call() + rc := reddit.NewClient("", "", &statsd.NoOpClient{}, db, 1, reddit.WithRetry(false), reddit.WithClient(tc)) + rac := rc.NewAuthenticatedClient(reddit.SkipRateLimiting, "", "") + + err := tt.call(rac) assert.ErrorIs(t, err, tt.err) }) diff --git a/internal/reddit/request.go b/internal/reddit/request.go index 4e46126..9fb2547 100644 --- a/internal/reddit/request.go +++ b/internal/reddit/request.go @@ -1,6 +1,7 @@ package reddit import ( + "context" "encoding/base64" "fmt" "net/http" @@ -20,6 +21,7 @@ type Request struct { tags []string emptyResponseBytes int retry bool + client *http.Client } type RequestOption func(*Request) @@ -38,6 +40,7 @@ func NewRequest(opts ...RequestOption) *Request { emptyResponseBytes: 0, retry: true, + client: nil, } for _, opt := range opts { @@ -47,8 +50,8 @@ func NewRequest(opts ...RequestOption) *Request { return req } -func (r *Request) HTTPRequest() (*http.Request, error) { - req, err := http.NewRequest(r.method, r.url, strings.NewReader(r.body.Encode())) +func (r *Request) HTTPRequest(ctx context.Context) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, r.method, r.url, strings.NewReader(r.body.Encode())) req.URL.RawQuery = r.query.Encode() req.Header.Add("Accept", "application/json") @@ -123,3 +126,9 @@ func WithRetry(retry bool) RequestOption { req.retry = retry } } + +func WithClient(client *http.Client) RequestOption { + return func(req *Request) { + req.client = client + } +} diff --git a/internal/reddit/types_test.go b/internal/reddit/types_test.go index f44e76f..7396878 100644 --- a/internal/reddit/types_test.go +++ b/internal/reddit/types_test.go @@ -1,27 +1,41 @@ -package reddit +package reddit_test import ( "io/ioutil" "testing" "time" + "github.com/christianselig/apollo-backend/internal/reddit" "github.com/stretchr/testify/assert" "github.com/valyala/fastjson" ) -var ( - parser = &fastjson.Parser{} -) +var pool = &fastjson.ParserPool{} + +func NewTestParser(t *testing.T) *fastjson.Parser { + t.Helper() + + parser := pool.Get() + + t.Cleanup(func() { + pool.Put(parser) + }) + + return parser +} func TestMeResponseParsing(t *testing.T) { + t.Parallel() + bb, err := ioutil.ReadFile("testdata/me.json") assert.NoError(t, err) + parser := NewTestParser(t) val, err := parser.ParseBytes(bb) assert.NoError(t, err) - ret := NewMeResponse(val) - me := ret.(*MeResponse) + ret := reddit.NewMeResponse(val) + me := ret.(*reddit.MeResponse) assert.NotNil(t, me) assert.Equal(t, "xgeee", me.ID) @@ -29,14 +43,17 @@ func TestMeResponseParsing(t *testing.T) { } func TestRefreshTokenResponseParsing(t *testing.T) { + t.Parallel() + bb, err := ioutil.ReadFile("testdata/refresh_token.json") assert.NoError(t, err) + parser := NewTestParser(t) val, err := parser.ParseBytes(bb) assert.NoError(t, err) - ret := NewRefreshTokenResponse(val) - rtr := ret.(*RefreshTokenResponse) + ret := reddit.NewRefreshTokenResponse(val) + rtr := ret.(*reddit.RefreshTokenResponse) assert.NotNil(t, rtr) assert.Equal(t, "***REMOVED***", rtr.AccessToken) @@ -45,15 +62,18 @@ func TestRefreshTokenResponseParsing(t *testing.T) { } func TestListingResponseParsing(t *testing.T) { + t.Parallel() + // Message list bb, err := ioutil.ReadFile("testdata/message_inbox.json") assert.NoError(t, err) + parser := NewTestParser(t) val, err := parser.ParseBytes(bb) assert.NoError(t, err) - ret := NewListingResponse(val) - l := ret.(*ListingResponse) + ret := reddit.NewListingResponse(val) + l := ret.(*reddit.ListingResponse) assert.NotNil(t, l) assert.Equal(t, 25, l.Count) @@ -62,7 +82,7 @@ func TestListingResponseParsing(t *testing.T) { assert.Equal(t, "", l.Before) thing := l.Children[0] - created := time.Time(time.Date(2021, time.July, 14, 17, 56, 35, 0, time.UTC)) + created := time.Date(2021, time.July, 14, 17, 56, 35, 0, time.UTC) assert.Equal(t, "t4", thing.Kind) assert.Equal(t, "138z6ke", thing.ID) assert.Equal(t, "unknown", thing.Type) @@ -86,8 +106,8 @@ func TestListingResponseParsing(t *testing.T) { val, err = parser.ParseBytes(bb) assert.NoError(t, err) - ret = NewListingResponse(val) - l = ret.(*ListingResponse) + ret = reddit.NewListingResponse(val) + l = ret.(*reddit.ListingResponse) assert.NotNil(t, l) assert.Equal(t, 100, l.Count) @@ -100,14 +120,17 @@ func TestListingResponseParsing(t *testing.T) { } func TestSubredditResponseParsing(t *testing.T) { + t.Parallel() + bb, err := ioutil.ReadFile("testdata/subreddit_about.json") assert.NoError(t, err) + parser := NewTestParser(t) val, err := parser.ParseBytes(bb) assert.NoError(t, err) - ret := NewSubredditResponse(val) - s := ret.(*SubredditResponse) + ret := reddit.NewSubredditResponse(val) + s := ret.(*reddit.SubredditResponse) assert.NotNil(t, s) assert.Equal(t, "t5", s.Kind) @@ -116,14 +139,17 @@ func TestSubredditResponseParsing(t *testing.T) { } func TestUserResponseParsing(t *testing.T) { + t.Parallel() + bb, err := ioutil.ReadFile("testdata/user_about.json") assert.NoError(t, err) + parser := NewTestParser(t) val, err := parser.ParseBytes(bb) assert.NoError(t, err) - ret := NewUserResponse(val) - u := ret.(*UserResponse) + ret := reddit.NewUserResponse(val) + u := ret.(*reddit.UserResponse) assert.NotNil(t, u) assert.Equal(t, "t2", u.Kind) @@ -133,14 +159,17 @@ func TestUserResponseParsing(t *testing.T) { } func TestUserPostsParsing(t *testing.T) { + t.Parallel() + bb, err := ioutil.ReadFile("testdata/user_posts.json") assert.NoError(t, err) + parser := NewTestParser(t) val, err := parser.ParseBytes(bb) assert.NoError(t, err) - ret := NewListingResponse(val) - ps := ret.(*ListingResponse) + ret := reddit.NewListingResponse(val) + ps := ret.(*reddit.ListingResponse) assert.NotNil(t, ps) post := ps.Children[0] diff --git a/internal/repository/connection.go b/internal/repository/connection.go new file mode 100644 index 0000000..f5f0867 --- /dev/null +++ b/internal/repository/connection.go @@ -0,0 +1,14 @@ +package repository + +import ( + "context" + + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" +) + +type Connection interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} diff --git a/internal/repository/postgres_account.go b/internal/repository/postgres_account.go index 83115b9..cba45f4 100644 --- a/internal/repository/postgres_account.go +++ b/internal/repository/postgres_account.go @@ -5,21 +5,19 @@ import ( "fmt" "time" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/christianselig/apollo-backend/internal/domain" ) type postgresAccountRepository struct { - pool *pgxpool.Pool + conn Connection } -func NewPostgresAccount(pool *pgxpool.Pool) domain.AccountRepository { - return &postgresAccountRepository{pool: pool} +func NewPostgresAccount(conn Connection) domain.AccountRepository { + return &postgresAccountRepository{conn: conn} } func (p *postgresAccountRepository) fetch(ctx context.Context, query string, args ...interface{}) ([]domain.Account, error) { - rows, err := p.pool.Query(ctx, query, args...) + rows, err := p.conn.Query(ctx, query, args...) if err != nil { return nil, err } @@ -96,7 +94,7 @@ func (p *postgresAccountRepository) CreateOrUpdate(ctx context.Context, acc *dom token_expires_at = $5 RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, acc.Username, @@ -115,7 +113,7 @@ func (p *postgresAccountRepository) Create(ctx context.Context, acc *domain.Acco VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, acc.Username, @@ -143,7 +141,7 @@ func (p *postgresAccountRepository) Update(ctx context.Context, acc *domain.Acco check_count = $10 WHERE id = $1` - res, err := p.pool.Exec( + res, err := p.conn.Exec( ctx, query, acc.ID, @@ -166,7 +164,7 @@ func (p *postgresAccountRepository) Update(ctx context.Context, acc *domain.Acco func (p *postgresAccountRepository) Delete(ctx context.Context, id int64) error { query := `DELETE FROM accounts WHERE id = $1` - res, err := p.pool.Exec(ctx, query, id) + res, err := p.conn.Exec(ctx, query, id) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -180,13 +178,13 @@ func (p *postgresAccountRepository) Associate(ctx context.Context, acc *domain.A (account_id, device_id) VALUES ($1, $2) ON CONFLICT(account_id, device_id) DO NOTHING` - _, err := p.pool.Exec(ctx, query, acc.ID, dev.ID) + _, err := p.conn.Exec(ctx, query, acc.ID, dev.ID) return err } func (p *postgresAccountRepository) Disassociate(ctx context.Context, acc *domain.Account, dev *domain.Device) error { query := `DELETE FROM devices_accounts WHERE account_id = $1 AND device_id = $2` - res, err := p.pool.Exec(ctx, query, acc.ID, dev.ID) + res, err := p.conn.Exec(ctx, query, acc.ID, dev.ID) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -212,7 +210,7 @@ func (p *postgresAccountRepository) PruneStale(ctx context.Context, expiry time. DELETE FROM accounts WHERE token_expires_at < $1` - res, err := p.pool.Exec(ctx, query, expiry) + res, err := p.conn.Exec(ctx, query, expiry) return res.RowsAffected(), err } @@ -231,7 +229,7 @@ func (p *postgresAccountRepository) PruneOrphaned(ctx context.Context) (int64, e WHERE device_count = 0 )` - res, err := p.pool.Exec(ctx, query) + res, err := p.conn.Exec(ctx, query) return res.RowsAffected(), err } diff --git a/internal/repository/postgres_device.go b/internal/repository/postgres_device.go index 08c255c..5152669 100644 --- a/internal/repository/postgres_device.go +++ b/internal/repository/postgres_device.go @@ -5,21 +5,19 @@ import ( "fmt" "time" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/christianselig/apollo-backend/internal/domain" ) type postgresDeviceRepository struct { - pool *pgxpool.Pool + conn Connection } -func NewPostgresDevice(pool *pgxpool.Pool) domain.DeviceRepository { - return &postgresDeviceRepository{pool: pool} +func NewPostgresDevice(conn Connection) domain.DeviceRepository { + return &postgresDeviceRepository{conn: conn} } func (p *postgresDeviceRepository) fetch(ctx context.Context, query string, args ...interface{}) ([]domain.Device, error) { - rows, err := p.pool.Query(ctx, query, args...) + rows, err := p.conn.Query(ctx, query, args...) if err != nil { return nil, err } @@ -118,7 +116,7 @@ func (p *postgresDeviceRepository) CreateOrUpdate(ctx context.Context, dev *doma UPDATE SET expires_at = $3, grace_period_expires_at = $4 RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, dev.APNSToken, @@ -139,7 +137,7 @@ func (p *postgresDeviceRepository) Create(ctx context.Context, dev *domain.Devic VALUES ($1, $2, $3, $4) RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, dev.APNSToken, @@ -159,7 +157,7 @@ func (p *postgresDeviceRepository) Update(ctx context.Context, dev *domain.Devic SET expires_at = $2, grace_period_expires_at = $3 WHERE id = $1` - res, err := p.pool.Exec(ctx, query, dev.ID, dev.ExpiresAt, dev.GracePeriodExpiresAt) + res, err := p.conn.Exec(ctx, query, dev.ID, dev.ExpiresAt, dev.GracePeriodExpiresAt) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -170,7 +168,7 @@ func (p *postgresDeviceRepository) Update(ctx context.Context, dev *domain.Devic func (p *postgresDeviceRepository) Delete(ctx context.Context, token string) error { query := `DELETE FROM devices WHERE apns_token = $1` - res, err := p.pool.Exec(ctx, query, token) + res, err := p.conn.Exec(ctx, query, token) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -187,7 +185,7 @@ func (p *postgresDeviceRepository) SetNotifiable(ctx context.Context, dev *domai global_mute = $3 WHERE device_id = $4 AND account_id = $5` - res, err := p.pool.Exec(ctx, query, inbox, watcher, global, dev.ID, acct.ID) + res, err := p.conn.Exec(ctx, query, inbox, watcher, global, dev.ID, acct.ID) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -203,7 +201,7 @@ func (p *postgresDeviceRepository) GetNotifiable(ctx context.Context, dev *domai WHERE device_id = $1 AND account_id = $2` var inbox, watcher, global bool - if err := p.pool.QueryRow(ctx, query, dev.ID, acct.ID).Scan(&inbox, &watcher, &global); err != nil { + if err := p.conn.QueryRow(ctx, query, dev.ID, acct.ID).Scan(&inbox, &watcher, &global); err != nil { return false, false, false, domain.ErrNotFound } @@ -213,7 +211,7 @@ func (p *postgresDeviceRepository) GetNotifiable(ctx context.Context, dev *domai func (p *postgresDeviceRepository) PruneStale(ctx context.Context, expiry time.Time) (int64, error) { query := `DELETE FROM devices WHERE grace_period_expires_at < $1` - res, err := p.pool.Exec(ctx, query, expiry) + res, err := p.conn.Exec(ctx, query, expiry) return res.RowsAffected(), err } diff --git a/internal/repository/postgres_device_test.go b/internal/repository/postgres_device_test.go new file mode 100644 index 0000000..b7378f9 --- /dev/null +++ b/internal/repository/postgres_device_test.go @@ -0,0 +1,135 @@ +package repository_test + +import ( + "context" + "crypto/rand" + "encoding/hex" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/christianselig/apollo-backend/internal/domain" + "github.com/christianselig/apollo-backend/internal/repository" + "github.com/christianselig/apollo-backend/internal/testhelper" +) + +const testToken = "313a182b63224821f5595f42aa019de850a0e7b776253659a9aac8140bb8a3f2" + +func NewTestPostgresDevice(t *testing.T) domain.DeviceRepository { + t.Helper() + + ctx := context.Background() + conn := testhelper.NewTestPgxConn(t) + + tx, err := conn.Begin(ctx) + require.NoError(t, err) + + repo := repository.NewPostgresDevice(tx) + + t.Cleanup(func() { + _ = tx.Rollback(ctx) + }) + + return repo +} + +func TestPostgresDevice_GetByID(t *testing.T) { + t.Parallel() + + ctx := context.Background() + repo := NewTestPostgresDevice(t) + + dev := &domain.Device{APNSToken: testToken} + require.NoError(t, repo.CreateOrUpdate(ctx, dev)) + + testCases := map[string]struct { + id int64 + want *domain.Device + err error + }{ + "valid ID": {dev.ID, dev, nil}, + "invalid ID": {0, nil, domain.ErrNotFound}, + } + + for scenario, tc := range testCases { //nolint:paralleltest + t.Run(scenario, func(t *testing.T) { + dev, err := repo.GetByID(ctx, tc.id) + if tc.err != nil { + require.Error(t, err) + assert.Equal(t, tc.err, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tc.want, &dev) + }) + } +} + +func TestPostgresDevice_Create(t *testing.T) { + t.Parallel() + + ctx := context.Background() + repo := NewTestPostgresDevice(t) + + testCases := map[string]struct { + have *domain.Device + err bool + }{ + "valid": {&domain.Device{APNSToken: testToken}, false}, + "invalid APNS token": {&domain.Device{APNSToken: "not valid"}, true}, + } + + for scenario, tc := range testCases { //nolint:paralleltest + t.Run(scenario, func(t *testing.T) { + err := repo.Create(ctx, tc.have) + + if tc.err { + assert.Error(t, err) + return + } + + assert.NotEqual(t, 0, tc.have.ID) + }) + } +} + +func TestPostgresDevice_Update(t *testing.T) { + t.Parallel() + + ctx := context.Background() + repo := NewTestPostgresDevice(t) + + testCases := map[string]struct { + fn func(*domain.Device) + err error + }{ + "valid update": {func(dev *domain.Device) { dev.Sandbox = true }, nil}, + "empty update": {func(dev *domain.Device) {}, nil}, + "update on non existant id": {func(dev *domain.Device) { dev.ID = 0 }, errors.New("weird behaviour, total rows affected: 0")}, + } + + for scenario, tc := range testCases { //nolint:paralleltest + t.Run(scenario, func(t *testing.T) { + b := make([]byte, 32) + _, err := rand.Read(b) + require.NoError(t, err) + + dev := &domain.Device{APNSToken: hex.EncodeToString(b)} + require.NoError(t, repo.Create(ctx, dev)) + + tc.fn(dev) + + err = repo.Update(ctx, dev) + if tc.err != nil { + require.Error(t, err) + assert.Equal(t, tc.err, err) + return + } + + require.NoError(t, err) + }) + } +} diff --git a/internal/repository/postgres_subreddit.go b/internal/repository/postgres_subreddit.go index 5336593..22db547 100644 --- a/internal/repository/postgres_subreddit.go +++ b/internal/repository/postgres_subreddit.go @@ -5,19 +5,18 @@ import ( "strings" "github.com/christianselig/apollo-backend/internal/domain" - "github.com/jackc/pgx/v4/pgxpool" ) type postgresSubredditRepository struct { - pool *pgxpool.Pool + conn Connection } -func NewPostgresSubreddit(pool *pgxpool.Pool) domain.SubredditRepository { - return &postgresSubredditRepository{pool: pool} +func NewPostgresSubreddit(conn Connection) domain.SubredditRepository { + return &postgresSubredditRepository{conn: conn} } func (p *postgresSubredditRepository) fetch(ctx context.Context, query string, args ...interface{}) ([]domain.Subreddit, error) { - rows, err := p.pool.Query(ctx, query, args...) + rows, err := p.conn.Query(ctx, query, args...) if err != nil { return nil, err } @@ -86,7 +85,7 @@ func (p *postgresSubredditRepository) CreateOrUpdate(ctx context.Context, sr *do ON CONFLICT(subreddit_id) DO NOTHING RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, sr.SubredditID, diff --git a/internal/repository/postgres_user.go b/internal/repository/postgres_user.go index 0636352..946f9e7 100644 --- a/internal/repository/postgres_user.go +++ b/internal/repository/postgres_user.go @@ -5,21 +5,19 @@ import ( "fmt" "strings" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/christianselig/apollo-backend/internal/domain" ) type postgresUserRepository struct { - pool *pgxpool.Pool + conn Connection } -func NewPostgresUser(pool *pgxpool.Pool) domain.UserRepository { - return &postgresUserRepository{pool: pool} +func NewPostgresUser(conn Connection) domain.UserRepository { + return &postgresUserRepository{conn: conn} } func (p *postgresUserRepository) fetch(ctx context.Context, query string, args ...interface{}) ([]domain.User, error) { - rows, err := p.pool.Query(ctx, query, args...) + rows, err := p.conn.Query(ctx, query, args...) if err != nil { return nil, err } @@ -88,7 +86,7 @@ func (p *postgresUserRepository) CreateOrUpdate(ctx context.Context, u *domain.U ON CONFLICT(user_id) DO NOTHING RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, u.UserID, @@ -99,7 +97,7 @@ func (p *postgresUserRepository) CreateOrUpdate(ctx context.Context, u *domain.U func (p *postgresUserRepository) Delete(ctx context.Context, id int64) error { query := `DELETE FROM users WHERE id = $1` - res, err := p.pool.Exec(ctx, query, id) + res, err := p.conn.Exec(ctx, query, id) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) diff --git a/internal/repository/postgres_watcher.go b/internal/repository/postgres_watcher.go index 25a96f6..c165663 100644 --- a/internal/repository/postgres_watcher.go +++ b/internal/repository/postgres_watcher.go @@ -5,21 +5,19 @@ import ( "fmt" "time" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/christianselig/apollo-backend/internal/domain" ) type postgresWatcherRepository struct { - pool *pgxpool.Pool + conn Connection } -func NewPostgresWatcher(pool *pgxpool.Pool) domain.WatcherRepository { - return &postgresWatcherRepository{pool: pool} +func NewPostgresWatcher(conn Connection) domain.WatcherRepository { + return &postgresWatcherRepository{conn: conn} } func (p *postgresWatcherRepository) fetch(ctx context.Context, query string, args ...interface{}) ([]domain.Watcher, error) { - rows, err := p.pool.Query(ctx, query, args...) + rows, err := p.conn.Query(ctx, query, args...) if err != nil { return nil, err } @@ -221,7 +219,7 @@ func (p *postgresWatcherRepository) Create(ctx context.Context, watcher *domain. VALUES ($1, 0, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id` - return p.pool.QueryRow( + return p.conn.QueryRow( ctx, query, now, @@ -255,7 +253,7 @@ func (p *postgresWatcherRepository) Update(ctx context.Context, watcher *domain. label = $8 WHERE id = $1` - res, err := p.pool.Exec( + res, err := p.conn.Exec( ctx, query, watcher.ID, @@ -277,7 +275,7 @@ func (p *postgresWatcherRepository) Update(ctx context.Context, watcher *domain. func (p *postgresWatcherRepository) IncrementHits(ctx context.Context, id int64) error { now := time.Now().Unix() query := `UPDATE watchers SET hits = hits + 1, last_notified_at = $2 WHERE id = $1` - res, err := p.pool.Exec(ctx, query, id, now) + res, err := p.conn.Exec(ctx, query, id, now) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -287,7 +285,7 @@ func (p *postgresWatcherRepository) IncrementHits(ctx context.Context, id int64) func (p *postgresWatcherRepository) Delete(ctx context.Context, id int64) error { query := `DELETE FROM watchers WHERE id = $1` - res, err := p.pool.Exec(ctx, query, id) + res, err := p.conn.Exec(ctx, query, id) if res.RowsAffected() != 1 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) @@ -297,7 +295,7 @@ func (p *postgresWatcherRepository) Delete(ctx context.Context, id int64) error func (p *postgresWatcherRepository) DeleteByTypeAndWatcheeID(ctx context.Context, typ domain.WatcherType, id int64) error { query := `DELETE FROM watchers WHERE type = $1 AND watchee_id = $2` - res, err := p.pool.Exec(ctx, query, typ, id) + res, err := p.conn.Exec(ctx, query, typ, id) if res.RowsAffected() == 0 { return fmt.Errorf("weird behaviour, total rows affected: %d", res.RowsAffected()) diff --git a/internal/repository/postgres_watcher_test.go b/internal/repository/postgres_watcher_test.go new file mode 100644 index 0000000..0a7c33e --- /dev/null +++ b/internal/repository/postgres_watcher_test.go @@ -0,0 +1,33 @@ +package repository_test + +import ( + "context" + "testing" + + "github.com/christianselig/apollo-backend/internal/domain" + "github.com/christianselig/apollo-backend/internal/repository" + "github.com/christianselig/apollo-backend/internal/testhelper" + "github.com/stretchr/testify/require" +) + +func NewTestPostgresWatcher(t *testing.T) domain.WatcherRepository { + t.Helper() + + ctx := context.Background() + conn := testhelper.NewTestPgxConn(t) + + tx, err := conn.Begin(ctx) + require.NoError(t, err) + + repo := repository.NewPostgresWatcher(tx) + + t.Cleanup(func() { + _ = tx.Rollback(ctx) + }) + + return repo +} + +func TestPostgresWatcher_GetByID(t *testing.T) { + t.Parallel() +} diff --git a/internal/testhelper/pgxpool.go b/internal/testhelper/pgxpool.go new file mode 100644 index 0000000..cb3c57d --- /dev/null +++ b/internal/testhelper/pgxpool.go @@ -0,0 +1,34 @@ +package testhelper + +import ( + "context" + "os" + "testing" + + "github.com/jackc/pgx/v4" + "github.com/stretchr/testify/require" +) + +func NewTestPgxConn(t *testing.T) *pgx.Conn { + t.Helper() + + ctx := context.Background() + + connString := os.Getenv("DATABASE_URL") + + if connString == "" { + t.Skipf("skipping due to missing environment variable %v", "DATABASE_URL") + } + + config, err := pgx.ParseConfig(connString) + require.NoError(t, err) + + conn, err := pgx.ConnectConfig(ctx, config) + require.NoError(t, err) + + t.Cleanup(func() { + conn.Close(ctx) + }) + + return conn +} diff --git a/internal/worker/notifications.go b/internal/worker/notifications.go index d20a407..0c0fe2e 100644 --- a/internal/worker/notifications.go +++ b/internal/worker/notifications.go @@ -33,6 +33,8 @@ const ( ) type notificationsWorker struct { + context.Context + logger *logrus.Logger statsd *statsd.Client db *pgxpool.Pool @@ -47,7 +49,7 @@ type notificationsWorker struct { deviceRepo domain.DeviceRepository } -func NewNotificationsWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { +func NewNotificationsWorker(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { reddit := reddit.NewClient( os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET"), @@ -71,6 +73,7 @@ func NewNotificationsWorker(logger *logrus.Logger, statsd *statsd.Client, db *pg } return ¬ificationsWorker{ + ctx, logger, statsd, db, @@ -137,11 +140,9 @@ func NewNotificationsConsumer(nw *notificationsWorker, tag int) *notificationsCo } func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { - ctx := context.Background() - defer func() { lockKey := fmt.Sprintf("locks:accounts:%s", delivery.Payload()) - if err := nc.redis.Del(ctx, lockKey).Err(); err != nil { + if err := nc.redis.Del(nc, lockKey).Err(); err != nil { nc.logger.WithFields(logrus.Fields{ "lockKey": lockKey, "err": err, @@ -168,7 +169,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { now := time.Now() - account, err := nc.accountRepo.GetByID(ctx, id) + account, err := nc.accountRepo.GetByID(nc, id) if err != nil { nc.logger.WithFields(logrus.Fields{ "account#id": id, @@ -183,7 +184,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { account.CheckCount++ account.NextNotificationCheckAt = time.Now().Add(domain.NotificationCheckInterval) - if err = nc.accountRepo.Update(ctx, &account); err != nil { + if err = nc.accountRepo.Update(nc, &account); err != nil { nc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), "err": err, @@ -197,7 +198,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { "account#username": account.NormalizedUsername(), }).Debug("refreshing reddit token") - tokens, err := rac.RefreshTokens() + tokens, err := rac.RefreshTokens(nc) if err != nil { if err != reddit.ErrOauthRevoked { nc.logger.WithFields(logrus.Fields{ @@ -207,7 +208,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { return } - err = nc.deleteAccount(ctx, account) + err = nc.deleteAccount(account) if err != nil { nc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), @@ -226,7 +227,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { // Refresh client rac = nc.reddit.NewAuthenticatedClient(account.AccountID, tokens.RefreshToken, tokens.AccessToken) - if err = nc.accountRepo.Update(ctx, &account); err != nil { + if err = nc.accountRepo.Update(nc, &account); err != nil { nc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), "err": err, @@ -250,14 +251,14 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { if account.LastMessageID != "" { opts = append(opts, reddit.WithQuery("before", account.LastMessageID)) } - msgs, err := rac.MessageInbox(opts...) + msgs, err := rac.MessageInbox(nc, opts...) if err != nil { switch err { case reddit.ErrTimeout: // Don't log timeouts break case reddit.ErrOauthRevoked: - err = nc.deleteAccount(ctx, account) + err = nc.deleteAccount(account) if err != nil { nc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), @@ -297,7 +298,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { } } - if err = nc.accountRepo.Update(ctx, &account); err != nil { + if err = nc.accountRepo.Update(nc, &account); err != nil { nc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), "err": err, @@ -313,7 +314,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { return } - devices, err := nc.deviceRepo.GetInboxNotifiableByAccountID(ctx, account.ID) + devices, err := nc.deviceRepo.GetInboxNotifiableByAccountID(nc, account.ID) if err != nil { nc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), @@ -359,7 +360,7 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { }).Error("failed to send notification") // Delete device as notifications might have been disabled here - _ = nc.deviceRepo.Delete(ctx, device.APNSToken) + _ = nc.deviceRepo.Delete(nc, device.APNSToken) } else { _ = nc.statsd.Incr("apns.notification.sent", []string{}, 1) nc.logger.WithFields(logrus.Fields{ @@ -378,20 +379,20 @@ func (nc *notificationsConsumer) Consume(delivery rmq.Delivery) { }).Debug("finishing job") } -func (nc *notificationsConsumer) deleteAccount(ctx context.Context, account domain.Account) error { +func (nc *notificationsConsumer) deleteAccount(account domain.Account) error { // Disassociate account from devices - devs, err := nc.deviceRepo.GetByAccountID(ctx, account.ID) + devs, err := nc.deviceRepo.GetByAccountID(nc, account.ID) if err != nil { return err } for _, dev := range devs { - if err := nc.accountRepo.Disassociate(ctx, &account, &dev); err != nil { + if err := nc.accountRepo.Disassociate(nc, &account, &dev); err != nil { return err } } - return nc.accountRepo.Delete(ctx, account.ID) + return nc.accountRepo.Delete(nc, account.ID) } func payloadFromMessage(acct domain.Account, msg *reddit.Thing, badgeCount int) *payload.Payload { diff --git a/internal/worker/stuck_notifications.go b/internal/worker/stuck_notifications.go index d87f190..7a82d1a 100644 --- a/internal/worker/stuck_notifications.go +++ b/internal/worker/stuck_notifications.go @@ -18,6 +18,8 @@ import ( ) type stuckNotificationsWorker struct { + context.Context + logger *logrus.Logger statsd *statsd.Client db *pgxpool.Pool @@ -30,7 +32,7 @@ type stuckNotificationsWorker struct { accountRepo domain.AccountRepository } -func NewStuckNotificationsWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { +func NewStuckNotificationsWorker(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { reddit := reddit.NewClient( os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET"), @@ -40,6 +42,7 @@ func NewStuckNotificationsWorker(logger *logrus.Logger, statsd *statsd.Client, d ) return &stuckNotificationsWorker{ + ctx, logger, statsd, db, @@ -99,8 +102,6 @@ func NewStuckNotificationsConsumer(snw *stuckNotificationsWorker, tag int) *stuc } func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) { - ctx := context.Background() - snc.logger.WithFields(logrus.Fields{ "account#id": delivery.Payload(), }).Debug("starting job") @@ -118,7 +119,7 @@ func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) { defer func() { _ = delivery.Ack() }() - account, err := snc.accountRepo.GetByID(ctx, id) + account, err := snc.accountRepo.GetByID(snc, id) if err != nil { snc.logger.WithFields(logrus.Fields{ "err": err, @@ -149,7 +150,7 @@ func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) { "thing#id": account.LastMessageID, }).Debug("checking last thing via inbox") - things, err = rac.MessageInbox() + things, err = rac.MessageInbox(snc) if err != nil { snc.logger.WithFields(logrus.Fields{ "err": err, @@ -157,7 +158,7 @@ func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) { return } } else { - things, err = rac.AboutInfo(account.LastMessageID) + things, err = rac.AboutInfo(snc, account.LastMessageID) if err != nil { snc.logger.WithFields(logrus.Fields{ "err": err, @@ -192,7 +193,7 @@ func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) { "account#username": account.NormalizedUsername(), }).Debug("getting message inbox to determine last good thing") - things, err = rac.MessageInbox() + things, err = rac.MessageInbox(snc) if err != nil { snc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), @@ -225,7 +226,7 @@ func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) { "thing#id": account.LastMessageID, }).Debug("updating last good thing") - if err := snc.accountRepo.Update(ctx, &account); err != nil { + if err := snc.accountRepo.Update(snc, &account); err != nil { snc.logger.WithFields(logrus.Fields{ "account#username": account.NormalizedUsername(), "err": err, diff --git a/internal/worker/subreddits.go b/internal/worker/subreddits.go index 3b381fe..bc015cc 100644 --- a/internal/worker/subreddits.go +++ b/internal/worker/subreddits.go @@ -24,6 +24,8 @@ import ( ) type subredditsWorker struct { + context.Context + logger *logrus.Logger statsd *statsd.Client db *pgxpool.Pool @@ -45,7 +47,7 @@ const ( subredditNotificationBodyFormat = "r/%s: \u201c%s\u201d" ) -func NewSubredditsWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { +func NewSubredditsWorker(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { reddit := reddit.NewClient( os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET"), @@ -69,6 +71,7 @@ func NewSubredditsWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpo } return &subredditsWorker{ + ctx, logger, statsd, db, @@ -137,8 +140,6 @@ func NewSubredditsConsumer(sw *subredditsWorker, tag int) *subredditsConsumer { } func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { - ctx := context.Background() - sc.logger.WithFields(logrus.Fields{ "subreddit#id": delivery.Payload(), }).Debug("starting job") @@ -156,7 +157,7 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { defer func() { _ = delivery.Ack() }() - subreddit, err := sc.subredditRepo.GetByID(ctx, id) + subreddit, err := sc.subredditRepo.GetByID(sc, id) if err != nil { sc.logger.WithFields(logrus.Fields{ "err": err, @@ -164,7 +165,7 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { return } - watchers, err := sc.watcherRepo.GetBySubredditID(ctx, subreddit.ID) + watchers, err := sc.watcherRepo.GetBySubredditID(sc, subreddit.ID) if err != nil { sc.logger.WithFields(logrus.Fields{ "subreddit#id": subreddit.ID, @@ -202,10 +203,10 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { i := rand.Intn(len(watchers)) watcher := watchers[i] - acc, _ := sc.accountRepo.GetByID(ctx, watcher.AccountID) + acc, _ := sc.accountRepo.GetByID(sc, watcher.AccountID) rac := sc.reddit.NewAuthenticatedClient(acc.AccountID, acc.RefreshToken, acc.AccessToken) - sps, err := rac.SubredditNew( + sps, err := rac.SubredditNew(sc, subreddit.Name, reddit.WithQuery("before", before), reddit.WithQuery("limit", "100"), @@ -267,9 +268,9 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { i := rand.Intn(len(watchers)) watcher := watchers[i] - acc, _ := sc.accountRepo.GetByID(ctx, watcher.AccountID) + acc, _ := sc.accountRepo.GetByID(sc, watcher.AccountID) rac := sc.reddit.NewAuthenticatedClient(acc.AccountID, acc.RefreshToken, acc.AccessToken) - sps, err := rac.SubredditHot( + sps, err := rac.SubredditHot(sc, subreddit.Name, reddit.WithQuery("limit", "100"), ) @@ -344,7 +345,7 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { } lockKey := fmt.Sprintf("watcher:%d:%s", watcher.DeviceID, post.ID) - notified, _ := sc.redis.Get(ctx, lockKey).Bool() + notified, _ := sc.redis.Get(sc, lockKey).Bool() if notified { sc.logger.WithFields(logrus.Fields{ @@ -357,7 +358,7 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { continue } - if err := sc.watcherRepo.IncrementHits(ctx, watcher.ID); err != nil { + if err := sc.watcherRepo.IncrementHits(sc, watcher.ID); err != nil { sc.logger.WithFields(logrus.Fields{ "subreddit#id": subreddit.ID, "watcher#id": watcher.ID, @@ -373,7 +374,7 @@ func (sc *subredditsConsumer) Consume(delivery rmq.Delivery) { "post#id": post.ID, }).Debug("got a hit") - sc.redis.SetEX(ctx, lockKey, true, 24*time.Hour) + sc.redis.SetEX(sc, lockKey, true, 24*time.Hour) notifs = append(notifs, watcher) } diff --git a/internal/worker/trending.go b/internal/worker/trending.go index 15ffc66..939b9e0 100644 --- a/internal/worker/trending.go +++ b/internal/worker/trending.go @@ -23,6 +23,8 @@ import ( ) type trendingWorker struct { + context.Context + logger *logrus.Logger statsd *statsd.Client redis *redis.Client @@ -40,7 +42,7 @@ type trendingWorker struct { const trendingNotificationTitleFormat = "🔥 r/%s Trending" -func NewTrendingWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { +func NewTrendingWorker(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { reddit := reddit.NewClient( os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET"), @@ -64,6 +66,7 @@ func NewTrendingWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool } return &trendingWorker{ + ctx, logger, statsd, redis, @@ -131,8 +134,6 @@ func NewTrendingConsumer(tw *trendingWorker, tag int) *trendingConsumer { } func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { - ctx := context.Background() - tc.logger.WithFields(logrus.Fields{ "subreddit#id": delivery.Payload(), }).Debug("starting job") @@ -150,7 +151,7 @@ func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { defer func() { _ = delivery.Ack() }() - subreddit, err := tc.subredditRepo.GetByID(ctx, id) + subreddit, err := tc.subredditRepo.GetByID(tc, id) if err != nil { tc.logger.WithFields(logrus.Fields{ "err": err, @@ -158,7 +159,7 @@ func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { return } - watchers, err := tc.watcherRepo.GetByTrendingSubredditID(ctx, subreddit.ID) + watchers, err := tc.watcherRepo.GetByTrendingSubredditID(tc, subreddit.ID) if err != nil { tc.logger.WithFields(logrus.Fields{ "subreddit#id": subreddit.ID, @@ -179,7 +180,7 @@ func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { watcher := watchers[i] rac := tc.reddit.NewAuthenticatedClient(watcher.Account.AccountID, watcher.Account.RefreshToken, watcher.Account.AccessToken) - tps, err := rac.SubredditTop(subreddit.Name, reddit.WithQuery("t", "week")) + tps, err := rac.SubredditTop(tc, subreddit.Name, reddit.WithQuery("t", "week")) if err != nil { tc.logger.WithFields(logrus.Fields{ "subreddit#id": subreddit.ID, @@ -219,7 +220,7 @@ func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { i = rand.Intn(len(watchers)) watcher = watchers[i] rac = tc.reddit.NewAuthenticatedClient(watcher.Account.AccountID, watcher.Account.RefreshToken, watcher.Account.AccessToken) - hps, err := rac.SubredditHot(subreddit.Name) + hps, err := rac.SubredditHot(tc, subreddit.Name) if err != nil { tc.logger.WithFields(logrus.Fields{ "subreddit#id": subreddit.ID, @@ -256,7 +257,7 @@ func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { } lockKey := fmt.Sprintf("watcher:trending:%d:%s", watcher.DeviceID, post.ID) - notified, _ := tc.redis.Get(ctx, lockKey).Bool() + notified, _ := tc.redis.Get(tc, lockKey).Bool() if notified { tc.logger.WithFields(logrus.Fields{ @@ -268,9 +269,9 @@ func (tc *trendingConsumer) Consume(delivery rmq.Delivery) { continue } - tc.redis.SetEX(ctx, lockKey, true, 48*time.Hour) + tc.redis.SetEX(tc, lockKey, true, 48*time.Hour) - if err := tc.watcherRepo.IncrementHits(ctx, watcher.ID); err != nil { + if err := tc.watcherRepo.IncrementHits(tc, watcher.ID); err != nil { tc.logger.WithFields(logrus.Fields{ "subreddit#id": subreddit.ID, "watcher#id": watcher.ID, diff --git a/internal/worker/users.go b/internal/worker/users.go index 567bf40..52e228e 100644 --- a/internal/worker/users.go +++ b/internal/worker/users.go @@ -23,6 +23,8 @@ import ( ) type usersWorker struct { + context.Context + logger *logrus.Logger statsd *statsd.Client db *pgxpool.Pool @@ -41,7 +43,7 @@ type usersWorker struct { const userNotificationTitleFormat = "👨\u200d🚀 %s" -func NewUsersWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { +func NewUsersWorker(ctx context.Context, logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker { reddit := reddit.NewClient( os.Getenv("REDDIT_CLIENT_ID"), os.Getenv("REDDIT_CLIENT_SECRET"), @@ -65,6 +67,7 @@ func NewUsersWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Po } return &usersWorker{ + ctx, logger, statsd, db, @@ -133,8 +136,6 @@ func NewUsersConsumer(uw *usersWorker, tag int) *usersConsumer { } func (uc *usersConsumer) Consume(delivery rmq.Delivery) { - ctx := context.Background() - uc.logger.WithFields(logrus.Fields{ "user#id": delivery.Payload(), }).Debug("starting job") @@ -152,7 +153,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { defer func() { _ = delivery.Ack() }() - user, err := uc.userRepo.GetByID(ctx, id) + user, err := uc.userRepo.GetByID(uc, id) if err != nil { uc.logger.WithFields(logrus.Fields{ "err": err, @@ -160,7 +161,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { return } - watchers, err := uc.watcherRepo.GetByUserID(ctx, user.ID) + watchers, err := uc.watcherRepo.GetByUserID(uc, user.ID) if err != nil { uc.logger.WithFields(logrus.Fields{ "user#id": user.ID, @@ -180,10 +181,10 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { i := rand.Intn(len(watchers)) watcher := watchers[i] - acc, _ := uc.accountRepo.GetByID(ctx, watcher.AccountID) + acc, _ := uc.accountRepo.GetByID(uc, watcher.AccountID) rac := uc.reddit.NewAuthenticatedClient(acc.AccountID, acc.RefreshToken, acc.AccessToken) - ru, err := rac.UserAbout(user.Name) + ru, err := rac.UserAbout(uc, user.Name) if err != nil { uc.logger.WithFields(logrus.Fields{ "user#id": user.ID, @@ -197,7 +198,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { "user#id": user.ID, }).Info("user disabled followers, removing") - if err := uc.watcherRepo.DeleteByTypeAndWatcheeID(ctx, domain.UserWatcher, user.ID); err != nil { + if err := uc.watcherRepo.DeleteByTypeAndWatcheeID(uc, domain.UserWatcher, user.ID); err != nil { uc.logger.WithFields(logrus.Fields{ "user#id": user.ID, "err": err, @@ -205,7 +206,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { return } - if err := uc.userRepo.Delete(ctx, user.ID); err != nil { + if err := uc.userRepo.Delete(uc, user.ID); err != nil { uc.logger.WithFields(logrus.Fields{ "user#id": user.ID, "err": err, @@ -214,7 +215,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { } } - posts, err := rac.UserPosts(user.Name) + posts, err := rac.UserPosts(uc, user.Name) if err != nil { uc.logger.WithFields(logrus.Fields{ "user#id": user.ID, @@ -259,7 +260,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { notification.Topic = "com.christianselig.Apollo" for _, watcher := range notifs { - if err := uc.watcherRepo.IncrementHits(ctx, watcher.ID); err != nil { + if err := uc.watcherRepo.IncrementHits(uc, watcher.ID); err != nil { uc.logger.WithFields(logrus.Fields{ "user#id": user.ID, "watcher#id": watcher.ID, @@ -268,7 +269,7 @@ func (uc *usersConsumer) Consume(delivery rmq.Delivery) { return } - device, _ := uc.deviceRepo.GetByID(ctx, watcher.DeviceID) + device, _ := uc.deviceRepo.GetByID(uc, watcher.DeviceID) title := fmt.Sprintf(userNotificationTitleFormat, watcher.Label) payload.AlertTitle(title) diff --git a/internal/worker/worker.go b/internal/worker/worker.go index 6d89763..d5ecd52 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -1,6 +1,8 @@ package worker import ( + "context" + "github.com/DataDog/datadog-go/statsd" "github.com/adjust/rmq/v4" "github.com/go-redis/redis/v8" @@ -8,7 +10,7 @@ import ( "github.com/sirupsen/logrus" ) -type NewWorkerFn func(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker +type NewWorkerFn func(context.Context, *logrus.Logger, *statsd.Client, *pgxpool.Pool, *redis.Client, rmq.Connection, int) Worker type Worker interface { Start() error Stop() diff --git a/migrations/20210509200230_create_devices_accounts.down.sql b/migrations/000001_create_accounts.down.sql similarity index 100% rename from migrations/20210509200230_create_devices_accounts.down.sql rename to migrations/000001_create_accounts.down.sql diff --git a/migrations/000001_create_accounts.up.sql b/migrations/000001_create_accounts.up.sql new file mode 100644 index 0000000..edac32c --- /dev/null +++ b/migrations/000001_create_accounts.up.sql @@ -0,0 +1,20 @@ +-- Table Definition ---------------------------------------------- + +CREATE TABLE accounts ( + id SERIAL PRIMARY KEY, + username character varying(20) DEFAULT ''::character varying UNIQUE, + access_token character varying(64) DEFAULT ''::character varying, + refresh_token character varying(64) DEFAULT ''::character varying, + expires_at integer DEFAULT 0, + last_message_id character varying(32) DEFAULT ''::character varying, + device_count integer DEFAULT 0, + last_checked_at double precision DEFAULT '0'::double precision, + last_enqueued_at double precision DEFAULT '0'::double precision, + account_id character varying(32) DEFAULT ''::character varying, + last_unstuck_at double precision DEFAULT '0'::double precision +); + +-- Indices ------------------------------------------------------- + +CREATE INDEX accounts_last_checked_at_idx ON accounts(last_checked_at float8_ops); + diff --git a/migrations/000002_create_devices.down.sql b/migrations/000002_create_devices.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/000002_create_devices.up.sql b/migrations/000002_create_devices.up.sql new file mode 100644 index 0000000..099dfbf --- /dev/null +++ b/migrations/000002_create_devices.up.sql @@ -0,0 +1,10 @@ +-- Table Definition ---------------------------------------------- + +CREATE TABLE devices ( + id SERIAL PRIMARY KEY, + apns_token character varying(100) UNIQUE, + sandbox boolean, + active_until integer, + grace_period_until integer +); + diff --git a/migrations/000003_create_devices_accounts.down.sql b/migrations/000003_create_devices_accounts.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/000003_create_devices_accounts.up.sql b/migrations/000003_create_devices_accounts.up.sql new file mode 100644 index 0000000..440a9e4 --- /dev/null +++ b/migrations/000003_create_devices_accounts.up.sql @@ -0,0 +1,15 @@ +-- Table Definition ---------------------------------------------- + +CREATE TABLE devices_accounts ( + id SERIAL PRIMARY KEY, + account_id integer REFERENCES accounts(id) ON DELETE CASCADE, + device_id integer REFERENCES devices(id) ON DELETE CASCADE, + watcher_notifiable boolean DEFAULT true, + inbox_notifiable boolean DEFAULT true, + global_mute boolean DEFAULT false +); + +-- Indices ------------------------------------------------------- + +CREATE UNIQUE INDEX devices_accounts_account_id_device_id_idx ON devices_accounts(account_id int4_ops,device_id int4_ops); + diff --git a/migrations/000004_create_subreddits.down.sql b/migrations/000004_create_subreddits.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/000004_create_subreddits.up.sql b/migrations/000004_create_subreddits.up.sql new file mode 100644 index 0000000..15b76c6 --- /dev/null +++ b/migrations/000004_create_subreddits.up.sql @@ -0,0 +1,9 @@ +-- Table Definition ---------------------------------------------- + +CREATE TABLE subreddits ( + id SERIAL PRIMARY KEY, + subreddit_id character varying(32) DEFAULT ''::character varying UNIQUE, + name character varying(32) DEFAULT ''::character varying, + last_checked_at double precision DEFAULT '0'::double precision +); + diff --git a/migrations/000005_create_users.down.sql b/migrations/000005_create_users.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/000005_create_users.up.sql b/migrations/000005_create_users.up.sql new file mode 100644 index 0000000..7663437 --- /dev/null +++ b/migrations/000005_create_users.up.sql @@ -0,0 +1,9 @@ +-- Table Definition ---------------------------------------------- + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + user_id character varying(32) DEFAULT ''::character varying UNIQUE, + name character varying(32) DEFAULT ''::character varying, + last_checked_at double precision DEFAULT '0'::double precision +); + diff --git a/migrations/000006_create_watchers.down.sql b/migrations/000006_create_watchers.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/000006_create_watchers.up.sql b/migrations/000006_create_watchers.up.sql new file mode 100644 index 0000000..9859886 --- /dev/null +++ b/migrations/000006_create_watchers.up.sql @@ -0,0 +1,24 @@ +-- Table Definition ---------------------------------------------- + +CREATE TABLE watchers ( + id SERIAL PRIMARY KEY, + device_id integer REFERENCES devices(id) ON DELETE CASCADE, + watchee_id integer, + upvotes integer DEFAULT 0, + keyword character varying(32) DEFAULT ''::character varying, + flair character varying(32) DEFAULT ''::character varying, + domain character varying(32) DEFAULT ''::character varying, + account_id integer REFERENCES accounts(id) ON DELETE CASCADE, + created_at double precision DEFAULT '0'::double precision, + hits integer DEFAULT 0, + type integer DEFAULT 0, + last_notified_at double precision DEFAULT '0'::double precision, + label character varying(64) DEFAULT ''::character varying, + author character varying(32) DEFAULT ''::character varying, + subreddit character varying(32) DEFAULT ''::character varying +); + +-- Indices ------------------------------------------------------- + +CREATE INDEX watchers_type_watchee_id_idx ON watchers(type int4_ops,watchee_id int4_ops); + diff --git a/migrations/20210509150615_create_devices.down.sql b/migrations/20210509150615_create_devices.down.sql deleted file mode 100644 index 2af6915..0000000 --- a/migrations/20210509150615_create_devices.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE devices; diff --git a/migrations/20210509150615_create_devices.up.sql b/migrations/20210509150615_create_devices.up.sql deleted file mode 100644 index e78478c..0000000 --- a/migrations/20210509150615_create_devices.up.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE IF NOT EXISTS devices ( - id SERIAL PRIMARY KEY, - apns_token character(100) UNIQUE, - sandbox boolean, - last_pinged_at integer -); -CREATE UNIQUE INDEX IF NOT EXISTS devices_pkey ON devices(id int4_ops); -CREATE UNIQUE INDEX IF NOT EXISTS devices_apns_token_key ON devices(apns_token bpchar_ops); diff --git a/migrations/20210509191431_create_accounts.down.sql b/migrations/20210509191431_create_accounts.down.sql deleted file mode 100644 index 5eb8e05..0000000 --- a/migrations/20210509191431_create_accounts.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE accounts; diff --git a/migrations/20210509191431_create_accounts.up.sql b/migrations/20210509191431_create_accounts.up.sql deleted file mode 100644 index 795757a..0000000 --- a/migrations/20210509191431_create_accounts.up.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS accounts ( - id SERIAL PRIMARY KEY, - username character varying(20), - access_token character varying(64), - refresh_token character varying(64), - expires_at integer, - last_message_id character varying(32), - device_count integer, - last_checked_at integer -); - -CREATE UNIQUE INDEX IF NOT EXISTS accounts_pkey ON accounts(id int4_ops); -CREATE UNIQUE INDEX IF NOT EXISTS accounts_username_key ON accounts(username bpchar_ops); diff --git a/migrations/20210509200230_create_devices_accounts.up.sql b/migrations/20210509200230_create_devices_accounts.up.sql deleted file mode 100644 index 812f6ff..0000000 --- a/migrations/20210509200230_create_devices_accounts.up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS devices_accounts ( - id SERIAL PRIMARY KEY, - account_id integer, - device_id integer -); -CREATE UNIQUE INDEX IF NOT EXISTS devices_accounts_pkey ON devices_accounts(id int4_ops); -CREATE UNIQUE INDEX IF NOT EXISTS devices_accounts_account_id_device_id_idx ON devices_accounts(account_id int4_ops,device_id int4_ops); diff --git a/script/bootstrap b/script/bootstrap deleted file mode 100755 index 649fc5c..0000000 --- a/script/bootstrap +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -set -e - -cd "$(dirname "$0")/.." - -brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies..." - brew bundle -} - -[ -d "tmp/postgresql" ] || { - echo "===> Setting up database..." - initdb -D tmp/postgresql -U apollo - postgres -D tmp/postgresql & - - echo "===> Waiting for Postgres to finish starting up..." - while ! nc -z localhost 5432; do - sleep 0.1 # wait for 1/10 of the second before check again - done - - createdb apollo -U apollo - - script/migrate - - kill -INT `head -n1 tmp/postgresql/postmaster.pid` -} - -go mod verify >/dev/null 2>&1 || { - echo "==> Installing Go dependencies..." - go mod download -} diff --git a/script/migrate b/script/migrate deleted file mode 100755 index 67428c3..0000000 --- a/script/migrate +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -set -e - -cd "$(dirname "$0")/.." - -DATABASE_URL=postgres://apollo:@localhost/apollo?sslmode=disable - -echo "===> Running migrations..." -migrate -path=./migrations -database=$DATABASE_URL up diff --git a/script/server b/script/server deleted file mode 100755 index d63a124..0000000 --- a/script/server +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -cd "$(dirname "$0")/.." - -# ensure everything in the app is up to date. -script/bootstrap - -# boot the app and any other necessary processes. -foreman start