diff --git a/util/echo.go b/util/echo.go index 68b2cfb..860a9a7 100644 --- a/util/echo.go +++ b/util/echo.go @@ -1,6 +1,11 @@ package util -import "github.com/labstack/echo/v4" +import ( + "errors" + "github.com/jackc/pgx/v5" + "github.com/labstack/echo/v4" + "net/http" +) func EchoAllParams[T any](c echo.Context, fn func(string) (T, error), params ...string) ([]T, error) { r := make([]T, 0, len(params)) @@ -14,3 +19,11 @@ func EchoAllParams[T any](c echo.Context, fn func(string) (T, error), params ... return r, nil } + +func NewEchoPgxHTTPError(err error) *echo.HTTPError { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound, err) + } + + return echo.NewHTTPError(http.StatusInternalServerError, err) +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..6217177 --- /dev/null +++ b/util/util.go @@ -0,0 +1,15 @@ +package util + +func Unique[T comparable](s []T) []T { + r := make([]T, 0, len(s)) + unq := make(map[T]bool) + + for _, v := range s { + if present, _ := unq[v]; !present { + unq[v] = true + r = append(r, v) + } + } + + return r +} diff --git a/web/dev_application.go b/web/dev_application.go index 4619fba..71e56d1 100644 --- a/web/dev_application.go +++ b/web/dev_application.go @@ -102,7 +102,7 @@ VALUES }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, map[string]any{ @@ -137,7 +137,7 @@ AND id = $2 }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } if !deleted { @@ -188,7 +188,7 @@ GROUP BY apps.id }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, results) @@ -232,7 +232,7 @@ GROUP BY app.id }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, result) @@ -338,7 +338,7 @@ AND apps.account_id = $2 }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } nextToken := "" diff --git a/web/dev_application_client.go b/web/dev_application_client.go index c12a3cc..367b56d 100644 --- a/web/dev_application_client.go +++ b/web/dev_application_client.go @@ -69,6 +69,7 @@ func CreateDevApplicationClientEndpoint() echo.HandlerFunc { return echo.NewHTTPError(http.StatusBadRequest, errors.New("displayname must be between 1 and 100 characters")) } + body.RedirectURIs = util.Unique(body.RedirectURIs) if len(body.RedirectURIs) < 1 || len(body.RedirectURIs) > 50 { return echo.NewHTTPError(http.StatusBadRequest, errors.New("at least one and at most 50 redirect URIs might be added")) } @@ -139,7 +140,7 @@ AND id = $2 }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } if !created { @@ -197,7 +198,7 @@ AND app_clients.id = $3 }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, result) @@ -251,7 +252,7 @@ WHERE id = ( }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } if !updated { @@ -286,17 +287,24 @@ func AddOrDeleteDevApplicationClientRedirectURIEndpoint() echo.HandlerFunc { if c.Request().Method == http.MethodDelete { sql = ` UPDATE application_clients -SET redirect_uris = ARRAY_REMOVE(redirect_uris, $4) -WHERE id = ( - SELECT app_clients.id +SET redirect_uris = prep.redirect_uris +FROM ( + SELECT + app_clients.id AS id, + CASE + WHEN ARRAY_POSITION(app_clients.redirect_uris, $4) IS NOT NULL THEN ARRAY_REMOVE(app_clients.redirect_uris, $4) + ELSE app_clients.redirect_uris + END AS redirect_uris, + app_clients.redirect_uris AS prev_redirect_uris FROM application_clients app_clients INNER JOIN applications apps ON app_clients.application_id = apps.id WHERE apps.account_id = $1 AND apps.id = $2 AND app_clients.id = $3 - AND ARRAY_POSITION(app_clients.redirect_uris, $4) IS NOT NULL -) +) prep +WHERE application_clients.id = prep.id +RETURNING prep.prev_redirect_uris, application_clients.redirect_uris ` } else { if err := validateRedirectURIs([]string{redirectUri}); err != nil { @@ -305,39 +313,43 @@ WHERE id = ( sql = ` UPDATE application_clients -SET redirect_uris = ARRAY_APPEND(redirect_uris, $4) -WHERE id = ( - SELECT app_clients.id +SET redirect_uris = prep.redirect_uris +FROM ( + SELECT + app_clients.id AS id, + CASE + WHEN ARRAY_POSITION(app_clients.redirect_uris, $4) IS NULL THEN ARRAY_APPEND(app_clients.redirect_uris, $4) + ELSE app_clients.redirect_uris + END AS redirect_uris, + app_clients.redirect_uris AS prev_redirect_uris FROM application_clients app_clients INNER JOIN applications apps ON app_clients.application_id = apps.id WHERE apps.account_id = $1 AND apps.id = $2 AND app_clients.id = $3 - AND ARRAY_POSITION(app_clients.redirect_uris, $4) IS NULL -) +) prep +WHERE application_clients.id = prep.id +RETURNING prep.prev_redirect_uris, application_clients.redirect_uris ` } ctx := c.Request().Context() - var updated bool + var prevRedirectURIs, newRedirectURIs []string err := rctx.ExecuteTx(ctx, pgx.TxOptions{}, func(tx pgx.Tx) error { - tag, err := tx.Exec(ctx, sql, session.AccountId, applicationId, clientId, redirectUri) - if err != nil { - return err - } - - updated = tag.RowsAffected() > 0 - return nil + return tx.QueryRow(ctx, sql, session.AccountId, applicationId, clientId, redirectUri).Scan( + &prevRedirectURIs, + &newRedirectURIs, + ) }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } - if !updated { - return echo.NewHTTPError(http.StatusNotFound, errors.New("no rows were updated")) + if slices.Equal(prevRedirectURIs, newRedirectURIs) { + return c.NoContent(http.StatusNotModified) } return c.JSON(http.StatusOK, map[string]string{}) @@ -379,7 +391,7 @@ WHERE id = ( }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } if !deleted { @@ -442,7 +454,7 @@ AND application_client_accounts.account_id = app_client_account.account_id }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } if !updated { diff --git a/web/gw2_account.go b/web/gw2_account.go index be67970..26a81a8 100644 --- a/web/gw2_account.go +++ b/web/gw2_account.go @@ -5,6 +5,7 @@ import ( "github.com/gofrs/uuid/v5" "github.com/gw2auth/gw2auth.com-api/service" "github.com/gw2auth/gw2auth.com-api/service/gw2" + "github.com/gw2auth/gw2auth.com-api/util" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" "go.uber.org/zap" @@ -124,7 +125,7 @@ GROUP BY gw2_acc.gw2_account_id return err }) if err != nil { - return err + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, results) @@ -206,7 +207,7 @@ GROUP BY gw2_acc.gw2_account_id ) }) if err != nil { - return err + return util.NewEchoPgxHTTPError(err) } if verified { @@ -271,7 +272,7 @@ WHERE account_id = $1 AND gw2_account_id = $2 }) if err != nil { - return err + return util.NewEchoPgxHTTPError(err) } if rowsAffected < 1 { diff --git a/web/gw2_api_token.go b/web/gw2_api_token.go index 793e2e7..5907559 100644 --- a/web/gw2_api_token.go +++ b/web/gw2_api_token.go @@ -5,6 +5,7 @@ import ( "github.com/gofrs/uuid/v5" "github.com/gw2auth/gw2auth.com-api/service" "github.com/gw2auth/gw2auth.com-api/service/gw2" + "github.com/gw2auth/gw2auth.com-api/util" "github.com/its-felix/shine" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" @@ -150,7 +151,7 @@ last_valid_check_time = EXCLUDED.last_valid_check_time if errors.As(err, &httpError) { return httpError } else { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } } @@ -181,7 +182,7 @@ AND gw2_account_id = $2 }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, map[string]string{}) diff --git a/web/summary.go b/web/summary.go index c25493c..fd3b161 100644 --- a/web/summary.go +++ b/web/summary.go @@ -1,6 +1,7 @@ package web import ( + "github.com/gw2auth/gw2auth.com-api/util" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" "net/http" @@ -41,7 +42,7 @@ SELECT }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, res) diff --git a/web/user_application.go b/web/user_application.go index f0ae1ad..1fe12e8 100644 --- a/web/user_application.go +++ b/web/user_application.go @@ -3,6 +3,7 @@ package web import ( "github.com/gofrs/uuid/v5" "github.com/gw2auth/gw2auth.com-api/service" + "github.com/gw2auth/gw2auth.com-api/util" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" "net/http" @@ -72,7 +73,7 @@ GROUP BY app.id }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, results) @@ -98,7 +99,7 @@ AND application_id = $2 }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err) + return util.NewEchoPgxHTTPError(err) } return c.JSON(http.StatusOK, map[string]string{})