From 2d22a3a582e6831cc38a06a8948de5799489e465 Mon Sep 17 00:00:00 2001 From: "ubuntu.hrishikesh" Date: Sun, 10 Dec 2023 18:50:13 +0000 Subject: [PATCH] Authentication middleware added. Added feed entity's schema, and handler. --- auth_middleware.go | 28 +++++++++ constants/constants.go | 2 + feed_handler.go | 77 +++++++++++++++++++++++++ internal/database/feeds.sql.go | 83 +++++++++++++++++++++++++++ internal/database/feeds_follow.sql.go | 46 +++++++++++++++ internal/database/models.go | 17 ++++++ main.go | 5 +- models.go | 46 +++++++++++++++ sql/queries/feeds.sql | 7 +++ sql/queries/feeds_follow.sql | 4 ++ sql/schema/003_feeds.sql | 12 ++++ sql/schema/004_feed_follows.sql | 12 ++++ user_handler.go | 16 +----- 13 files changed, 340 insertions(+), 15 deletions(-) create mode 100644 auth_middleware.go create mode 100644 feed_handler.go create mode 100644 internal/database/feeds.sql.go create mode 100644 internal/database/feeds_follow.sql.go create mode 100644 sql/queries/feeds.sql create mode 100644 sql/queries/feeds_follow.sql create mode 100644 sql/schema/003_feeds.sql create mode 100644 sql/schema/004_feed_follows.sql diff --git a/auth_middleware.go b/auth_middleware.go new file mode 100644 index 0000000..9b84b63 --- /dev/null +++ b/auth_middleware.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/itishrishikesh/gofeed/auth" + "github.com/itishrishikesh/gofeed/constants" + "github.com/itishrishikesh/gofeed/internal/database" +) + +type authHeader func(http.ResponseWriter, *http.Request, database.User) + +func (config *apiConfig) authMiddleware(handler authHeader) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + apiKey, err := auth.GetAPIKey(request.Header) + if err != nil { + respondWithError(writer, constants.HTTP_FORBIDDEN, fmt.Sprintf("E#1OV41H - Authentication Error: %v", err)) + return + } + user, err := config.DB.GetUserByAPIKey(request.Context(), apiKey) + if err != nil { + respondWithError(writer, constants.HTTP_BAD_REQUEST, fmt.Sprintf("E#1OV44M - Couldn't get user: %v", err)) + return + } + handler(writer, request, user) + } +} diff --git a/constants/constants.go b/constants/constants.go index 95af363..fb3b623 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -3,5 +3,7 @@ package constants const CONTENT_TYPE string = "Content-Type" const APPLICATION_JSON string = "application/json" const HTTP_SUCCESS int = 200 +const HTTP_CREATED int = 200 const HTTP_ERROR int = 500 const HTTP_BAD_REQUEST int = 400 +const HTTP_FORBIDDEN int = 400 diff --git a/feed_handler.go b/feed_handler.go new file mode 100644 index 0000000..ab66439 --- /dev/null +++ b/feed_handler.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + "github.com/google/uuid" + "github.com/itishrishikesh/gofeed/constants" + "github.com/itishrishikesh/gofeed/internal/database" +) + +func (apiCfg *apiConfig) createFeedHandler(writer http.ResponseWriter, request *http.Request, user database.User) { + type parameters struct { + Name string `json:"name"` + URL string `json:"url"` + } + params := parameters{} + decoder := json.NewDecoder(request.Body) + err := decoder.Decode(¶ms) + if err != nil { + respondWithError(writer, constants.HTTP_BAD_REQUEST, "Error parsing JSON") + log.Println("D#1OV4W5 - User passed incorrect JSON") + return + } + feed, err := apiCfg.DB.CreateFeed(request.Context(), database.CreateFeedParams{ + ID: uuid.New(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Name: params.Name, + Url: params.URL, + UserID: user.ID, + }) + if err != nil { + respondWithError(writer, constants.HTTP_BAD_REQUEST, fmt.Sprintf("E#1OV5DR - Couldn't create feed %v", err)) + return + } + + respondWithJSON(writer, constants.HTTP_CREATED, databaseFeedToFeed(feed)) +} + +func (config *apiConfig) getFeedsHandler(writer http.ResponseWriter, request *http.Request) { + feeds, err := config.DB.GetFeed(request.Context()) + if err != nil { + respondWithError(writer, constants.HTTP_BAD_REQUEST, fmt.Sprintf("E#1OV63D - Couldn't get feed %v", err)) + return + } + respondWithJSON(writer, constants.HTTP_CREATED, databaseFeedToFeeds(feeds)) +} + +func (config *apiConfig) createFeedFollowHandler(writer http.ResponseWriter, request *http.Request, user database.User) { + type parameters struct { + FeedID uuid.UUID `json:"feed_id"` + } + params := parameters{} + decoder := json.NewDecoder(request.Body) + err := decoder.Decode(¶ms) + if err != nil { + respondWithError(writer, constants.HTTP_BAD_REQUEST, "Error parsing JSON") + log.Println("D#1OV7DL - User passed incorrect JSON") + return + } + feedFollow, err := config.DB.CreateFeedFollow(request.Context(), database.CreateFeedFollowParams{ + ID: uuid.New(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + UserID: user.ID, + FeedID: params.FeedID, + }) + if err != nil { + respondWithError(writer, constants.HTTP_BAD_REQUEST, fmt.Sprintf("E#1OV7DV - Couldn't create feed follow %v", err)) + return + } + respondWithJSON(writer, constants.HTTP_CREATED, databaseFeedFollowToFeedFollow(feedFollow)) +} diff --git a/internal/database/feeds.sql.go b/internal/database/feeds.sql.go new file mode 100644 index 0000000..3743ab6 --- /dev/null +++ b/internal/database/feeds.sql.go @@ -0,0 +1,83 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.24.0 +// source: feeds.sql + +package database + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +const createFeed = `-- name: CreateFeed :one +INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, created_at, updated_at, name, url, user_id +` + +type CreateFeedParams struct { + ID uuid.UUID + CreatedAt time.Time + UpdatedAt time.Time + Name string + Url string + UserID uuid.UUID +} + +func (q *Queries) CreateFeed(ctx context.Context, arg CreateFeedParams) (Feed, error) { + row := q.db.QueryRowContext(ctx, createFeed, + arg.ID, + arg.CreatedAt, + arg.UpdatedAt, + arg.Name, + arg.Url, + arg.UserID, + ) + var i Feed + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.Name, + &i.Url, + &i.UserID, + ) + return i, err +} + +const getFeed = `-- name: GetFeed :many +SELECT id, created_at, updated_at, name, url, user_id from feeds +` + +func (q *Queries) GetFeed(ctx context.Context) ([]Feed, error) { + rows, err := q.db.QueryContext(ctx, getFeed) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Feed + for rows.Next() { + var i Feed + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.Name, + &i.Url, + &i.UserID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/database/feeds_follow.sql.go b/internal/database/feeds_follow.sql.go new file mode 100644 index 0000000..23f41b7 --- /dev/null +++ b/internal/database/feeds_follow.sql.go @@ -0,0 +1,46 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.24.0 +// source: feeds_follow.sql + +package database + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +const createFeedFollow = `-- name: CreateFeedFollow :one +INSERT INTO feed_follows (id, created_at, updated_at, user_id, feed_id) +VALUES ($1, $2, $3, $4, $5) +RETURNING id, created_at, updated_at, user_id, feed_id +` + +type CreateFeedFollowParams struct { + ID uuid.UUID + CreatedAt time.Time + UpdatedAt time.Time + UserID uuid.UUID + FeedID uuid.UUID +} + +func (q *Queries) CreateFeedFollow(ctx context.Context, arg CreateFeedFollowParams) (FeedFollow, error) { + row := q.db.QueryRowContext(ctx, createFeedFollow, + arg.ID, + arg.CreatedAt, + arg.UpdatedAt, + arg.UserID, + arg.FeedID, + ) + var i FeedFollow + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.UserID, + &i.FeedID, + ) + return i, err +} diff --git a/internal/database/models.go b/internal/database/models.go index 209f333..4874a2e 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -10,6 +10,23 @@ import ( "github.com/google/uuid" ) +type Feed struct { + ID uuid.UUID + CreatedAt time.Time + UpdatedAt time.Time + Name string + Url string + UserID uuid.UUID +} + +type FeedFollow struct { + ID uuid.UUID + CreatedAt time.Time + UpdatedAt time.Time + UserID uuid.UUID + FeedID uuid.UUID +} + type User struct { ID uuid.UUID CreatedAt time.Time diff --git a/main.go b/main.go index 7914b71..c931e53 100644 --- a/main.go +++ b/main.go @@ -60,7 +60,10 @@ func main() { v1Router.Get("/healthz", healthCheckHandler) v1Router.Get("/err", errorHandler) v1Router.Post("/users", apiCfg.createUserHandler) - v1Router.Get("/users", apiCfg.getUserHandler) + v1Router.Get("/users", apiCfg.authMiddleware(apiCfg.getUserHandler)) + v1Router.Post("/feeds", apiCfg.authMiddleware(apiCfg.createFeedHandler)) + v1Router.Get("/feeds", apiCfg.getFeedsHandler) + v1Router.Post("/feedfollow", apiCfg.authMiddleware(apiCfg.createFeedFollowHandler)) router.Mount("/v1", v1Router) server := &http.Server{ diff --git a/models.go b/models.go index 111bb04..f4c97e3 100644 --- a/models.go +++ b/models.go @@ -24,3 +24,49 @@ func databaseUserToUser(dbUser database.User) User { ApiKey: dbUser.ApiKey, } } + +type Feed struct { + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Name string `json:"name"` + Url string `json:"url"` + UserID uuid.UUID `json:"user_id"` +} + +func databaseFeedToFeed(dbFeed database.Feed) Feed { + return Feed{ + ID: dbFeed.ID, + CreatedAt: dbFeed.CreatedAt, + UpdatedAt: dbFeed.UpdatedAt, + Name: dbFeed.Name, + Url: dbFeed.Url, + UserID: dbFeed.UserID, + } +} + +func databaseFeedToFeeds(dbFeeds []database.Feed) []Feed { + feeds := []Feed{} + for _, dbFeed := range dbFeeds { + feeds = append(feeds, databaseFeedToFeed(dbFeed)) + } + return feeds +} + +type FeedFollow struct { + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + UserID uuid.UUID `json:"user_id"` + FeedID uuid.UUID `json:"feed_id"` +} + +func databaseFeedFollowToFeedFollow(dbFeedFollow database.FeedFollow) FeedFollow { + return FeedFollow{ + ID: dbFeedFollow.ID, + CreatedAt: dbFeedFollow.CreatedAt, + UpdatedAt: dbFeedFollow.UpdatedAt, + UserID: dbFeedFollow.UserID, + FeedID: dbFeedFollow.FeedID, + } +} diff --git a/sql/queries/feeds.sql b/sql/queries/feeds.sql new file mode 100644 index 0000000..67d7fae --- /dev/null +++ b/sql/queries/feeds.sql @@ -0,0 +1,7 @@ +-- name: CreateFeed :one +INSERT INTO feeds (id, created_at, updated_at, name, url, user_id) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING *; + +-- name: GetFeed :many +SELECT * from feeds; \ No newline at end of file diff --git a/sql/queries/feeds_follow.sql b/sql/queries/feeds_follow.sql new file mode 100644 index 0000000..06c5df0 --- /dev/null +++ b/sql/queries/feeds_follow.sql @@ -0,0 +1,4 @@ +-- name: CreateFeedFollow :one +INSERT INTO feed_follows (id, created_at, updated_at, user_id, feed_id) +VALUES ($1, $2, $3, $4, $5) +RETURNING *; \ No newline at end of file diff --git a/sql/schema/003_feeds.sql b/sql/schema/003_feeds.sql new file mode 100644 index 0000000..6b4ad99 --- /dev/null +++ b/sql/schema/003_feeds.sql @@ -0,0 +1,12 @@ +-- +goose Up +CREATE TABLE feeds ( + id UUID PRIMARY KEY, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + name TEXT NOT NULL, + url TEXT UNIQUE NOT NULL, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE +); + +-- +goose Down +DROP TABLE feeds; \ No newline at end of file diff --git a/sql/schema/004_feed_follows.sql b/sql/schema/004_feed_follows.sql new file mode 100644 index 0000000..45364ba --- /dev/null +++ b/sql/schema/004_feed_follows.sql @@ -0,0 +1,12 @@ +-- +goose Up +CREATE TABLE feed_follows ( + id UUID PRIMARY KEY, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + feed_id UUID NOT NULL REFERENCES feeds(id) ON DELETE CASCADE, + UNIQUE(user_id, feed_id) +); + +-- +goose Down +DROP TABLE feed_follows; \ No newline at end of file diff --git a/user_handler.go b/user_handler.go index bf26f4c..1751677 100644 --- a/user_handler.go +++ b/user_handler.go @@ -8,14 +8,13 @@ import ( "time" "github.com/google/uuid" - "github.com/itishrishikesh/gofeed/auth" "github.com/itishrishikesh/gofeed/constants" "github.com/itishrishikesh/gofeed/internal/database" ) func (apiCfg *apiConfig) createUserHandler(writer http.ResponseWriter, request *http.Request) { type parameters struct { - Name string `json:name` + Name string `json:"name"` } params := parameters{} @@ -42,17 +41,6 @@ func (apiCfg *apiConfig) createUserHandler(writer http.ResponseWriter, request * respondWithJSON(writer, constants.HTTP_SUCCESS, databaseUserToUser(user)) } -func (apiCfg *apiConfig) getUserHandler(writer http.ResponseWriter, request *http.Request) { - apiKey, err := auth.GetAPIKey(request.Header) - if err != nil { - respondWithError(writer, 403, fmt.Sprintf("E#1OTELA - Auth Error: %v", err)) - return - } - user, err := apiCfg.DB.GetUserByAPIKey(request.Context(), apiKey) - if err != nil { - respondWithError(writer, constants.HTTP_ERROR, fmt.Sprintf("E#1OTELI - Couldn't get user: %v", err)) - return - } - +func (apiCfg *apiConfig) getUserHandler(writer http.ResponseWriter, request *http.Request, user database.User) { respondWithJSON(writer, constants.HTTP_SUCCESS, user) }