From 8014a0d6ddf6c7f623be5e1e30a967b8d6e847cf Mon Sep 17 00:00:00 2001 From: Logan Cox Date: Wed, 12 Jun 2024 08:24:50 +0100 Subject: [PATCH] feat: add authenticated URL delete endpoint --- collision.go | 28 +++++++++++++++++++++ handlers.go | 46 +++++++++++++++++------------------ internal/database/urls.sql.go | 16 ++++++++++++ main.go | 2 ++ sql/queries/urls.sql | 5 ++++ 5 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 collision.go diff --git a/collision.go b/collision.go new file mode 100644 index 0000000..cd584e0 --- /dev/null +++ b/collision.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "database/sql" + + "url-short/internal/database" + "url-short/internal/shortener" +) + +func hashCollisionDetection(DB *database.Queries, url string, count int, requestContext context.Context) (string, error) { + hashURL := shortener.Hash(url, count) + shortURLHash := shortener.Shorten(hashURL) + + _, err := DB.SelectURL(requestContext, shortURLHash) + + if err == sql.ErrNoRows { + return shortURLHash, nil + } + + if err != nil && err != sql.ErrNoRows { + return "", err + } + + count++ + + return hashCollisionDetection(DB, url, count, requestContext) +} diff --git a/handlers.go b/handlers.go index faafd85..25f6484 100644 --- a/handlers.go +++ b/handlers.go @@ -1,7 +1,6 @@ package main import ( - "context" "crypto/rand" "database/sql" "encoding/hex" @@ -15,11 +14,10 @@ import ( "strings" "time" - "url-short/internal/database" - "url-short/internal/shortener" - "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" + + "url-short/internal/database" ) type apiConfig struct { @@ -114,25 +112,6 @@ func (apiCfg *apiConfig) postLongURL(w http.ResponseWriter, r *http.Request, use }) } -func hashCollisionDetection(DB *database.Queries, url string, count int, requestContext context.Context) (string, error) { - hashURL := shortener.Hash(url, count) - shortURLHash := shortener.Shorten(hashURL) - - _, err := DB.SelectURL(requestContext, shortURLHash) - - if err == sql.ErrNoRows { - return shortURLHash, nil - } - - if err != nil && err != sql.ErrNoRows { - return "", err - } - - count++ - - return hashCollisionDetection(DB, url, count, requestContext) -} - func (apiCfg *apiConfig) getShortURL(w http.ResponseWriter, r *http.Request) { query := r.PathValue("shortUrl") @@ -147,6 +126,27 @@ func (apiCfg *apiConfig) getShortURL(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, row.LongUrl, http.StatusMovedPermanently) } +func (apiCfg *apiConfig) deleteShortURL(w http.ResponseWriter, r * http.Request, user database.User) { + query := r.PathValue("shortUrl") + + if query == "" { + respondWithError(w, http.StatusBadRequest, "no short url supplied") + return + } + + err := apiCfg.DB.DeleteURL(r.Context(), database.DeleteURLParams{ + UserID: user.ID, + ShortUrl: query, + }) + + if err != nil { + respondWithError(w, http.StatusBadRequest, "could not delete short url") + return + } + + w.WriteHeader(http.StatusOK) +} + func (apiCfg *apiConfig) postAPIUsers(w http.ResponseWriter, r *http.Request) { payload := APIUserRequest{} diff --git a/internal/database/urls.sql.go b/internal/database/urls.sql.go index af18260..10a0f9b 100644 --- a/internal/database/urls.sql.go +++ b/internal/database/urls.sql.go @@ -44,6 +44,22 @@ func (q *Queries) CreateURL(ctx context.Context, arg CreateURLParams) (Url, erro return i, err } +const deleteURL = `-- name: DeleteURL :exec +DELETE FROM urls +WHERE user_id = $1 AND +short_url = $2 +` + +type DeleteURLParams struct { + UserID int32 + ShortUrl string +} + +func (q *Queries) DeleteURL(ctx context.Context, arg DeleteURLParams) error { + _, err := q.db.ExecContext(ctx, deleteURL, arg.UserID, arg.ShortUrl) + return err +} + const selectURL = `-- name: SelectURL :one SELECT id, short_url, long_url, created_at, updated_at, user_id FROM urls diff --git a/main.go b/main.go index 653ff0f..9a9e947 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,8 @@ func main() { // url management endpoints mux.HandleFunc("POST /api/v1/data/shorten", apiCfg.authenticationMiddlewear(apiCfg.postLongURL)) mux.HandleFunc("GET /api/v1/{shortUrl}", apiCfg.getShortURL) + mux.HandleFunc("DELETE /api/v1/{shortUrl}", apiCfg.authenticationMiddlewear(apiCfg.deleteShortURL)) + // user management endpoints mux.HandleFunc("POST /api/v1/users", apiCfg.postAPIUsers) diff --git a/sql/queries/urls.sql b/sql/queries/urls.sql index 04d90c6..76c634b 100644 --- a/sql/queries/urls.sql +++ b/sql/queries/urls.sql @@ -7,3 +7,8 @@ RETURNING *; SELECT * FROM urls WHERE short_url = $1; + +-- name: DeleteURL :exec +DELETE FROM urls +WHERE user_id = $1 AND +short_url = $2;