add topics
This commit is contained in:
		
							
								
								
									
										5
									
								
								app.lua
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								app.lua
									
									
									
									
									
								
							| @@ -8,11 +8,16 @@ app.layout = require "views.base" | ||||
|  | ||||
| local function inject_methods(req) | ||||
|   req.avatar_url = util.get_user_avatar_url | ||||
|   req.ntob = function(_, v) | ||||
|     return util.ntob(v) | ||||
|   end | ||||
| end | ||||
|  | ||||
| app:before_filter(inject_methods) | ||||
|  | ||||
| app:include("apps.users", {path = "/user"}) | ||||
| app:include("apps.topics", {path = "/topics"}) | ||||
| app:include("apps.threads", {path = "/threads"}) | ||||
|  | ||||
| app:get("/", function() | ||||
|   return "Welcome to Lapis " .. require("lapis.version") | ||||
|   | ||||
							
								
								
									
										7
									
								
								apps/threads.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								apps/threads.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| local app = require("lapis").Application() | ||||
|  | ||||
| app:get("thread_create", "/create", function(self) | ||||
|  | ||||
| end) | ||||
|  | ||||
| return app | ||||
							
								
								
									
										125
									
								
								apps/topics.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								apps/topics.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| local app = require("lapis").Application() | ||||
| local lapis_util = require("lapis.util") | ||||
|  | ||||
| local db = require("lapis.db") | ||||
| local constants = require("constants") | ||||
|  | ||||
| local util = require("util") | ||||
|  | ||||
| local models = require("models") | ||||
| local Users = models.Users | ||||
| local Avatars = models.Avatars | ||||
| local Topics = models.Topics | ||||
| local Threads = models.Threads | ||||
|  | ||||
| local ThreadCreateError = { | ||||
|   OK = 0, | ||||
|   GUEST = 1, | ||||
|   LOGGED_OUT = 2, | ||||
|   TOPIC_LOCKED = 3, | ||||
| } | ||||
|  | ||||
| app:get("all_topics", "/", function(self) | ||||
|   self.topic_list = db.query("select * from topics limit 25;") | ||||
|   self.user = util.get_logged_in_user(self) or util.TransientUser | ||||
|   return {render = "topics.topics"} | ||||
| end) | ||||
|  | ||||
| app:get("topic_create", "/create", function(self) | ||||
|   local user = util.get_logged_in_user(self) or util.TransientUser | ||||
|   if not user:is_admin() then | ||||
|     return {status = 403} | ||||
|   end | ||||
|  | ||||
|   return {render = "topics.create"} | ||||
| end) | ||||
|  | ||||
| app:post("topic_create", "/create", function(self) | ||||
|   local user = util.get_logged_in_user(self) or util.TransientUser | ||||
|   if not user:is_admin() then | ||||
|     return {redirect_to = "all_topics"} | ||||
|   end | ||||
|  | ||||
|   local topic_name = lapis_util.trim(self.params.name) | ||||
|   local topic_description = self.params.description | ||||
|   local time = os.time() | ||||
|   local slug = lapis_util.slugify(topic_name) .. "-" .. time | ||||
|  | ||||
|   local topic = Topics:create({ | ||||
|     name = topic_name, | ||||
|     description = topic_description, | ||||
|     slug = slug, | ||||
|   }) | ||||
|  | ||||
|   return {redirect_to = self:url_for("all_topics")} | ||||
| end) | ||||
|  | ||||
| app:get("topic", "/:slug", function(self) | ||||
|   local topic = Topics:find({ | ||||
|     slug = self.params.slug | ||||
|   }) | ||||
|   if not topic then | ||||
|     return {status = 404} | ||||
|   end | ||||
|   self.topic = topic | ||||
|   self.threads_list = Threads:select(db.clause({ | ||||
|     topic_id = topic.id | ||||
|   })) | ||||
|   local user = util.get_logged_in_user_or_transient(self) | ||||
|   print(topic.is_locked, type(topic.is_locked)) | ||||
|   self.user = user | ||||
|   self.ThreadCreateError = ThreadCreateError | ||||
|   self.thread_create_error = ThreadCreateError.OK | ||||
|   if user:is_logged_in_guest() then | ||||
|     self.thread_create_error = ThreadCreateError.GUEST | ||||
|   elseif user:is_guest() then | ||||
|     self.thread_create_error = ThreadCreateError.LOGGED_OUT | ||||
|   elseif util.ntob(topic.is_locked) and not user:is_admin() then | ||||
|     self.thread_create_error = ThreadCreateError.TOPIC_LOCKED | ||||
|   end | ||||
|    | ||||
|   return {render = "topics.topic"} | ||||
| end) | ||||
|  | ||||
| app:get("topic_edit", "/:slug/edit", function(self) | ||||
|   local user = util.get_logged_in_user_or_transient(self) | ||||
|   if not user:is_admin() then | ||||
|     return {redirect_to = self:url_for("topic", {slug = self.params.slug})} | ||||
|   end | ||||
|   local topic = Topics:find({ | ||||
|     slug = self.params.slug | ||||
|   }) | ||||
|   if not topic then | ||||
|     return {redirect_to = self:url_for("all_topics")} | ||||
|   end | ||||
|   self.topic = topic | ||||
|   return {render = "topics.edit"} | ||||
| end) | ||||
|  | ||||
| app:post("topic_edit", "/:slug/edit", function(self) | ||||
|   local user = util.get_logged_in_user_or_transient(self) | ||||
|   if not user:is_admin() then | ||||
|     return {redirect_to = self:url_for("topic", {slug = self.params.slug})} | ||||
|   end | ||||
|   local topic = Topics:find({ | ||||
|     slug = self.params.slug | ||||
|   }) | ||||
|   if not topic then | ||||
|     return {redirect_to = self:url_for("all_topics")} | ||||
|   end | ||||
|   local name = self.params.name or topic.name | ||||
|   local description = self.params.description or topic.description | ||||
|   local is_locked = topic.is_locked | ||||
|   if self.params.is_locked ~= nil then | ||||
|     is_locked = util.form_bool_to_sqlite(self.params.is_locked) | ||||
|   end | ||||
|  | ||||
|   topic:update({ | ||||
|     name = name, | ||||
|     description = description, | ||||
|     is_locked = is_locked, | ||||
|   }) | ||||
|   return {redirect_to = self:url_for("topic", {slug = self.params.slug})} | ||||
| end) | ||||
|  | ||||
| return app | ||||
| @@ -13,15 +13,6 @@ local Users = models.Users | ||||
| local Sessions = models.Sessions | ||||
| local Avatars = models.Avatars | ||||
|  | ||||
| local TransientUser = { | ||||
|   is_admin = function (self) | ||||
|     return false | ||||
|   end, | ||||
|   is_guest = function (self) | ||||
|     return true | ||||
|   end | ||||
| } | ||||
|  | ||||
| local function authenticate_user(user, password) | ||||
|   return bcrypt.verify(password, user.password_hash) | ||||
| end | ||||
| @@ -81,7 +72,7 @@ app:get("user", "/:username", function(self) | ||||
|   end | ||||
|  | ||||
|   -- local me = validate_session(self.session.session_key) or TransientUser | ||||
|   local me = util.get_logged_in_user(self) or TransientUser | ||||
|   local me = util.get_logged_in_user(self) or util.TransientUser | ||||
|   self.user = user | ||||
|   self.me = me | ||||
|  | ||||
|   | ||||
| @@ -29,4 +29,16 @@ return { | ||||
|     }) | ||||
|     schema.add_column("users", "avatar_id", "REFERENCES avatars(id) ON DELETE SET NULL") | ||||
|   end, | ||||
|  | ||||
|   [4] = function () | ||||
|     schema.add_column("topics", "description", types.text{default=""}) | ||||
|  | ||||
|     -- topic locked = no new threads can be created in the topic, but posts can be made in threads | ||||
|     -- thread locked = no new posts can be created in the thread, existing posts can not be edited | ||||
|     -- admins bypass both restrictions | ||||
|     schema.add_column("topics", "is_locked", "BOOLEAN DEFAULT FALSE") | ||||
|     schema.add_column("threads", "is_locked", "BOOLEAN DEFAULT FALSE") | ||||
|     -- will appear on top of non-stickied threads in topic view | ||||
|     schema.add_column("threads", "is_stickied", "BOOLEAN DEFAULT FALSE") | ||||
|   end, | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,10 @@ function Users_mt:is_admin() | ||||
|   return self.permission == constants.PermissionLevel.ADMIN | ||||
| end | ||||
|  | ||||
| function Users_mt:is_logged_in_guest() | ||||
|   return self:is_guest() and true | ||||
| end | ||||
|  | ||||
| function Users_mt:is_default_avatar() | ||||
|   return self.avatar_id == nil | ||||
| end | ||||
|   | ||||
							
								
								
									
										37
									
								
								util.lua
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								util.lua
									
									
									
									
									
								
							| @@ -5,6 +5,18 @@ local db = require("lapis.db") | ||||
| local Avatars = require("models").Avatars | ||||
| local Users = require("models").Users | ||||
|  | ||||
| util.TransientUser = { | ||||
|   is_admin = function (self) | ||||
|     return false | ||||
|   end, | ||||
|   is_guest = function (self) | ||||
|     return true | ||||
|   end, | ||||
|   is_logged_in_guest = function (self) | ||||
|     return false | ||||
|   end, | ||||
| } | ||||
|  | ||||
| function util.get_user_avatar_url(req, user) | ||||
|   if not user.avatar_id then | ||||
|     return "/avatars/default.webp" | ||||
| @@ -57,4 +69,29 @@ function util.get_logged_in_user(req) | ||||
|   return nil | ||||
| end | ||||
|  | ||||
| function util.get_logged_in_user_or_transient(req) | ||||
|   return util.get_logged_in_user(req) or util.TransientUser | ||||
| end | ||||
|  | ||||
| function util.ntob(v) | ||||
|   return v ~= 0 | ||||
| end | ||||
|  | ||||
| function util.bton(b) | ||||
|   return 1 and b or 0 | ||||
| end | ||||
|  | ||||
| function util.stob(s) | ||||
|   if s == "true" then | ||||
|     return true | ||||
|   end | ||||
|   if s == "false" then | ||||
|     return false | ||||
|   end | ||||
| end | ||||
|  | ||||
| function util.form_bool_to_sqlite(s) | ||||
|   return util.bton(util.stob(s)) | ||||
| end | ||||
|  | ||||
| return util | ||||
							
								
								
									
										6
									
								
								views/topics/create.etlua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								views/topics/create.etlua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <h1>Create topic</h1> | ||||
| <form method="post"> | ||||
|   <input type="text" name="name" id="name" placeholder="Topic name" required><br> | ||||
|   <textarea id="description" name="description" placeholder="Topic description" required></textarea><br> | ||||
|   <input type="submit" value="Create topic"> | ||||
| </form> | ||||
							
								
								
									
										12
									
								
								views/topics/edit.etlua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								views/topics/edit.etlua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <h1>Editing topic <%= topic.name %></h1> | ||||
| <form method="post"> | ||||
|   <input type="text" name="name" id="name" value="<%= topic.name %>" placeholder="Topic name" required><br> | ||||
|   <textarea id="description" name="description" value="<%= topic.description %>" placeholder="Topic description"></textarea><br> | ||||
|   <input type="checkbox" id="is_locked" name="is_locked" value="<%= ntob(topic.is_locked) %>"> | ||||
|   <label for="is_locked">Locked</label><br> | ||||
|   <input type="submit" value="Save changes"> | ||||
| </form> | ||||
| <form method="get" action="<%= url_for("topic", {slug = topic.slug}) %>"> | ||||
|   <input type="submit" value="Cancel"> | ||||
| </form> | ||||
| <i>Note: to preserve history, you cannot change the topic URL.</i> | ||||
							
								
								
									
										25
									
								
								views/topics/topic.etlua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								views/topics/topic.etlua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <h1><%= topic.name %></h1> | ||||
| <h2><%= topic.description %></h2> | ||||
| <% if #threads_list == 0 then %> | ||||
|   <p>There are no threads in this topic.</p> | ||||
| <% end %> | ||||
|  | ||||
| <% if thread_create_error == ThreadCreateError.OK then %> | ||||
| <a href=<%= url_for("thread_create", nil, {topic_id = topic.id}) %>>New thread</a> | ||||
| <% elseif thread_create_error == ThreadCreateError.GUEST then %> | ||||
| <p>Your account is still pending confirmation by an administrator. You are not able to create a new thread or post at this time.</p> | ||||
| <% elseif thread_create_error == ThreadCreateError.LOGGED_OUT then %> | ||||
| <p>Only logged in users can create threads. <a href="<%= url_for("user_signup") %>">Sign up</a> or <a href="<%= url_for("user_login")%>">log in</a> to create a thread.</p> | ||||
| <% else %> | ||||
| <p>This topic is locked.</p> | ||||
| <% end %> | ||||
|  | ||||
| <% if user:is_admin() then %> | ||||
|   <br> | ||||
|   <a href="<%= url_for("topic_edit", {slug = topic.slug}) %>">Edit topic</a> | ||||
|   <form method="post" action="<%= url_for("topic_edit", {slug = topic.slug}) %>"> | ||||
|   <input type="hidden" name="is_locked" value="<%= not ntob(topic.is_locked) %>"> | ||||
|   <p><%= "This topic is " .. (ntob(topic.is_locked) and "" or "un") .. "locked." %></p> | ||||
|   <input type="submit" id="lock" value="<%= ntob(topic.is_locked) and "Unlock" or "Lock" %>"> | ||||
|   </form> | ||||
| <% end %> | ||||
							
								
								
									
										16
									
								
								views/topics/topics.etlua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								views/topics/topics.etlua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <h1>Topics</h1> | ||||
|  | ||||
| <% if #topic_list == 0 then %> | ||||
| <p>There are no topics.</p> | ||||
| <% else %> | ||||
|   <ul> | ||||
|   <% for i, v in ipairs(topic_list) do %> | ||||
|     <li> | ||||
|     <a href=<%= v.slug %>><%= v.name %></a> - <%= v.description %> | ||||
|     </li> | ||||
|   <% end %> | ||||
| <% end %> | ||||
| </ul> | ||||
| <% if user:is_admin() then %> | ||||
| <a href="<%= url_for("topic_create") %>">Create new topic</a> | ||||
| <% end %> | ||||
		Reference in New Issue
	
	Block a user