add avatars
This commit is contained in:
parent
9c327957d9
commit
836ad72521
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ nginx.conf.compiled
|
|||||||
db.*.sqlite
|
db.*.sqlite
|
||||||
.vscode/
|
.vscode/
|
||||||
.local/
|
.local/
|
||||||
|
static/
|
||||||
|
8
app.lua
8
app.lua
@ -1,9 +1,17 @@
|
|||||||
local lapis = require("lapis")
|
local lapis = require("lapis")
|
||||||
local app = lapis.Application()
|
local app = lapis.Application()
|
||||||
|
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
app:enable("etlua")
|
app:enable("etlua")
|
||||||
app.layout = require "views.base"
|
app.layout = require "views.base"
|
||||||
|
|
||||||
|
local function inject_methods(req)
|
||||||
|
req.avatar_url = util.get_user_avatar_url
|
||||||
|
end
|
||||||
|
|
||||||
|
app:before_filter(inject_methods)
|
||||||
|
|
||||||
app:include("apps.users", {path = "/user"})
|
app:include("apps.users", {path = "/user"})
|
||||||
|
|
||||||
app:get("/", function()
|
app:get("/", function()
|
||||||
|
108
apps/users.lua
108
apps/users.lua
@ -3,12 +3,15 @@ local app = require("lapis").Application()
|
|||||||
local db = require("lapis.db")
|
local db = require("lapis.db")
|
||||||
local constants = require("constants")
|
local constants = require("constants")
|
||||||
|
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
local bcrypt = require("bcrypt")
|
local bcrypt = require("bcrypt")
|
||||||
local rand = require("openssl.rand")
|
local rand = require("openssl.rand")
|
||||||
|
|
||||||
local models = require("models")
|
local models = require("models")
|
||||||
local Users = models.Users
|
local Users = models.Users
|
||||||
local Sessions = models.Sessions
|
local Sessions = models.Sessions
|
||||||
|
local Avatars = models.Avatars
|
||||||
|
|
||||||
local TransientUser = {
|
local TransientUser = {
|
||||||
is_admin = function (self)
|
is_admin = function (self)
|
||||||
@ -76,6 +79,10 @@ local function validate_username(username)
|
|||||||
return username:match("^[%w_-]+$") and true
|
return username:match("^[%w_-]+$") and true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function validate_url(url)
|
||||||
|
return url:match('^https?://.+$') and true
|
||||||
|
end
|
||||||
|
|
||||||
app:get("user", "/:username", function(self)
|
app:get("user", "/:username", function(self)
|
||||||
local user = Users:find({username = self.params.username})
|
local user = Users:find({username = self.params.username})
|
||||||
if not user then
|
if not user then
|
||||||
@ -101,6 +108,107 @@ app:get("user", "/:username", function(self)
|
|||||||
return {render = "user.user"}
|
return {render = "user.user"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
app:post("user_clear_avatar", "/:username/clear_avatar", function(self)
|
||||||
|
local me = validate_session(self.session.session_key)
|
||||||
|
if me == nil then
|
||||||
|
self.session.flash = {error = "You must be logged in to perform this action."}
|
||||||
|
return {redirect_to = self:url_for("user_login")}
|
||||||
|
end
|
||||||
|
local target_user = Users:find({username = self.params.username})
|
||||||
|
if me.id ~= target_user.id then
|
||||||
|
return {redirect_to = self:url_for("user", {username = self.params.username})}
|
||||||
|
end
|
||||||
|
target_user:update({
|
||||||
|
avatar_id = db.NULL,
|
||||||
|
})
|
||||||
|
self.session.flash = {success = true, msg = "Avatar cleared."}
|
||||||
|
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||||
|
end)
|
||||||
|
|
||||||
|
app:post("user_set_avatar", "/:username/set_avatar", function(self)
|
||||||
|
local me = validate_session(self.session.session_key)
|
||||||
|
if me == nil then
|
||||||
|
self.session.flash = {error = "You must be logged in to perform this action."}
|
||||||
|
return {redirect_to = self:url_for("user_login")}
|
||||||
|
end
|
||||||
|
local target_user = Users:find({username = self.params.username})
|
||||||
|
if me.id ~= target_user.id then
|
||||||
|
return {redirect_to = self:url_for("user", {username = self.params.username})}
|
||||||
|
end
|
||||||
|
local file = self.params.avatar
|
||||||
|
if not file then
|
||||||
|
self.session.flash = {error = "Something went wrong. Try again later."}
|
||||||
|
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||||
|
end
|
||||||
|
local time = os.time()
|
||||||
|
local filename = "u" .. target_user.id .. "d" .. time .. ".webp"
|
||||||
|
local proxied_filename = "/avatars/" .. filename
|
||||||
|
local save_path = "static" .. proxied_filename
|
||||||
|
local res = util.validate_and_create_image(file.content, save_path)
|
||||||
|
if not res then
|
||||||
|
self.session.flash = {error = "Something went wrong. Try again later."}
|
||||||
|
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||||
|
end
|
||||||
|
|
||||||
|
self.session.flash = {success = true, msg = "Avatar updated."}
|
||||||
|
local avatar = Avatars:create({
|
||||||
|
file_path = proxied_filename,
|
||||||
|
uploaded_at = time,
|
||||||
|
})
|
||||||
|
|
||||||
|
target_user:update({
|
||||||
|
avatar_id = avatar.id
|
||||||
|
})
|
||||||
|
|
||||||
|
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||||
|
end)
|
||||||
|
|
||||||
|
app:get("user_settings", "/:username/settings", function(self)
|
||||||
|
local me = validate_session(self.session.session_key)
|
||||||
|
if me == nil then
|
||||||
|
self.session.flash = {error = "You must be logged in to perform this action."}
|
||||||
|
return {redirect_to = self:url_for("user_login")}
|
||||||
|
end
|
||||||
|
local target_user = Users:find({username = self.params.username})
|
||||||
|
if me.id ~= target_user.id then
|
||||||
|
return {redirect_to = self:url_for("user", {username = self.params.username})}
|
||||||
|
end
|
||||||
|
if self.session.flash then
|
||||||
|
local flash = self.session.flash
|
||||||
|
self.session.flash = nil
|
||||||
|
if flash.success then
|
||||||
|
self.flash_msg = flash.msg
|
||||||
|
elseif flash.error then
|
||||||
|
self.flash_msg = flash.error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.user = target_user
|
||||||
|
return {render = "user.settings"}
|
||||||
|
end)
|
||||||
|
|
||||||
|
app:post("user_settings", "/:username/settings", function(self)
|
||||||
|
local me = validate_session(self.session.session_key)
|
||||||
|
if me == nil then
|
||||||
|
self.session.flash = {error = "You must be logged in to perform this action."}
|
||||||
|
return {redirect_to = self:url_for("user_login")}
|
||||||
|
end
|
||||||
|
local target_user = Users:find({username = self.params.username})
|
||||||
|
if me.id ~= target_user.id then
|
||||||
|
return {redirect_to = self:url_for("user", {username = self.params.username})}
|
||||||
|
end
|
||||||
|
|
||||||
|
local status = self.params.status:sub(1, 100)
|
||||||
|
|
||||||
|
target_user:update({
|
||||||
|
status = status,
|
||||||
|
})
|
||||||
|
self.session.flash = {
|
||||||
|
success = true,
|
||||||
|
msg = "Settings updated."
|
||||||
|
}
|
||||||
|
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||||
|
end)
|
||||||
|
|
||||||
app:get("user_login", "/login", function(self)
|
app:get("user_login", "/login", function(self)
|
||||||
if self.session.session_key then
|
if self.session.session_key then
|
||||||
local user = validate_session(self.session.session_key)
|
local user = validate_session(self.session.session_key)
|
||||||
|
@ -18,5 +18,15 @@ return {
|
|||||||
|
|
||||||
[2] = function ()
|
[2] = function ()
|
||||||
schema.add_column("users", "confirmed_on", types.integer{null = true})
|
schema.add_column("users", "confirmed_on", types.integer{null = true})
|
||||||
end
|
end,
|
||||||
|
|
||||||
|
[3] = function ()
|
||||||
|
schema.add_column("users", "status", types.text{null = true, default=""})
|
||||||
|
schema.create_table("avatars", {
|
||||||
|
{"id", types.integer{primary_key = true}},
|
||||||
|
{"file_path", types.text{unique = true}},
|
||||||
|
{"uploaded_at", "INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP))"},
|
||||||
|
})
|
||||||
|
schema.add_column("users", "avatar_id", "REFERENCES avatars(id) ON DELETE SET NULL")
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,10 @@ function Users_mt:is_admin()
|
|||||||
return self.permission == constants.PermissionLevel.ADMIN
|
return self.permission == constants.PermissionLevel.ADMIN
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Users_mt:is_default_avatar()
|
||||||
|
return self.avatar_id == nil
|
||||||
|
end
|
||||||
|
|
||||||
local ret = {
|
local ret = {
|
||||||
Users = Users,
|
Users = Users,
|
||||||
Topics = Model:extend("topics"),
|
Topics = Model:extend("topics"),
|
||||||
@ -19,6 +23,7 @@ local ret = {
|
|||||||
Posts = Model:extend("posts"),
|
Posts = Model:extend("posts"),
|
||||||
PostHistory = Model:extend("post_history"),
|
PostHistory = Model:extend("post_history"),
|
||||||
Sessions = Model:extend("sessions"),
|
Sessions = Model:extend("sessions"),
|
||||||
|
Avatars = Model:extend("avatars"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -32,5 +32,10 @@ http {
|
|||||||
location /favicon.ico {
|
location /favicon.ico {
|
||||||
alias static/favicon.ico;
|
alias static/favicon.ico;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /avatars {
|
||||||
|
alias static/avatars;
|
||||||
|
expires 1y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
util.lua
Normal file
45
util.lua
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
local util = {}
|
||||||
|
local magick = require("magick")
|
||||||
|
|
||||||
|
local Avatars = require("models").Avatars
|
||||||
|
|
||||||
|
function util.get_user_avatar_url(req, user)
|
||||||
|
if not user.avatar_id then
|
||||||
|
return "/avatars/default.webp"
|
||||||
|
end
|
||||||
|
return Avatars:find(user.avatar_id).file_path
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.validate_and_create_image(input_image, filename)
|
||||||
|
local img = magick.load_image_from_blob(input_image)
|
||||||
|
|
||||||
|
if not img then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
img:strip()
|
||||||
|
img:set_gravity("CenterGravity")
|
||||||
|
|
||||||
|
local width, height = img:get_width(), img:get_height()
|
||||||
|
local min_dim = math.min(width, height)
|
||||||
|
if min_dim > 256 then
|
||||||
|
local ratio = 256.0 / min_dim
|
||||||
|
local new_w, new_h = width * ratio, height * ratio
|
||||||
|
img:resize(new_w, new_h)
|
||||||
|
end
|
||||||
|
|
||||||
|
width, height = img:get_width(), img:get_height()
|
||||||
|
local crop_size = math.min(width, height)
|
||||||
|
local x_offset = (width - crop_size) / 2
|
||||||
|
local y_offset = (height - crop_size) / 2
|
||||||
|
img:crop(crop_size, crop_size, x_offset, y_offset)
|
||||||
|
|
||||||
|
img:set_format("webp")
|
||||||
|
img:set_quality(85)
|
||||||
|
|
||||||
|
img:write(filename)
|
||||||
|
img:destroy()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return util
|
18
views/user/settings.etlua
Normal file
18
views/user/settings.etlua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<h1>User settings</h1>
|
||||||
|
<% if flash_msg then %>
|
||||||
|
<h2><%= flash_msg %></h2>
|
||||||
|
<% end %>
|
||||||
|
<form method="post" action="<%= url_for("user_set_avatar", {username = user.username}) %>" enctype="multipart/form-data">
|
||||||
|
<img src="<%= avatar_url(user) %>"><br>
|
||||||
|
<input type="file" name="avatar" accept="image/*"><br>
|
||||||
|
<input type="submit" value="Update avatar">
|
||||||
|
<% if not user:is_default_avatar() then %>
|
||||||
|
<input type="submit" value="Clear avatar" formaction="<%= url_for("user_clear_avatar", {username = user.username}) %>">
|
||||||
|
<% end %>
|
||||||
|
<br>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="">
|
||||||
|
<label for="status">Status</label>
|
||||||
|
<input type="text" id="status" name="status" value="<%= user.status %>" maxlength="10"><br>
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
</form>
|
@ -1,6 +1,7 @@
|
|||||||
<% if just_logged_in then %>
|
<% if just_logged_in then %>
|
||||||
<h1>Logged in successfully.</h1>
|
<h1>Logged in successfully.</h1>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<img src="<%= avatar_url(user) %>">
|
||||||
<h1><%= user.username %></h1>
|
<h1><%= user.username %></h1>
|
||||||
<% if user:is_guest() and user_is_me then %>
|
<% if user:is_guest() and user_is_me then %>
|
||||||
<h2>You are a guest. An administrator needs to approve your account before you will be able to post.</h2>
|
<h2>You are a guest. An administrator needs to approve your account before you will be able to post.</h2>
|
||||||
|
Loading…
Reference in New Issue
Block a user