diff --git a/generated/store/models.go b/generated/store/models.go index 5238900..1582506 100644 --- a/generated/store/models.go +++ b/generated/store/models.go @@ -7,12 +7,9 @@ package store import ( "database/sql" "time" - - "github.com/google/uuid" ) type ElonTweet struct { - ID uuid.UUID TwitterID int64 Text string PostedAt time.Time @@ -22,8 +19,7 @@ type ElonTweet struct { } type User struct { - ID uuid.UUID - TwitterID string + TwitterID int64 Username string Name string AccessToken string diff --git a/generated/store/queries.sql.go b/generated/store/queries.sql.go index 8a7c8e1..da16b40 100644 --- a/generated/store/queries.sql.go +++ b/generated/store/queries.sql.go @@ -17,11 +17,11 @@ INSERT INTO users (twitter_id, username, name, access_token, refresh_token, crea ON CONFLICT (twitter_id) DO UPDATE SET access_token = EXCLUDED.access_token, refresh_token = EXCLUDED.refresh_token, username = EXCLUDED.username, name = EXCLUDED.name, updated_at = EXCLUDED.updated_at - RETURNING id, twitter_id, username, name, access_token, refresh_token, delete_tweets_enabled, delete_tweets_num_per_iteration, created_at, updated_at + RETURNING twitter_id, username, name, access_token, refresh_token, delete_tweets_enabled, delete_tweets_num_per_iteration, created_at, updated_at ` type CreateUserParams struct { - TwitterID string + TwitterID int64 Username string Name string AccessToken string @@ -42,7 +42,6 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e ) var i User err := row.Scan( - &i.ID, &i.TwitterID, &i.Username, &i.Name, @@ -57,14 +56,13 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e } const getLastElonTweet = `-- name: GetLastElonTweet :one -SELECT id, twitter_id, text, posted_at, processed_at, created_at, updated_at from elon_tweets ORDER BY twitter_id DESC LIMIT 1 +SELECT twitter_id, text, posted_at, processed_at, created_at, updated_at from elon_tweets ORDER BY twitter_id DESC LIMIT 1 ` func (q *Queries) GetLastElonTweet(ctx context.Context) (ElonTweet, error) { row := q.db.QueryRow(ctx, getLastElonTweet) var i ElonTweet err := row.Scan( - &i.ID, &i.TwitterID, &i.Text, &i.PostedAt, @@ -76,14 +74,13 @@ func (q *Queries) GetLastElonTweet(ctx context.Context) (ElonTweet, error) { } const getUserByTwitterID = `-- name: GetUserByTwitterID :one -SELECT id, twitter_id, username, name, access_token, refresh_token, delete_tweets_enabled, delete_tweets_num_per_iteration, created_at, updated_at FROM users WHERE twitter_id = $1 +SELECT twitter_id, username, name, access_token, refresh_token, delete_tweets_enabled, delete_tweets_num_per_iteration, created_at, updated_at FROM users WHERE twitter_id = $1 ` -func (q *Queries) GetUserByTwitterID(ctx context.Context, twitterID string) (User, error) { +func (q *Queries) GetUserByTwitterID(ctx context.Context, twitterID int64) (User, error) { row := q.db.QueryRow(ctx, getUserByTwitterID, twitterID) var i User err := row.Scan( - &i.ID, &i.TwitterID, &i.Username, &i.Name, @@ -101,7 +98,7 @@ const upsertElonTweet = `-- name: UpsertElonTweet :one INSERT INTO elon_tweets (twitter_id, text, posted_at, processed_at, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (twitter_id) DO NOTHING - RETURNING id, twitter_id, text, posted_at, processed_at, created_at, updated_at + RETURNING twitter_id, text, posted_at, processed_at, created_at, updated_at ` type UpsertElonTweetParams struct { @@ -120,7 +117,6 @@ func (q *Queries) UpsertElonTweet(ctx context.Context, arg UpsertElonTweetParams ) var i ElonTweet err := row.Scan( - &i.ID, &i.TwitterID, &i.Text, &i.PostedAt, diff --git a/go.mod b/go.mod index 9ea61cd..77dbc16 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.18 require ( github.com/go-chi/chi v1.5.4 github.com/gorilla/sessions v1.2.1 + github.com/jackc/pgconn v1.12.1 + github.com/jackc/pgx/v4 v4.16.1 github.com/stretchr/testify v1.7.1 go.uber.org/zap v1.21.0 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 @@ -14,16 +16,13 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.4.2 // indirect github.com/google/go-cmp v0.5.7 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.12.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/pgx/v4 v4.16.1 // indirect github.com/jackc/puddle v1.2.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.2.0 // indirect diff --git a/go.sum b/go.sum index 86fecfe..351a013 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P 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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -61,6 +62,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -108,8 +110,6 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -119,7 +119,6 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -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= @@ -136,10 +135,10 @@ github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 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= @@ -182,6 +181,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -199,10 +199,10 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= diff --git a/httpserver/handler.go b/httpserver/handler.go index f735f5a..c40c3a1 100644 --- a/httpserver/handler.go +++ b/httpserver/handler.go @@ -7,6 +7,7 @@ import ( "encoding/gob" "html/template" "net/http" + "strconv" "time" "git.netflux.io/rob/elon-eats-my-tweets/config" @@ -15,7 +16,6 @@ import ( "git.netflux.io/rob/elon-eats-my-tweets/twitterapi" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" - "github.com/google/uuid" "github.com/gorilla/sessions" "go.uber.org/zap" "golang.org/x/oauth2" @@ -26,12 +26,12 @@ func init() { } type CurrentUser struct { - ID uuid.UUID + TwitterID int64 TwitterName, TwitterUsername string } func (cu CurrentUser) IsNil() bool { - return cu.ID == uuid.Nil + return cu.TwitterID == 0 } type contextKey string @@ -194,8 +194,15 @@ func (h *handler) getCallback(w http.ResponseWriter, r *http.Request) { return } + twitterUserID, err := strconv.ParseInt(twitterUser.ID, 10, 64) + if err != nil { + h.logger.With("err", err).Error("error parsing Twitter user ID") + http.Error(w, "error validating user", http.StatusInternalServerError) + return + } + user, err := h.store.CreateUser(r.Context(), store.CreateUserParams{ - TwitterID: twitterUser.ID, + TwitterID: twitterUserID, Username: twitterUser.Username, Name: twitterUser.Name, AccessToken: token.AccessToken, @@ -212,7 +219,7 @@ func (h *handler) getCallback(w http.ResponseWriter, r *http.Request) { // finally, save in the session. To avoid hitting the database on most // requests we'll store the Twitter name and username in the session. session.Values[sessionKeyCurrentUser] = CurrentUser{ - ID: user.ID, + TwitterID: user.TwitterID, TwitterName: twitterUser.Name, TwitterUsername: twitterUser.Username, } diff --git a/httpserver/handler_test.go b/httpserver/handler_test.go index adbe350..493cf88 100644 --- a/httpserver/handler_test.go +++ b/httpserver/handler_test.go @@ -12,7 +12,6 @@ import ( "git.netflux.io/rob/elon-eats-my-tweets/generated/store" "git.netflux.io/rob/elon-eats-my-tweets/httpserver" "git.netflux.io/rob/elon-eats-my-tweets/twitterapi" - "github.com/google/uuid" "github.com/gorilla/sessions" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -48,7 +47,7 @@ func TestGetIndex(t *testing.T) { }, { name: "logged out", - currentUser: httpserver.CurrentUser{ID: uuid.New(), TwitterName: "foo", TwitterUsername: "Foo Bar"}, + currentUser: httpserver.CurrentUser{TwitterID: 1, TwitterName: "foo", TwitterUsername: "Foo Bar"}, wantBody: "Welcome to your dashboard", }, } @@ -272,7 +271,7 @@ func TestGetCallback(t *testing.T) { var mockStore mocks.Store mockStore.On("CreateUser", mock.Anything, mock.MatchedBy(func(params store.CreateUserParams) bool { - return params.TwitterID == "1" && params.Name == "foo" && params.Username == "Foo Bar" + return params.TwitterID == 1 && params.Name == "foo" && params.Username == "Foo Bar" })).Return(store.User{}, tc.createUserError) const callbackURL = "https://www.example.com/callback" diff --git a/httpserver/middleware.go b/httpserver/middleware.go index 217cac0..e4c5a94 100644 --- a/httpserver/middleware.go +++ b/httpserver/middleware.go @@ -15,12 +15,7 @@ func loggerMiddleware(l *zap.Logger, sessionStore sessions.Store) func(next http ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) t1 := time.Now() - cu, ok := getCurrentUser(r, sessionStore) - var userID string - if ok { - userID = cu.ID.String() - } - + cu, _ := getCurrentUser(r, sessionStore) defer func() { l.Info("HTTP", zap.String("proto", r.Proto), @@ -31,7 +26,7 @@ func loggerMiddleware(l *zap.Logger, sessionStore sessions.Store) func(next http zap.Int("size", ww.BytesWritten()), zap.String("ip", r.RemoteAddr), zap.String("reqId", middleware.GetReqID(r.Context())), - zap.String("userID", userID), + zap.Int64("userID", cu.TwitterID), zap.String("username", cu.TwitterUsername)) }() diff --git a/sql/migrations/20220525041635_refactor_data_model.down.sql b/sql/migrations/20220525041635_refactor_data_model.down.sql new file mode 100644 index 0000000..be7f412 --- /dev/null +++ b/sql/migrations/20220525041635_refactor_data_model.down.sql @@ -0,0 +1,8 @@ +ALTER TABLE elon_tweets DROP CONSTRAINT elon_tweets_pkey; +ALTER TABLE elon_tweets ADD COLUMN id uuid PRIMARY KEY DEFAULT gen_random_uuid(); +CREATE UNIQUE INDEX index_elon_tweets_on_twitter_id ON elon_tweets (twitter_id); +ALTER TABLE users DROP CONSTRAINT users_pkey; +ALTER TABLE users ADD COLUMN id uuid PRIMARY KEY DEFAULT gen_random_uuid(); +ALTER TABLE users ALTER COLUMN twitter_id TYPE CHARACTER VARYING(256); +ALTER TABLE users ALTER COLUMN twitter_id SET NOT NULL; +CREATE UNIQUE INDEX index_users_on_twitter_id ON users (twitter_id); diff --git a/sql/migrations/20220525041635_refactor_data_model.up.sql b/sql/migrations/20220525041635_refactor_data_model.up.sql new file mode 100644 index 0000000..658bf56 --- /dev/null +++ b/sql/migrations/20220525041635_refactor_data_model.up.sql @@ -0,0 +1,10 @@ +-- requires an empty db +DROP INDEX index_users_on_twitter_id; +ALTER TABLE users ALTER COLUMN twitter_id DROP DEFAULT; +ALTER TABLE users ALTER COLUMN twitter_id TYPE bigint USING (twitter_id::bigint); +ALTER TABLE users DROP COLUMN id; +ALTER TABLE users ADD PRIMARY KEY (twitter_id); +DROP INDEX index_elon_tweets_on_twitter_id; +ALTER TABLE elon_tweets DROP CONSTRAINT elon_tweets_pkey; +ALTER TABLE elon_tweets DROP COLUMN id; +ALTER TABLE elon_tweets ADD PRIMARY KEY (twitter_id);