this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

some improvements on delete which also removes the posts after deleting subscription

+255 -83
+8 -3
feedgenerator.go
··· 12 12 type feedStore interface { 13 13 GetUsersFeed(usersDID string, cursor int64, limit int) ([]store.FeedPost, error) 14 14 GetSubscriptionsForUser(ctx context.Context, userDID string) ([]store.Subscription, error) 15 - DeleteSubscriptionByIdAndUser(userDID string, id int) error 15 + DeleteSubscriptionBySubRKeyAndUser(userDID, rkey string) error 16 + DeleteFeedPostsForSubscribedPostURIandUserDID(subscribedPostURI, userDID string) error 16 17 } 17 18 18 19 type FeedGenerator struct { ··· 66 67 return f.store.GetSubscriptionsForUser(ctx, userDID) 67 68 } 68 69 69 - func (f *FeedGenerator) DeleteSubscriptionByIdAndUser(userDID string, id int) error { 70 - return f.store.DeleteSubscriptionByIdAndUser(userDID, id) 70 + func (f *FeedGenerator) DeleteSubscriptionBySubRKeyAndUser(userDID, rkey string) error { 71 + return f.store.DeleteSubscriptionBySubRKeyAndUser(userDID, rkey) 72 + } 73 + 74 + func (f *FeedGenerator) DeleteFeedPostsForSubscribedPostURIandUserDID(subscribedPostURI, userDID string) error { 75 + return f.store.DeleteFeedPostsForSubscribedPostURIandUserDID(subscribedPostURI, userDID) 71 76 }
+1 -1
frontend/base.templ
··· 4 4 <!DOCTYPE html> 5 5 <html lang="en"> 6 6 <head> 7 - <title>Redirecter</title> 7 + <title>BSFeeder</title> 8 8 <link rel="icon" type="image/x-icon" href="/public/favicon.ico"/> 9 9 <meta charset="UTF-8"/> 10 10 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+2 -2
frontend/base_templ.go
··· 29 29 templ_7745c5c3_Var1 = templ.NopComponent 30 30 } 31 31 ctx = templ.ClearChildren(ctx) 32 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><title>Redirecter</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/public/favicon.ico\"><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link href=\"/public/styles.css\" rel=\"stylesheet\"><script defer src=\"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js\"></script><script src=\"https://unpkg.com/htmx.org\"></script><script src=\"https://unpkg.com/htmx.org@1.9.9\" defer></script><script src=\"https://unpkg.com/htmx.org@1.9.12/dist/ext/json-enc.js\"></script></head><body class=\"antialiased\">") 32 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) 33 33 if templ_7745c5c3_Err != nil { 34 34 return templ_7745c5c3_Err 35 35 } ··· 41 41 if templ_7745c5c3_Err != nil { 42 42 return templ_7745c5c3_Err 43 43 } 44 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>") 44 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) 45 45 if templ_7745c5c3_Err != nil { 46 46 return templ_7745c5c3_Err 47 47 }
+7 -7
frontend/home_templ.go
··· 91 91 if templ_7745c5c3_Err != nil { 92 92 return templ_7745c5c3_Err 93 93 } 94 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"relative flex justify-center overflow-hidden bg-gray-50 py-6 sm:py-12\"><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">") 94 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) 95 95 if templ_7745c5c3_Err != nil { 96 96 return templ_7745c5c3_Err 97 97 } ··· 104 104 if templ_7745c5c3_Err != nil { 105 105 return templ_7745c5c3_Err 106 106 } 107 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">") 107 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) 108 108 if templ_7745c5c3_Err != nil { 109 109 return templ_7745c5c3_Err 110 110 } ··· 117 117 if templ_7745c5c3_Err != nil { 118 118 return templ_7745c5c3_Err 119 119 } 120 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div></div><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">") 120 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) 121 121 if templ_7745c5c3_Err != nil { 122 122 return templ_7745c5c3_Err 123 123 } ··· 130 130 if templ_7745c5c3_Err != nil { 131 131 return templ_7745c5c3_Err 132 132 } 133 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">") 133 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) 134 134 if templ_7745c5c3_Err != nil { 135 135 return templ_7745c5c3_Err 136 136 } ··· 143 143 if templ_7745c5c3_Err != nil { 144 144 return templ_7745c5c3_Err 145 145 } 146 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div></div><div class=\"flex-1 mx-auto w-full max-w-md bg-white px-6 pt-6 pb-6 shadow-xl ring-1 ring-gray-900/5 sm:rounded-xl sm:px-8\"><div class=\"w-full\"><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your username is</h1><p class=\"mt-2 text-gray-500\">") 146 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) 147 147 if templ_7745c5c3_Err != nil { 148 148 return templ_7745c5c3_Err 149 149 } ··· 156 156 if templ_7745c5c3_Err != nil { 157 157 return templ_7745c5c3_Err 158 158 } 159 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div><div class=\"text-center\"><h1 class=\"text-3xl font-semibold text-gray-900\">Your email is</h1><p class=\"mt-2 text-gray-500\">") 159 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6) 160 160 if templ_7745c5c3_Err != nil { 161 161 return templ_7745c5c3_Err 162 162 } ··· 169 169 if templ_7745c5c3_Err != nil { 170 170 return templ_7745c5c3_Err 171 171 } 172 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div></div></div>") 172 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7) 173 173 if templ_7745c5c3_Err != nil { 174 174 return templ_7745c5c3_Err 175 175 }
+5 -5
frontend/login_templ.go
··· 62 62 templ_7745c5c3_Var2 = templ.NopComponent 63 63 } 64 64 ctx = templ.ClearChildren(ctx) 65 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form class=\"h-screen flex items-center justify-center\" id=\"login-form\" hx-swap=\"outerHTML\" hx-post=\"/login\" hx-ext=\"json-enc\"><div class=\"w-full max-w-sm\"><div class=\"md:flex md:items-center mb-6\"><div class=\"md:w-1/3\"><label class=\"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4\" for=\"handle\">Bsky Handle</label></div><div class=\"md:w-2/3\"><input class=\"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500\" id=\"handle\" name=\"handle\" type=\"text\" value=\"") 65 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) 66 66 if templ_7745c5c3_Err != nil { 67 67 return templ_7745c5c3_Err 68 68 } ··· 75 75 if templ_7745c5c3_Err != nil { 76 76 return templ_7745c5c3_Err 77 77 } 78 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div></div><div class=\"md:flex md:items-center mb-6\"><div class=\"md:w-1/3\"><label class=\"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4\" for=\"appPassword\">App Password</label></div><div class=\"md:w-2/3\"><input class=\"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500\" id=\"appPassword\" name=\"appPassword\" type=\"password\"></div></div><div class=\"md:flex md:items-center\"><div class=\"md:w-1/3\"></div><div class=\"md:w-1/3\"><button class=\"shadow bg-blue-500 hover:bg-blue-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded\" type=\"submit\" form=\"login-form\">Login</button></div>") 78 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) 79 79 if templ_7745c5c3_Err != nil { 80 80 return templ_7745c5c3_Err 81 81 } 82 82 if errorMsg != "" { 83 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"md:w-1/3\" id=\"error-message\"><label class=\"text-red-500 font-bold\">") 83 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) 84 84 if templ_7745c5c3_Err != nil { 85 85 return templ_7745c5c3_Err 86 86 } ··· 93 93 if templ_7745c5c3_Err != nil { 94 94 return templ_7745c5c3_Err 95 95 } 96 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label></div>") 96 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) 97 97 if templ_7745c5c3_Err != nil { 98 98 return templ_7745c5c3_Err 99 99 } 100 100 } 101 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></form>") 101 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) 102 102 if templ_7745c5c3_Err != nil { 103 103 return templ_7745c5c3_Err 104 104 }
+1 -4
frontend/nav.templ
··· 14 14 <a href="/">Home</a> 15 15 </li> 16 16 <li class="p-4 text-blue-500 hover:text-blue-800"> 17 - <a href="">Something</a> 18 - </li> 19 - <li class="p-4 text-blue-500 hover:text-blue-800"> 20 - <a href="">Hello</a> 17 + <a href="/subscription">Subcriptions</a> 21 18 </li> 22 19 </ul> 23 20 </nav>
+6 -6
frontend/nav_templ.go
··· 35 35 templ_7745c5c3_Var1 = templ.NopComponent 36 36 } 37 37 ctx = templ.ClearChildren(ctx) 38 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header class=\"header sticky top-0 bg-white shadow-md flex items-center justify-between px-8 py-02\"><nav class=\"nav font-semibold text-lg\"><ul class=\"flex items-center\"><li class=\"p-4 text-blue-500 hover:text-blue-800\"><a href=\"/\">Home</a></li><li class=\"p-4 text-blue-500 hover:text-blue-800\"><a href=\"\">Something</a></li><li class=\"p-4 text-blue-500 hover:text-blue-800\"><a href=\"\">Hello</a></li></ul></nav><div class=\"w-3/12 flex justify-end\"><div class=\"p-4 text-blue-500 hover:text-blue-800\">") 38 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) 39 39 if templ_7745c5c3_Err != nil { 40 40 return templ_7745c5c3_Err 41 41 } 42 42 if username, ok := ctx.Value(ContextUsernameKey).(string); ok { 43 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"text-right\" href=\"/account\">") 43 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) 44 44 if templ_7745c5c3_Err != nil { 45 45 return templ_7745c5c3_Err 46 46 } 47 47 var templ_7745c5c3_Var2 string 48 48 templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(username) 49 49 if templ_7745c5c3_Err != nil { 50 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/nav.templ`, Line: 27, Col: 53} 50 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/nav.templ`, Line: 24, Col: 53} 51 51 } 52 52 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) 53 53 if templ_7745c5c3_Err != nil { 54 54 return templ_7745c5c3_Err 55 55 } 56 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>") 56 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) 57 57 if templ_7745c5c3_Err != nil { 58 58 return templ_7745c5c3_Err 59 59 } 60 60 } else { 61 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"text-right\" href=\"/account\">Account </a>") 61 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) 62 62 if templ_7745c5c3_Err != nil { 63 63 return templ_7745c5c3_Err 64 64 } 65 65 } 66 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></header>") 66 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) 67 67 if templ_7745c5c3_Err != nil { 68 68 return templ_7745c5c3_Err 69 69 }
+23 -21
frontend/subscriptions.templ
··· 14 14 </div> 15 15 </div> 16 16 } 17 - <section class="border-t border-t-zinc-200 mt-6 px-2 py-4 w-96"> 18 - <p>Subscriptions</p> 19 - // LOOP THROUGH THE TODOS 20 - <ul id="todo-list"> 21 - for _, sub := range subscriptions { 22 - <li class="ml-4 ml-4 border p-2 rounded-lg mb-2" id={ fmt.Sprintf("sub%d", sub.ID) }> 23 - <a class="font-medium text-sm" href={ templ.URL(sub.SubscribedPostURI) }>{ sub.SubscribedPostURI }</a> 24 - <div class="flex gap-4 items-center mt-2"> 25 - <button 26 - hx-delete={ fmt.Sprintf("/sub/%d", sub.ID) } 27 - hx-swap="delete" 28 - hx-target={ fmt.Sprintf("#sub%d", sub.ID) } 29 - class="flex items-center border py-1 px-2 rounded-lg hover:bg-red-300" 30 - > 31 - <p class="text-sm">Delete</p> 32 - </button> 33 - </div> 34 - </li> 35 - } 36 - </ul> 37 - </section> 17 + <div class="overflow-x-auto"> 18 + <table class="min-w-half divide-y-2 divide-gray-200 bg-white text-sm"> 19 + <tbody class="divide-y divide-gray-200"> 20 + for _, sub := range subscriptions { 21 + <tr id={ fmt.Sprintf("sub-%s", sub.SubscriptionPostRkey) }> 22 + <td class="whitespace-nowrap px-4 py-2 font-medium text-gray-900"> 23 + <a class="font-medium text-sm" href={ templ.URL(sub.SubscribedPostURI) }>{ sub.SubscribedPostURI }</a> 24 + </td> 25 + <td class="whitespace-nowrap px-4 py-2 text-gray-700"> 26 + <button 27 + hx-delete={ fmt.Sprintf("/sub/%s", sub.SubscriptionPostRkey) } 28 + hx-swap="delete" 29 + hx-target={ fmt.Sprintf("#sub-%s", sub.SubscriptionPostRkey) } 30 + class="flex items-center border py-1 px-2 rounded-lg hover:bg-red-300" 31 + > 32 + <p class="text-sm">Delete</p> 33 + </button> 34 + </td> 35 + </tr> 36 + } 37 + </tbody> 38 + </table> 39 + </div> 38 40 }
+17 -17
frontend/subscriptions_templ.go
··· 39 39 return templ_7745c5c3_Err 40 40 } 41 41 if errorMsg != "" { 42 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div role=\"alert\"><div class=\"border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700\"><p>") 42 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) 43 43 if templ_7745c5c3_Err != nil { 44 44 return templ_7745c5c3_Err 45 45 } ··· 52 52 if templ_7745c5c3_Err != nil { 53 53 return templ_7745c5c3_Err 54 54 } 55 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div></div>") 55 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) 56 56 if templ_7745c5c3_Err != nil { 57 57 return templ_7745c5c3_Err 58 58 } 59 59 } 60 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section class=\"border-t border-t-zinc-200 mt-6 px-2 py-4 w-96\"><p>Subscriptions</p><ul id=\"todo-list\">") 60 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) 61 61 if templ_7745c5c3_Err != nil { 62 62 return templ_7745c5c3_Err 63 63 } 64 64 for _, sub := range subscriptions { 65 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"ml-4 ml-4 border p-2 rounded-lg mb-2\" id=\"") 65 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) 66 66 if templ_7745c5c3_Err != nil { 67 67 return templ_7745c5c3_Err 68 68 } 69 69 var templ_7745c5c3_Var3 string 70 - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("sub%d", sub.ID)) 70 + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("sub-%s", sub.SubscriptionPostRkey)) 71 71 if templ_7745c5c3_Err != nil { 72 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 22, Col: 86} 72 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 21, Col: 61} 73 73 } 74 74 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) 75 75 if templ_7745c5c3_Err != nil { 76 76 return templ_7745c5c3_Err 77 77 } 78 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a class=\"font-medium text-sm\" href=\"") 78 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) 79 79 if templ_7745c5c3_Err != nil { 80 80 return templ_7745c5c3_Err 81 81 } ··· 84 84 if templ_7745c5c3_Err != nil { 85 85 return templ_7745c5c3_Err 86 86 } 87 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") 87 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6) 88 88 if templ_7745c5c3_Err != nil { 89 89 return templ_7745c5c3_Err 90 90 } 91 91 var templ_7745c5c3_Var5 string 92 92 templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(sub.SubscribedPostURI) 93 93 if templ_7745c5c3_Err != nil { 94 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 23, Col: 101} 94 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 23, Col: 103} 95 95 } 96 96 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) 97 97 if templ_7745c5c3_Err != nil { 98 98 return templ_7745c5c3_Err 99 99 } 100 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a><div class=\"flex gap-4 items-center mt-2\"><button hx-delete=\"") 100 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7) 101 101 if templ_7745c5c3_Err != nil { 102 102 return templ_7745c5c3_Err 103 103 } 104 104 var templ_7745c5c3_Var6 string 105 - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/sub/%d", sub.ID)) 105 + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/sub/%s", sub.SubscriptionPostRkey)) 106 106 if templ_7745c5c3_Err != nil { 107 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 26, Col: 49} 107 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 27, Col: 68} 108 108 } 109 109 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) 110 110 if templ_7745c5c3_Err != nil { 111 111 return templ_7745c5c3_Err 112 112 } 113 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"delete\" hx-target=\"") 113 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8) 114 114 if templ_7745c5c3_Err != nil { 115 115 return templ_7745c5c3_Err 116 116 } 117 117 var templ_7745c5c3_Var7 string 118 - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("#sub%d", sub.ID)) 118 + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("#sub-%s", sub.SubscriptionPostRkey)) 119 119 if templ_7745c5c3_Err != nil { 120 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 28, Col: 48} 120 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `frontend/subscriptions.templ`, Line: 29, Col: 68} 121 121 } 122 122 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) 123 123 if templ_7745c5c3_Err != nil { 124 124 return templ_7745c5c3_Err 125 125 } 126 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"flex items-center border py-1 px-2 rounded-lg hover:bg-red-300\"><p class=\"text-sm\">Delete</p></button></div></li>") 126 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9) 127 127 if templ_7745c5c3_Err != nil { 128 128 return templ_7745c5c3_Err 129 129 } 130 130 } 131 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></section>") 131 + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 10) 132 132 if templ_7745c5c3_Err != nil { 133 133 return templ_7745c5c3_Err 134 134 }
+14 -10
frontend_handlers.go
··· 8 8 "io" 9 9 "log/slog" 10 10 "net/http" 11 - "strconv" 12 11 "strings" 13 12 14 13 "github.com/willdot/bskyfeedgen/frontend" ··· 70 69 handle = did 71 70 } 72 71 73 - slog.Info("sub id", "id", sub.ID) 74 - 75 72 uri := fmt.Sprintf("https://bsky.app/profile/%s/post/%s", handle, splitStr[4]) 76 73 sub.SubscribedPostURI = uri 77 74 subResp = append(subResp, sub) ··· 81 78 } 82 79 83 80 func (s *Server) HandleDeleteSubscription(w http.ResponseWriter, r *http.Request) { 84 - sub := r.PathValue("id") 81 + subRKey := r.PathValue("id") 85 82 86 - slog.Info("deleting sub", "sub", sub) 83 + slog.Info("deleting sub", "sub", subRKey) 87 84 88 85 didCookie, err := r.Cookie(didCookieName) 89 86 if err != nil { ··· 99 96 100 97 usersDid := didCookie.Value 101 98 102 - id, err := strconv.Atoi(sub) 99 + // id, err := strconv.Atoi(sub) 100 + // if err != nil { 101 + // slog.Error("failed to convert sub ID to int", "error", err) 102 + // http.Error(w, "invalid ID", http.StatusBadRequest) 103 + // return 104 + // } 105 + 106 + err = s.feeder.DeleteFeedPostsForSubscribedPostURIandUserDID(subRKey, usersDid) 103 107 if err != nil { 104 - slog.Error("failed to convert sub ID to int", "error", err) 105 - http.Error(w, "invalid ID", http.StatusBadRequest) 108 + slog.Error("delete feed posts for subscription and user", "error", err, "subscription URI", subRKey) 109 + http.Error(w, "failed to delete feed posts for subscription and user", http.StatusInternalServerError) 106 110 return 107 111 } 108 112 109 - err = s.feeder.DeleteSubscriptionByIdAndUser(usersDid, id) 113 + err = s.feeder.DeleteSubscriptionBySubRKeyAndUser(usersDid, subRKey) 110 114 if err != nil { 111 - slog.Error("delete subscription for user", "error", err, "subscription URI", sub) 115 + slog.Error("delete subscription for user", "error", err, "subscription RKey", subRKey) 112 116 http.Error(w, "failed to delete subscription", http.StatusInternalServerError) 113 117 return 114 118 }
+163
public/styles.css
··· 554 554 display: none; 555 555 } 556 556 557 + .absolute { 558 + position: absolute; 559 + } 560 + 557 561 .relative { 558 562 position: relative; 559 563 } ··· 562 566 position: sticky; 563 567 } 564 568 569 + .inset-x-0 { 570 + left: 0px; 571 + right: 0px; 572 + } 573 + 565 574 .top-0 { 566 575 top: 0px; 567 576 } 568 577 578 + .bottom-0 { 579 + bottom: 0px; 580 + } 581 + 569 582 .mx-auto { 570 583 margin-left: auto; 571 584 margin-right: auto; ··· 593 606 594 607 .mt-6 { 595 608 margin-top: 1.5rem; 609 + } 610 + 611 + .mt-1 { 612 + margin-top: 0.25rem; 613 + } 614 + 615 + .mt-4 { 616 + margin-top: 1rem; 596 617 } 597 618 598 619 .block { ··· 611 632 display: contents; 612 633 } 613 634 635 + .hidden { 636 + display: none; 637 + } 638 + 639 + .size-16 { 640 + width: 4rem; 641 + height: 4rem; 642 + } 643 + 614 644 .h-screen { 615 645 height: 100vh; 616 646 } 617 647 648 + .h-2 { 649 + height: 0.5rem; 650 + } 651 + 618 652 .w-3\/12 { 619 653 width: 25%; 620 654 } ··· 625 659 626 660 .w-full { 627 661 width: 100%; 662 + } 663 + 664 + .min-w-full { 665 + min-width: 100%; 628 666 } 629 667 630 668 .max-w-md { ··· 645 683 appearance: none; 646 684 } 647 685 686 + .flex-col-reverse { 687 + flex-direction: column-reverse; 688 + } 689 + 648 690 .items-center { 649 691 align-items: center; 650 692 } ··· 665 707 gap: 1rem; 666 708 } 667 709 710 + .divide-y > :not([hidden]) ~ :not([hidden]) { 711 + --tw-divide-y-reverse: 0; 712 + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); 713 + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); 714 + } 715 + 716 + .divide-y-2 > :not([hidden]) ~ :not([hidden]) { 717 + --tw-divide-y-reverse: 0; 718 + border-top-width: calc(2px * calc(1 - var(--tw-divide-y-reverse))); 719 + border-bottom-width: calc(2px * var(--tw-divide-y-reverse)); 720 + } 721 + 722 + .divide-gray-200 > :not([hidden]) ~ :not([hidden]) { 723 + --tw-divide-opacity: 1; 724 + border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); 725 + } 726 + 668 727 .overflow-hidden { 669 728 overflow: hidden; 670 729 } 671 730 731 + .overflow-x-auto { 732 + overflow-x: auto; 733 + } 734 + 735 + .whitespace-nowrap { 736 + white-space: nowrap; 737 + } 738 + 739 + .text-pretty { 740 + text-wrap: pretty; 741 + } 742 + 672 743 .rounded { 673 744 border-radius: 0.25rem; 674 745 } ··· 708 779 border-color: rgb(248 113 113 / var(--tw-border-opacity, 1)); 709 780 } 710 781 782 + .border-gray-100 { 783 + --tw-border-opacity: 1; 784 + border-color: rgb(243 244 246 / var(--tw-border-opacity, 1)); 785 + } 786 + 711 787 .border-t-zinc-200 { 712 788 --tw-border-opacity: 1; 713 789 border-top-color: rgb(228 228 231 / var(--tw-border-opacity, 1)); ··· 738 814 background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); 739 815 } 740 816 817 + .bg-gradient-to-r { 818 + background-image: linear-gradient(to right, var(--tw-gradient-stops)); 819 + } 820 + 821 + .from-green-300 { 822 + --tw-gradient-from: #86efac var(--tw-gradient-from-position); 823 + --tw-gradient-to: rgb(134 239 172 / 0) var(--tw-gradient-to-position); 824 + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); 825 + } 826 + 827 + .via-blue-500 { 828 + --tw-gradient-to: rgb(59 130 246 / 0) var(--tw-gradient-to-position); 829 + --tw-gradient-stops: var(--tw-gradient-from), #3b82f6 var(--tw-gradient-via-position), var(--tw-gradient-to); 830 + } 831 + 832 + .to-purple-600 { 833 + --tw-gradient-to: #9333ea var(--tw-gradient-to-position); 834 + } 835 + 836 + .object-cover { 837 + -o-object-fit: cover; 838 + object-fit: cover; 839 + } 840 + 741 841 .p-2 { 742 842 padding: 0.5rem; 743 843 } ··· 826 926 line-height: 1.25rem; 827 927 } 828 928 929 + .text-xs { 930 + font-size: 0.75rem; 931 + line-height: 1rem; 932 + } 933 + 829 934 .font-bold { 830 935 font-weight: 700; 831 936 } ··· 877 982 color: rgb(255 255 255 / var(--tw-text-opacity, 1)); 878 983 } 879 984 985 + .text-gray-600 { 986 + --tw-text-opacity: 1; 987 + color: rgb(75 85 99 / var(--tw-text-opacity, 1)); 988 + } 989 + 880 990 .antialiased { 881 991 -webkit-font-smoothing: antialiased; 882 992 -moz-osx-font-smoothing: grayscale; ··· 900 1010 box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 901 1011 } 902 1012 1013 + .shadow-sm { 1014 + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); 1015 + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); 1016 + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1017 + } 1018 + 903 1019 .ring-1 { 904 1020 --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); 905 1021 --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); ··· 941 1057 } 942 1058 943 1059 @media (min-width: 640px) { 1060 + .sm\:block { 1061 + display: block; 1062 + } 1063 + 1064 + .sm\:flex { 1065 + display: flex; 1066 + } 1067 + 1068 + .sm\:shrink-0 { 1069 + flex-shrink: 0; 1070 + } 1071 + 1072 + .sm\:justify-between { 1073 + justify-content: space-between; 1074 + } 1075 + 1076 + .sm\:gap-4 { 1077 + gap: 1rem; 1078 + } 1079 + 1080 + .sm\:gap-6 { 1081 + gap: 1.5rem; 1082 + } 1083 + 944 1084 .sm\:rounded-xl { 945 1085 border-radius: 0.75rem; 946 1086 } 947 1087 1088 + .sm\:p-6 { 1089 + padding: 1.5rem; 1090 + } 1091 + 948 1092 .sm\:px-8 { 949 1093 padding-left: 2rem; 950 1094 padding-right: 2rem; ··· 953 1097 .sm\:py-12 { 954 1098 padding-top: 3rem; 955 1099 padding-bottom: 3rem; 1100 + } 1101 + 1102 + .sm\:text-xl { 1103 + font-size: 1.25rem; 1104 + line-height: 1.75rem; 956 1105 } 957 1106 } 958 1107 ··· 981 1130 text-align: right; 982 1131 } 983 1132 } 1133 + 1134 + @media (min-width: 1024px) { 1135 + .lg\:p-8 { 1136 + padding: 2rem; 1137 + } 1138 + } 1139 + 1140 + .ltr\:text-left:where([dir="ltr"], [dir="ltr"] *) { 1141 + text-align: left; 1142 + } 1143 + 1144 + .rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) { 1145 + text-align: right; 1146 + }
+2 -1
server.go
··· 13 13 type Feeder interface { 14 14 GetFeed(ctx context.Context, userDID, feed, cursor string, limit int) (FeedReponse, error) 15 15 GetSubscriptionsForUser(ctx context.Context, userDID string) ([]store.Subscription, error) 16 - DeleteSubscriptionByIdAndUser(userDID string, id int) error 16 + DeleteSubscriptionBySubRKeyAndUser(userDID, rkey string) error 17 + DeleteFeedPostsForSubscribedPostURIandUserDID(subscribedPostURI, userDID string) error 17 18 } 18 19 19 20 type Server struct {
+6 -6
store/subscription.go
··· 97 97 } 98 98 99 99 func (s *Store) GetSubscriptionsForUser(ctx context.Context, userDID string) ([]Subscription, error) { 100 - sql := "SELECT id, subscribedPostURI FROM subscriptions WHERE userDID = ?;" 100 + sql := "SELECT id, subscribedPostURI, subscriptionPostRkey FROM subscriptions WHERE userDID = ?;" 101 101 rows, err := s.db.Query(sql, userDID) 102 102 if err != nil { 103 103 return nil, fmt.Errorf("run query to get subscribed posts for user: %w", err) ··· 107 107 var results []Subscription 108 108 for rows.Next() { 109 109 var subscription Subscription 110 - if err := rows.Scan(&subscription.ID, &subscription.SubscribedPostURI); err != nil { 110 + if err := rows.Scan(&subscription.ID, &subscription.SubscribedPostURI, &subscription.SubscriptionPostRkey); err != nil { 111 111 return nil, fmt.Errorf("scan row: %w", err) 112 112 } 113 113 ··· 116 116 return results, nil 117 117 } 118 118 119 - func (s *Store) DeleteSubscriptionByIdAndUser(userDID string, id int) error { 120 - sql := "DELETE FROM subscriptions WHERE id = ?;" 121 - _, err := s.db.Exec(sql, id) 119 + func (s *Store) DeleteSubscriptionBySubRKeyAndUser(userDID, rkey string) error { 120 + sql := "DELETE FROM subscriptions WHERE subscriptionPostRkey = ?;" 121 + _, err := s.db.Exec(sql, rkey) 122 122 if err != nil { 123 - return fmt.Errorf("exec delete subscription by id: %w", err) 123 + return fmt.Errorf("exec delete subscription by rkey: %w", err) 124 124 } 125 125 return nil 126 126 }