From bd1ba6c087a6cdcdcfee3d8ab54c1bd2bbfb9dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Mon, 2 Jun 2025 20:54:36 +0300 Subject: [PATCH] add subscribing and unsubscribing to threads --- apps/threads.lua | 46 +++++++++++++++++++++++++++++- migrations.lua | 11 +++++++ models.lua | 1 + util.lua | 7 +++-- views/common/babycode-editor.etlua | 6 ++++ views/threads/thread.etlua | 45 +++++++++++++++++------------ 6 files changed, 94 insertions(+), 22 deletions(-) diff --git a/apps/threads.lua b/apps/threads.lua index a9107fa..74222a0 100644 --- a/apps/threads.lua +++ b/apps/threads.lua @@ -9,6 +9,7 @@ local models = require("models") local Topics = models.Topics local Threads = models.Threads local Posts = models.Posts +local Subscriptions = models.Subscriptions local POSTS_PER_PAGE = 10 @@ -97,7 +98,18 @@ app:get("thread", "/:slug", function(self) self.other_topics = db.query("SELECT topics.id, topics.name FROM topics") self.me = util.get_logged_in_user_or_transient(self) self.posts = posts - + + if self.me:is_logged_in() then + self.is_subscribed = false + local subscription = Subscriptions:find({user_id = self.me.id, thread_id = thread.id}) + if subscription then + self.is_subscribed = true + if posts[#posts].created_at > subscription.last_seen then + subscription:update({last_seen = os.time()}) + end + end + end + self.page_title = thread.title return {render = "threads.thread"} @@ -133,6 +145,10 @@ app:post("thread", "/:slug", function(self) return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"} end + if self.params.subscribe == "on" then + Subscriptions:create({user_id = user.id, thread_id = thread.id, last_seen = os.time()}) + end + return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"} end) @@ -206,4 +222,32 @@ app:post("thread_move", "/:slug/move", function(self) return {redirect_to = self:url_for("thread", {slug = self.params.slug})} end) +app:post("thread_subscribe", "/:slug/subscribe", function(self) + local user = util.get_logged_in_user(self) + if not user then + return {status = 403} + end + local thread = Threads:find({slug = self.params.slug}) + if not thread then + return {status = 404} + end + local subscription = Subscriptions:find({user_id = user.id, thread_id = thread.id}) + if self.params.subscribe == "subscribe" then + local now = os.time() + if subscription then + subscription:delete() + end + Subscriptions:create({user_id = user.id, thread_id = thread.id, last_seen = now}) + return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.first_visible_post})} + elseif self.params.subscribe == "unsubscribe" then + if not subscription then + return {status = 404} + end + subscription:delete() + return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.first_visible_post})} + end + + return {status = 400} +end) + return app diff --git a/migrations.lua b/migrations.lua index db7c9c4..e8727bb 100644 --- a/migrations.lua +++ b/migrations.lua @@ -102,4 +102,15 @@ return { db.query("CREATE INDEX idx_rate_limit_user_method ON api_rate_limits (user_id, method)") end, + + [13] = function () + schema.create_table("subscriptions", { + {"id", types.integer{primary_key = true}}, + {"user_id", "INTEGER REFERENCES users(id) ON DELETE CASCADE"}, + {"thread_id", "INTEGER REFERENCES threads(id) ON DELETE CASCADE"}, + {"last_seen", "INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP)) NOT NULL"}, + }) + + db.query("CREATE INDEX idx_subscription_user_thread ON subscriptions (user_id, thread_id)") + end, } diff --git a/models.lua b/models.lua index 24ee4e5..0678e15 100644 --- a/models.lua +++ b/models.lua @@ -40,6 +40,7 @@ local ret = { PostHistory = Model:extend("post_history"), Sessions = Model:extend("sessions"), Avatars = Model:extend("avatars"), + Subscriptions = Model:extend("subscriptions"), } return ret diff --git a/util.lua b/util.lua index c8ad50d..545bd12 100644 --- a/util.lua +++ b/util.lua @@ -105,13 +105,16 @@ function util.split_sentences(sentences, max_sentences) end ---@return string -function util.get_post_url(req, post_id) +function util.get_post_url(req, post_id, hash) + hash = hash ~= false local post = Posts:find({id = post_id}) if not post then return "" end local thread = Threads:find({id = post.thread_id}) if not thread then return "" end - return req:url_for("thread", {slug = thread.slug}, {after = post_id}) .. "#post-" .. post_id + local url = req:url_for("thread", {slug = thread.slug}, {after = post_id}) + if not hash then return url end + return url .. "#post-" .. post_id end function util.infobox_message(msg) diff --git a/views/common/babycode-editor.etlua b/views/common/babycode-editor.etlua index 0160bc5..21ba28c 100644 --- a/views/common/babycode-editor.etlua +++ b/views/common/babycode-editor.etlua @@ -6,6 +6,12 @@ %>
"> <% render ("views.common.babycode-editor-component", {ta_name = ta_name, prefill = prefill}) %> + <% if not cancel_url then %> + + + + + <% end %> <% if cancel_url then %> diff --git a/views/threads/thread.etlua b/views/threads/thread.etlua index fc32135..8b4ab9b 100644 --- a/views/threads/thread.etlua +++ b/views/threads/thread.etlua @@ -14,29 +14,36 @@ <% if is_stickied then %> • stickied, so it's probably important <% end %> - <% if can_lock then %>
- " method="post"> - - "> - - <% if me:is_mod() then %> -
" method="post"> - - "> -
-
" method="post"> - - - + <% if me:is_logged_in() then %> + " method="post"> + > + > + ">
<% end %> + <% if can_lock then %> +
" method="post"> + + "> +
+ <% if me:is_mod() then %> +
" method="post"> + + "> +
+
" method="post"> + + + +
+ <% end %> + <% end %>
- <% end %> <% for i, post in ipairs(posts) do %> <% render("views.threads.post", {post = post, render_sig = true, is_latest = i == #posts}) %>