From b7d2fdf2ce59efd72bb608ecb73ae05b8b24ec30 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Thu, 14 Nov 2024 18:16:32 -0600 Subject: [PATCH] Moving updateLink to use org link hash instead of db ID --- api/api_test.go | 43 ++++++++++++++------ api/graph/generated.go | 10 ++--- api/graph/model/models_gen.go | 2 +- api/graph/schema.graphqls | 2 +- api/graph/schema.resolvers.go | 9 ++-- core/routes.go | 70 ++++++++++++++++---------------- core/routes_test.go | 13 +++--- migrations/test_migration.up.sql | 4 +- templates/link_create.html | 2 +- templates/link_detail.html | 4 +- templates/link_list.html | 10 ++--- templates/note_create.html | 2 +- templates/org_link_as_read.html | 2 +- 13 files changed, 93 insertions(+), 80 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index d1487d4..3daa88f 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -534,16 +534,24 @@ func TestAPI(t *testing.T) { }) t.Run("invalid org link update", func(t *testing.T) { + // We know this has already been saved earlier. We need the hash to fetch it so + // let's query the db + ols, err := models.GetOrgLinks(dbCtx, + &database.FilterOptions{Filter: sq.Eq{"ol.id": 3}, Limit: 1}, + ) + c.NoError(err) + ol := ols[0] + type GraphQLResponse struct { Link models.OrgLink `json:"updateLink"` } var result GraphQLResponse - q := `mutation UpdateLink($id: Int!, $title: String!, + q := `mutation UpdateLink($hash: String!, $title: String!, $url: String!, $visibility: Int!, $tags: String) { updateLink(input: { title: $title, - id: $id, + hash: $hash, url: $url, visibility: $visibility, tags: $tags, @@ -551,17 +559,17 @@ func TestAPI(t *testing.T) { }` op := gqlclient.NewOperation(q) op.Var("title", "New title") - op.Var("id", "3") + op.Var("hash", ol.Hash) op.Var("url", "invalid url") op.Var("visibility", models.OrgLinkVisibilityPublic) op.Var("tags", "one, two, three") - err := links.Execute(ctx, op, &result) + err = links.Execute(ctx, op, &result) c.Error(err) c.Equal("gqlclient: server failure: Invalid URL.", err.Error()) op = gqlclient.NewOperation(q) op.Var("title", "") - op.Var("id", "3") + op.Var("hash", ol.Hash) op.Var("url", "https://netlandish.com") op.Var("visibility", models.OrgLinkVisibilityPublic) op.Var("tags", "one, two, three") @@ -571,7 +579,7 @@ func TestAPI(t *testing.T) { op = gqlclient.NewOperation(q) op.Var("title", "New Title") - op.Var("id", "3") + op.Var("hash", ol.Hash) op.Var("url", "https://netlandish.com") op.Var("visibility", 100) op.Var("tags", "one, two, three") @@ -581,16 +589,24 @@ func TestAPI(t *testing.T) { }) t.Run("org link update", func(t *testing.T) { + // We know this has already been saved earlier. We need the hash to fetch it so + // let's query the db + ols, err := models.GetOrgLinks(dbCtx, + &database.FilterOptions{Filter: sq.Eq{"ol.id": 3}, Limit: 1}, + ) + c.NoError(err) + ol := ols[0] + type GraphQLResponse struct { Link models.OrgLink `json:"updateLink"` } var result GraphQLResponse - q := `mutation UpdateLink($id: Int!, $title: String!, + q := `mutation UpdateLink($hash: String!, $title: String!, $url: String!, $visibility: Int!, $tags: String) { updateLink(input: { title: $title, - id: $id, + hash: $hash, url: $url, visibility: $visibility, tags: $tags, @@ -598,12 +614,12 @@ func TestAPI(t *testing.T) { }` op := gqlclient.NewOperation(q) op.Var("title", "New title") - op.Var("id", "3") + op.Var("hash", ol.Hash) op.Var("url", "https://netlandish.com") op.Var("visibility", models.OrgLinkVisibilityPrivate) // We remove a tag and add a new one op.Var("tags", "one, three, four") - err := links.Execute(ctx, op, &result) + err = links.Execute(ctx, op, &result) c.NoError(err) c.Equal("New title", result.Link.Title) c.Equal("https://netlandish.com", result.Link.URL) @@ -1870,16 +1886,17 @@ func TestAPI(t *testing.T) { ctx := server.ServerContext(context.Background(), srv) ctx = auth.Context(ctx, test.NewTestUser(2, false, false, true, true)) ctx = crypto.Context(ctx, entropy) + type GraphQLResponse struct { Link models.OrgLink `json:"updateLink"` } var result GraphQLResponse - q := `mutation UpdateLink($id: Int!, $title: String!, + q := `mutation UpdateLink($hash: String!, $title: String!, $url: String!, $visibility: Int!, $tags: String) { updateLink(input: { title: $title, - id: $id, + hash: $hash, url: $url, visibility: $visibility, tags: $tags, @@ -1887,7 +1904,7 @@ func TestAPI(t *testing.T) { }` op := gqlclient.NewOperation(q) op.Var("title", "New title") - op.Var("id", "3") + op.Var("hash", "not valid hash") op.Var("url", "https://netlandish.com") op.Var("visibility", models.OrgLinkVisibilityPrivate) err := links.Execute(ctx, op, &result) diff --git a/api/graph/generated.go b/api/graph/generated.go index 383320c..a81a031 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -23114,20 +23114,20 @@ func (ec *executionContext) unmarshalInputUpdateLinkInput(ctx context.Context, o asMap[k] = v } - fieldsInOrder := [...]string{"id", "title", "description", "url", "visibility", "unread", "starred", "tags"} + fieldsInOrder := [...]string{"hash", "title", "description", "url", "visibility", "unread", "starred", "tags"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "id": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) - data, err := ec.unmarshalNInt2int(ctx, v) + case "hash": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("hash")) + data, err := ec.unmarshalNString2string(ctx, v) if err != nil { return it, err } - it.ID = data + it.Hash = data case "title": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("title")) data, err := ec.unmarshalOString2áš–string(ctx, v) diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index a0149ff..2fede96 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -387,7 +387,7 @@ type UpdateAdminDomainInput struct { } type UpdateLinkInput struct { - ID int `json:"id"` + Hash string `json:"hash"` Title *string `json:"title,omitempty"` Description *string `json:"description,omitempty"` URL *string `json:"url,omitempty"` diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 74ae393..bebe566 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -521,7 +521,7 @@ input GetListingDetailInput { } input UpdateLinkInput{ - id: Int! + hash: String! title: String description: String url: String diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 247833c..d576c09 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -328,7 +328,7 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi lt := localizer.GetLocalizer(lang) validator := valid.New(ctx) - validator.Expect(input.ID != 0, lt.Translate("Id required")). + validator.Expect(input.Hash != "", lt.Translate("Link hash required")). WithField("id"). WithCode(valid.ErrValidationCode) @@ -371,11 +371,8 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi } opts := &database.FilterOptions{ - Filter: sq.And{ - sq.Eq{"ol.id": input.ID}, - sq.Eq{"ol.user_id": user.ID}, - }, - Limit: 1, + Filter: sq.Eq{"ol.hash": input.Hash}, + Limit: 1, } orgLinks, err := models.GetOrgLinks(ctx, opts) diff --git a/core/routes.go b/core/routes.go index ec1476b..6479c2e 100644 --- a/core/routes.go +++ b/core/routes.go @@ -87,19 +87,19 @@ func (s *Service) RegisterRoutes() { s.Group.GET("/home", s.OrgLinksList).Name = s.RouteName("home_link_list") s.Group.GET("/add", s.OrgLinksCreate).Name = s.RouteName("link_create") s.Group.POST("/add", s.OrgLinksCreate).Name = s.RouteName("link_create_post") - s.Group.GET("/read/:id", s.OrgLinkAsReadToggle).Name = s.RouteName("link_mark_as_read") - s.Group.GET("/star/:id", s.OrgLinkStarToggle).Name = s.RouteName("link_star_toggle") + s.Group.GET("/read/:hash", s.OrgLinkAsReadToggle).Name = s.RouteName("link_mark_as_read") + s.Group.GET("/star/:hash", s.OrgLinkStarToggle).Name = s.RouteName("link_star_toggle") s.Group.GET("/link/:hash/delete", s.OrgLinkDelete).Name = s.RouteName("link_delete") s.Group.POST("/link/:hash/delete", s.OrgLinkDelete).Name = s.RouteName("link_delete") - s.Group.GET("/link/:id/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit") - s.Group.POST("/link/:id/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit_post") + s.Group.GET("/link/:hash/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit") + s.Group.POST("/link/:hash/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit_post") s.Group.GET("/qr/:hash/detail", s.QRManageDetail).Name = s.RouteName("qr_manage_detail") s.Group.GET("/qr/:id/delete", s.QRManageDelete).Name = s.RouteName("qr_manage_delete") s.Group.POST("/qr/:id/delete", s.QRManageDelete).Name = s.RouteName("qr_manage_delete_post") s.Group.GET("/note/add", s.NoteCreate).Name = s.RouteName("note_create") s.Group.POST("/note/add", s.NoteCreate).Name = s.RouteName("note_create_post") - s.Group.GET("/note/:id/edit", s.NoteUpdate).Name = s.RouteName("note_edit") - s.Group.POST("/note/:id/edit", s.NoteUpdate).Name = s.RouteName("note_edit_post") + s.Group.GET("/note/:hash/edit", s.NoteUpdate).Name = s.RouteName("note_edit") + s.Group.POST("/note/:hash/edit", s.NoteUpdate).Name = s.RouteName("note_edit_post") } func (s *Service) InvalidDomain(c echo.Context) error { @@ -2409,8 +2409,8 @@ func (s *Service) OrgLinkDelete(c echo.Context) error { // OrgLinkUpdate ... func (s *Service) OrgLinkUpdate(c echo.Context) error { gctx := c.(*server.Context) - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + hash := c.Param("hash") + if hash == "" { return echo.NotFoundHandler(c) } @@ -2418,7 +2418,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error { opts := &database.FilterOptions{ Filter: sq.And{ sq.Eq{"ol.user_id": user.ID}, - sq.Eq{"ol.id": id}, + sq.Eq{"ol.hash": hash}, }, Limit: 1, } @@ -2461,7 +2461,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error { "pd": pd, "visibilityOpt": visibilityOpt, "orgs": orgs, - "orgLinkID": orgLink.ID, + "orgLinkHash": orgLink.Hash, "navFlag": "addLink", "useTagAutocomplete": true, "autoCompleteOrgID": orgLink.OrgID, @@ -2486,25 +2486,25 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( - `mutation UpdateLink($id: Int!, $title: String!, $description: String, + `mutation UpdateLink($hash: String!, $title: String!, $description: String, $url: String!, $visibility: Int!, $unread: Boolean, $starred: Boolean, $tags: String) { updateLink(input: { title: $title, description: $description, - id: $id, + hash: $hash, url: $url, visibility: $visibility, unread: $unread, starred: $starred, tags: $tags, }) { - id + hash } }`) op.Var("title", form.Title) op.Var("description", form.Description) - op.Var("id", orgLink.ID) + op.Var("hash", orgLink.Hash) op.Var("url", form.URL) op.Var("tags", form.Tags) op.Var("unread", form.Unread) @@ -3034,14 +3034,14 @@ func (s *Service) ImportData(c echo.Context) error { func (s *Service) OrgLinkStarToggle(c echo.Context) error { gctx := c.(*server.Context) - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - return err + hash := c.Param("hash") + if hash == "" { + return echo.NotFoundHandler(c) } opts := &database.FilterOptions{ Filter: sq.And{ - sq.Eq{"ol.id": id}, + sq.Eq{"ol.hash": hash}, sq.Eq{"ol.user_id": gctx.User.GetID()}, }, Limit: 1, @@ -3058,15 +3058,15 @@ func (s *Service) OrgLinkStarToggle(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( - `mutation UpdateLink($id: Int!, $starred: Boolean) { + `mutation UpdateLink($hash: String!, $starred: Boolean) { updateLink(input: { - id: $id, + hash: $hash, starred: $starred, }) { id } }`) - op.Var("id", link.ID) + op.Var("hash", link.Hash) op.Var("starred", !link.Starred) err = links.Execute(c.Request().Context(), op, &result) if err != nil { @@ -3082,14 +3082,14 @@ func (s *Service) OrgLinkStarToggle(c echo.Context) error { func (s *Service) OrgLinkAsReadToggle(c echo.Context) error { gctx := c.(*server.Context) - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - return err + hash := c.Param("hash") + if hash == "" { + return echo.NotFoundHandler(c) } opts := &database.FilterOptions{ Filter: sq.And{ - sq.Eq{"ol.id": id}, + sq.Eq{"ol.hash": hash}, sq.Eq{"ol.user_id": gctx.User.GetID()}, }, Limit: 1, @@ -3106,15 +3106,15 @@ func (s *Service) OrgLinkAsReadToggle(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( - `mutation UpdateLink($id: Int!, $unread: Boolean) { + `mutation UpdateLink($hash: String!, $unread: Boolean) { updateLink(input: { - id: $id, + hash: $hash, unread: $unread, }) { id } }`) - op.Var("id", link.ID) + op.Var("hash", link.Hash) op.Var("unread", !link.Unread) err = links.Execute(c.Request().Context(), op, &result) if err != nil { @@ -3231,8 +3231,8 @@ func (s *Service) FollowToggle(c echo.Context) error { func (s *Service) NoteUpdate(c echo.Context) error { gctx := c.(*server.Context) - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + hash := c.Param("hash") + if hash == "" { return echo.NotFoundHandler(c) } @@ -3240,7 +3240,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { opts := &database.FilterOptions{ Filter: sq.And{ sq.Eq{"ol.user_id": user.ID}, - sq.Eq{"ol.id": id}, + sq.Eq{"ol.hash": hash}, }, Limit: 1, } @@ -3281,7 +3281,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { "pd": pd, "visibilityOpt": visibilityOpt, "orgs": orgs, - "orgLinkID": orgLink.ID, + "orgLinkHash": orgLink.Hash, "navFlag": "addNote", "useTagAutocomplete": true, "autoCompleteOrgID": orgLink.OrgID, @@ -3306,10 +3306,10 @@ func (s *Service) NoteUpdate(c echo.Context) error { var result GraphQLResponse op := gqlclient.NewOperation( - `mutation UpdateLink($id: Int!, $title: String!, $description: String, + `mutation UpdateLink($hash: String!, $title: String!, $description: String, $visibility: Int!, $starred: Boolean, $tags: String) { updateLink(input: { - id: $id, + hash: $hash, title: $title, description: $description, visibility: $visibility, @@ -3322,7 +3322,7 @@ func (s *Service) NoteUpdate(c echo.Context) error { }`) op.Var("title", form.Title) op.Var("description", form.Description) - op.Var("id", orgLink.ID) + op.Var("hash", orgLink.Hash) op.Var("tags", form.Tags) op.Var("starred", form.Starred) op.Var("visibility", form.Visibility) diff --git a/core/routes_test.go b/core/routes_test.go index 0c97291..1f4eece 100644 --- a/core/routes_test.go +++ b/core/routes_test.go @@ -11,7 +11,6 @@ import ( "net/http" "net/http/httptest" "net/url" - "strconv" "strings" "testing" @@ -244,9 +243,9 @@ func TestHandlers(t *testing.T) { Context: e.NewContext(request, recorder), User: loggedInUser, } - ctx.SetPath("/link/:id/edit") - ctx.SetParamNames("id") - ctx.SetParamValues(strconv.Itoa(orgLink.ID)) + ctx.SetPath("/link/:hash/edit") + ctx.SetParamNames("hash") + ctx.SetParamValues(orgLink.Hash) // We get the error from the API call err = test.MakeRequestWithDomain(srv, coreService.OrgLinkUpdate, ctx, domains[0]) c.NoError(err) @@ -279,9 +278,9 @@ func TestHandlers(t *testing.T) { Context: e.NewContext(request, recorder), User: loggedInUser, } - ctx.SetPath("/link/:id/edit") - ctx.SetParamNames("id") - ctx.SetParamValues(strconv.Itoa(orgLink.ID)) + ctx.SetPath("/link/:hash/edit") + ctx.SetParamNames("hash") + ctx.SetParamValues(orgLink.Hash) // We get the error from the API call err = test.MakeRequestWithDomain(srv, coreService.OrgLinkUpdate, ctx, domains[0]) c.NoError(err) diff --git a/migrations/test_migration.up.sql b/migrations/test_migration.up.sql index d954a89..52f99c6 100644 --- a/migrations/test_migration.up.sql +++ b/migrations/test_migration.up.sql @@ -10,10 +10,10 @@ INSERT INTO organizations (owner_id, name, slug) VALUES (2, 'api test org', 'api INSERT INTO base_urls (url, hash) VALUES ('http://base.com', 'abcdefg'); INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, hash) VALUES - ('Public Business url', 'http://base.com?vis=public', 1, 1, 2, 0, 'hijklmn'); + ('Public Business url', 'http://base.com?vis=public', 1, 1, 2, 0, 'hash1'); INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, hash) VALUES - ('Private Business url', 'http://base.com?vis=private', 1, 1, 2, 1, 'opqrstu'); + ('Private Business url', 'http://base.com?vis=private', 1, 1, 2, 1, 'hash2'); INSERT INTO domains (name, lookup_name, org_id, service, status) VALUES ('short domain', 'short.domain.org', 1, 1, 1); INSERT INTO domains (name, lookup_name, org_id, service, status, level) VALUES ('listing domain', 'list.domain.org', 1, 2, 1, 1); diff --git a/templates/link_create.html b/templates/link_create.html index 442dd86..23270db 100644 --- a/templates/link_create.html +++ b/templates/link_create.html @@ -6,7 +6,7 @@
-
- + {{if .errors._global_ }} {{range .errors._global_}} diff --git a/templates/org_link_as_read.html b/templates/org_link_as_read.html index 62ee666..4f6e2c2 100644 --- a/templates/org_link_as_read.html +++ b/templates/org_link_as_read.html @@ -1,6 +1,6 @@ {{template "base" .}}
- +

{{.pd.Data.message}}?

-- 2.45.2