From d34cb39e9f599022ffe69f4ce9bf7d33590156f2 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Mon, 8 Jul 2024 18:29:09 -0600 Subject: [PATCH] Update link's clicked counter when clicked --- assets/main.min.js | 2 +- data/queries.sql.go | 29 ++++++++++++++++++++ handlers.go | 19 +++++++++++++ main.go | 1 + queries.sql | 9 ++++++- src/Main.elm | 65 ++++++++++++++++++++++++++++++++------------- 6 files changed, 105 insertions(+), 20 deletions(-) diff --git a/assets/main.min.js b/assets/main.min.js index 9b2299e..eec4568 100644 --- a/assets/main.min.js +++ b/assets/main.min.js @@ -1 +1 @@ -!function(W){"use strict";function R(n,r,t){return t.a=n,t.f=r,t}function r(t){return R(2,t,function(r){return function(n){return t(r,n)}})}function t(e){return R(3,e,function(t){return function(r){return function(n){return e(t,r,n)}}})}function n(u){return R(4,u,function(e){return function(t){return function(r){return function(n){return u(e,t,r,n)}}}})}function H(a){return R(5,a,function(u){return function(e){return function(t){return function(r){return function(n){return a(u,e,t,r,n)}}}}})}function F(c){return R(6,c,function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return c(a,u,e,t,r,n)}}}}}})}function q(i){return R(7,i,function(c){return function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return i(c,a,u,e,t,r,n)}}}}}}})}function s(n,r,t){return 2===n.a?n.f(r,t):n(r)(t)}function b(n,r,t,e){return 3===n.a?n.f(r,t,e):n(r)(t)(e)}function x(n,r,t,e,u){return 4===n.a?n.f(r,t,e,u):n(r)(t)(e)(u)}function v(n,r,t,e,u,a){return 5===n.a?n.f(r,t,e,u,a):n(r)(t)(e)(u)(a)}function D(n,r,t,e,u,a,c){return 6===n.a?n.f(r,t,e,u,a,c):n(r)(t)(e)(u)(a)(c)}function G(n,r,t,e,u,a,c,i){return 7===n.a?n.f(r,t,e,u,a,c,i):n(r)(t)(e)(u)(a)(c)(i)}function V(n,r){for(var t,e=[],u=I(n,r,0,e);u&&(t=e.pop());u=I(t.a,t.b,0,e));return u}function I(n,r,t,e){if(n!==r){if("object"!=typeof n||null===n||null===r)return"function"==typeof n&&tn(5),!1;if(100c)return u}var v=t.$;if(4===v){for(var l=t.k;4===l.$;)l=l.k;return n(r,l,e,u,a+1,c,r.elm_event_node_ref)}var d=t.e;var $=r.childNodes;for(var h=0;hc))return u;a=p}return u}(n,r,t,0,0,r.b,e)}function mr(n,r,t,e){return 0===t.length?n:(pr(n,r,t,e),wr(n,t))}function wr(n,r){for(var t=0;t "),B(n.bl),s(gu,d([S({$:5,a:n.cc})]),d([B(" ×")]))])),s(pa,l,s(_,vt,n.bC))]))]))])):B("")},Oa={$:20},ya=r(function(n,r){return s(Y,l,d([b(ra,"Watches",Oa,s(Y,l,d([v(ba,"Item: ","some string...","name",n.B.bl,function(n){return{$:10,a:e(r,{bl:n})}}),v(ba,"Repository: ","NixOS/nixpkgs","repo",n.B.cF,function(n){return{$:10,a:e(r,{cF:n})}})])))]))}),gn=bn({bl:"Action",Y:ln,_:function(n){return s($u,l,d([s(ou,d([S({$:5,a:n.cc})]),d([B("Delete")]))]))}}),_a=(u={aB:d([s(hn,"Name",function(n){return n.bl}),s(hn,"Repo",function(n){return n.cF}),gn]),aW:function(n){return n.bl},aX:function(n){return{$:24,a:n}}},en=u.aX,Q=u.aW,{aB:s(_,function(n){return n},u.aB),a3:rn,aW:Q,aX:en}),X=_n({cf:function(n){return{a:Qe,b:fe(d([Ue,Xe,Pe]))}},cO:function(n){return Ze},cT:un,cU:function(t){return s(Y,l,d([s(Au,l,d([s(Y,d([C("grid")]),d([s(Y,d([C("col")]),d([function(n){return s(Y,l,d([s(da,s(ya,n,n.B),s(Xu,d([S(ga)]),d([B(" ⟳")]))),n.aa.b?s(pa,l,s(_,ka,n.aa)):B("No watches found!")]))}(t)])),s(Y,d([C("col")]),d([function(n){return s(Y,l,d([s(da,s(va,n,n.s),s(Xu,d([S(Uu)]),d([B(" ⟳")]))),n.W.b?s(Y,d([C("icon-grid")]),s(_,ha,n.W)):B("No links found!")]))}(t)]))])),s(Y,d([C("grid")]),d([function(){var n,r=t.i;return 4===r.$?(r=r.a,s(Y,d([(n=d([{a:"error",b:!0}]),C(s(_t," ",s(_,Pt,s(bu,su,n)))))]),d([B(r)]))):B("")}()]))])),s(lu,l,d([s(vu,l,d([s(Lu,l,d([s(iu,l,d([B("Maintenence")]))])),s(Y,l,d([s(ou,d([S(cu)]),d([B("Update Icons")]))])),s(Y,l,d([s(du,l,d([B("Links")])),b(Pu,Nu,t.as,t.W)])),s(Y,l,d([s(du,l,d([B("Watched Items")])),b(Pu,_a,t.az,t.aa)])),s(Y,l,d([s(du,l,d([B("Watched Items Ignores")])),b(Pu,Tu,t.aq,t.ar)]))]))]))]))}});nn={Main:{init:X(re(0))(0)}},W.Elm?function n(r,t){for(var e in t)e in r?"init"==e?tn(6):n(r[e],t[e]):r[e]=t[e]}(W.Elm,nn):W.Elm=nn}(this); \ No newline at end of file +!function(q){"use strict";function Y(n,r,t){return t.a=n,t.f=r,t}function r(t){return Y(2,t,function(r){return function(n){return t(r,n)}})}function t(e){return Y(3,e,function(t){return function(r){return function(n){return e(t,r,n)}}})}function R(u){return Y(4,u,function(e){return function(t){return function(r){return function(n){return u(e,t,r,n)}}}})}function W(a){return Y(5,a,function(u){return function(e){return function(t){return function(r){return function(n){return a(u,e,t,r,n)}}}}})}function D(c){return Y(6,c,function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return c(a,u,e,t,r,n)}}}}}})}function G(o){return Y(7,o,function(c){return function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return o(c,a,u,e,t,r,n)}}}}}}})}function s(n,r,t){return 2===n.a?n.f(r,t):n(r)(t)}function b(n,r,t,e){return 3===n.a?n.f(r,t,e):n(r)(t)(e)}function H(n,r,t,e,u){return 4===n.a?n.f(r,t,e,u):n(r)(t)(e)(u)}function v(n,r,t,e,u,a){return 5===n.a?n.f(r,t,e,u,a):n(r)(t)(e)(u)(a)}function I(n,r,t,e,u,a,c){return 6===n.a?n.f(r,t,e,u,a,c):n(r)(t)(e)(u)(a)(c)}function M(n,r,t,e,u,a,c,o){return 7===n.a?n.f(r,t,e,u,a,c,o):n(r)(t)(e)(u)(a)(c)(o)}function J(n,r){for(var t,e=[],u=X(n,r,0,e);u&&(t=e.pop());u=X(t.a,t.b,0,e));return u}function X(n,r,t,e){if(n!==r){if("object"!=typeof n||null===n||null===r)return"function"==typeof n&&tn(5),!1;if(100c)return u}var v=t.$;if(4===v){for(var l=t.k;4===l.$;)l=l.k;return n(r,l,e,u,a+1,c,r.elm_event_node_ref)}var d=t.e;var $=r.childNodes;for(var h=0;hc))return u;a=p}return u}(n,r,t,0,0,r.b,e)}function mr(n,r,t,e){return 0===t.length?n:(pr(n,r,t,e),wr(n,t))}function wr(n,r){for(var t=0;t "),F(n.bl),s(Au,d([T({$:7,a:n.cc})]),d([F(" ×")]))])),s(_a,l,s(y,ht,n.bC))]))]))])):F("")},La={$:22},Ca=r(function(n,r){return s(S,l,d([b(fa,"Watches",La,s(S,l,d([v(pa,"Item: ","some string...","name",n.B.bl,function(n){return{$:12,a:e(r,{bl:n})}}),v(pa,"Repository: ","NixOS/nixpkgs","repo",n.B.cF,function(n){return{$:12,a:e(r,{cF:n})}})])))]))}),Q=ln({bl:"Action",Y:un,_:function(n){return s(Ou,l,d([s(hu,d([T({$:7,a:n.cc})]),d([F("Delete")]))]))}}),Ba=(kr={aF:d([s(sn,"Name",function(n){return n.bl}),s(sn,"Repo",function(n){return n.cF}),Q]),aX:function(n){return n.bl},aY:function(n){return{$:26,a:n}}},n=kr.aY,z=kr.aX,{aF:s(y,function(n){return n},kr.aF),a3:rn,aX:z,aY:n}),yn=fn({cf:function(n){return{a:au,b:he(d([tu,eu,ru]))}},cO:function(n){return cu},cT:en,cU:function(t){return s(S,l,d([s(Yu,l,d([s(S,d([B("grid")]),d([s(S,d([B("col")]),d([function(n){return s(S,l,d([s(ka,s(Ca,n,n.B),s(ua,d([T(Aa)]),d([F(" ⟳")]))),n.aa.b?s(_a,l,s(y,Ea,n.aa)):F("No watches found!")]))}(t)])),s(S,d([B("col")]),d([function(n){return s(S,l,d([s(ka,s(wa,n,n.s),s(ua,d([T(ea)]),d([F(" ⟳")]))),n.W.b?s(S,d([B("icon-grid")]),s(y,ya,n.W)):F("No links found!")]))}(t)]))])),s(S,d([B("grid")]),d([function(){var n,r=t.e;return 4===r.$?(r=r.a,s(S,d([(n=d([{a:"error",b:!0}]),B(s(xt," ",s(y,Kt,s(pu,mu,n)))))]),d([F(r)]))):F("")}()]))])),s(Nu,l,d([s(wu,l,d([s(Ru,l,d([s($u,l,d([F("Maintenence")]))])),s(S,l,d([s(hu,d([T(du)]),d([F("Update Icons")]))])),s(S,l,d([s(ku,l,d([F("Links")])),b(ta,qu,t.at,t.W)])),s(S,l,d([s(ku,l,d([F("Watched Items")])),b(ta,Ba,t.aD,t.aa)])),s(S,l,d([s(ku,l,d([F("Watched Items Ignores")])),b(ta,Tu,t.ar,t.as)]))]))]))]))}});a={Main:{init:yn(ae(0))(0)}},q.Elm?function n(r,t){for(var e in t)e in r?"init"==e?tn(6):n(r[e],t[e]):r[e]=t[e]}(q.Elm,a):q.Elm=a}(this); \ No newline at end of file diff --git a/data/queries.sql.go b/data/queries.sql.go index 0d5defc..7582238 100644 --- a/data/queries.sql.go +++ b/data/queries.sql.go @@ -320,6 +320,7 @@ select id, owner_id, created_at, url, name, clicked, logo_url, shared from links where owner_id = ? or shared = true + order by clicked desc ` func (q *Queries) GetAllLinksForOwner(ctx context.Context, ownerID int64) ([]Link, error) { @@ -555,3 +556,31 @@ func (q *Queries) GetOwner(ctx context.Context, id int64) (Owner, error) { ) return i, err } + +const incrementLink = `-- name: IncrementLink :one +update links set +clicked = clicked + 1 +where id = ? + and owner_id = ? returning id, owner_id, created_at, url, name, clicked, logo_url, shared +` + +type IncrementLinkParams struct { + ID int64 `json:"id"` + OwnerID int64 `json:"owner_id"` +} + +func (q *Queries) IncrementLink(ctx context.Context, arg IncrementLinkParams) (Link, error) { + row := q.db.QueryRowContext(ctx, incrementLink, arg.ID, arg.OwnerID) + var i Link + err := row.Scan( + &i.ID, + &i.OwnerID, + &i.CreatedAt, + &i.Url, + &i.Name, + &i.Clicked, + &i.LogoUrl, + &i.Shared, + ) + return i, err +} diff --git a/handlers.go b/handlers.go index ba7a111..3bf3fde 100644 --- a/handlers.go +++ b/handlers.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "image" "image/color" "image/png" @@ -385,3 +386,21 @@ func linkDELETE(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) } } + +func linkGET(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ownerID, ok := ctx.Value(ownerKey).(int64) + if !ok { + http.Error(w, http.StatusText(http.StatusUnprocessableEntity), http.StatusUnprocessableEntity) + return + } + linkID, err := strconv.Atoi(chi.URLParam(r, "linkID")) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + link, err := app.queries.IncrementLink(ctx, data.IncrementLinkParams{ID: int64(linkID), OwnerID: ownerID}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + fmt.Fprintf(w, "%s", link.Url) +} diff --git a/main.go b/main.go index 2fe170d..1300c4c 100644 --- a/main.go +++ b/main.go @@ -149,6 +149,7 @@ func main() { r.Use(render.SetContentType(render.ContentTypeJSON)) r.Get("/", linksGET) r.Delete("/{linkID:[0-9]+}", linkDELETE) + r.Get("/{linkID:[0-9]+}", linkGET) r.Post("/", linksPOST) }) router.Route("/watches", func(r chi.Router) { diff --git a/queries.sql b/queries.sql index 1a47e2a..61462a4 100644 --- a/queries.sql +++ b/queries.sql @@ -30,7 +30,8 @@ where id = ? select * from links where owner_id = ? - or shared = true; + or shared = true + order by clicked desc; -- name: GetAllLinks :many select * @@ -97,3 +98,9 @@ where owner_id = ?; -- name: AddPullRequestIgnore :one insert into pull_request_ignores (owner_id, number, repo) values (?, ?, ?) returning *; + +-- name: IncrementLink :one +update links set +clicked = clicked + 1 +where id = ? + and owner_id = ? returning * ; diff --git a/src/Main.elm b/src/Main.elm index b635400..86343f3 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -1,6 +1,7 @@ module Main exposing (..) import Browser +import Browser.Navigation exposing (load) import Html exposing (..) import Html.Attributes exposing @@ -37,7 +38,9 @@ type Msg = AddedLink (Result Http.Error ()) | AddedWatch (Result Http.Error ()) | DeletedLink (Result Http.Error ()) + | ClickedLink (Result Http.Error String) | DeleteLink Int + | IncrementLink Int | DeletedWatch (Result Http.Error ()) | DeleteWatch Int | DeletedIgnore (Result Http.Error ()) @@ -186,6 +189,19 @@ deleteLink linkId = } +incrementLink : Int -> Cmd Msg +incrementLink linkId = + Http.request + { url = "/links/" ++ String.fromInt linkId + , method = "GET" + , timeout = Nothing + , tracker = Nothing + , headers = [] + , body = Http.emptyBody + , expect = Http.expectString ClickedLink + } + + deleteIgnore : Int -> Cmd Msg deleteIgnore ignoreId = Http.request @@ -227,6 +243,9 @@ update msg model = DeleteLink linkId -> ( model, deleteLink linkId ) + IncrementLink linkId -> + ( model, incrementLink linkId ) + DeleteWatch watchId -> ( model, deleteWatch watchId ) @@ -251,6 +270,12 @@ update msg model = AddedLink (Ok _) -> ( { model | newlink = initialModel.newlink }, getLinks ) + ClickedLink (Ok newUrl) -> + ( model, load newUrl ) + + ClickedLink (Err _) -> + ( { model | status = Errored "Server error incrementing link!" }, Cmd.none ) + DeletedLink (Ok _) -> ( model, getLinks ) @@ -678,6 +703,28 @@ viewLinks model = ] +viewLink : Links.Link -> Html Msg +viewLink link = + div [] + [ div [ class "icon" ] + [ span [ onClick (DeleteLink link.id) ] [ text "×" ] + , a + [ onClick (IncrementLink link.id) + + -- , href link.url + ] + [ div + [] + [ header [] + [ img [ src ("/icons/" ++ String.fromInt link.id) ] [] + ] + , text link.name + ] + ] + ] + ] + + viewWatches : Model -> Html Msg viewWatches model = div [] @@ -691,24 +738,6 @@ viewWatches model = ] -viewLink : Links.Link -> Html Msg -viewLink link = - div [] - [ div [ class "icon" ] - [ span [ onClick (DeleteLink link.id) ] [ text "×" ] - , a [ href link.url ] - [ div - [] - [ header [] - [ img [ src ("/icons/" ++ String.fromInt link.id) ] [] - ] - , text link.name - ] - ] - ] - ] - - viewWatch : Watches.Watch -> Html Msg viewWatch watch = case watch.results of