Compare commits
4 Commits
027b125285
...
f0846308da
Author | SHA1 | Date | |
---|---|---|---|
f0846308da | |||
b3b7fe6359 | |||
f0b541ee50 | |||
df9077e3c1 |
@ -35,8 +35,7 @@ class SettingsController < ApplicationController
|
||||
|
||||
if @user.avatar_new.present?
|
||||
if store_user_avatar
|
||||
LdapManager::UpdateAvatar.call(user: @user)
|
||||
XmppSetAvatarJob.perform_later(user: @user)
|
||||
UserManager::UpdateAvatar.call(user: @user)
|
||||
else
|
||||
@validation_errors = @user.errors
|
||||
render :show, status: :unprocessable_entity and return
|
||||
@ -193,7 +192,11 @@ class SettingsController < ApplicationController
|
||||
|
||||
def store_user_avatar
|
||||
io = @user.avatar_new.tempfile
|
||||
img_data = process_avatar(io)
|
||||
img_data = UserManager::ProcessAvatar.call(io: io)
|
||||
if img_data.blank?
|
||||
@user.errors.add(:avatar, "failed to process file")
|
||||
false
|
||||
end
|
||||
tempfile = Tempfile.create
|
||||
tempfile.binmode
|
||||
tempfile.write(img_data)
|
||||
@ -212,18 +215,4 @@ class SettingsController < ApplicationController
|
||||
@user.save
|
||||
end
|
||||
end
|
||||
|
||||
def process_avatar(io)
|
||||
processed = ImageProcessing::Vips
|
||||
.source(io)
|
||||
.resize_to_fill(400, 400)
|
||||
.saver(strip: true)
|
||||
.call
|
||||
io.rewind
|
||||
processed.read
|
||||
rescue Vips::Error => e
|
||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||
Rails.logger.error { "Image processing failed for avatar: #{e.message}" }
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
@ -4,19 +4,19 @@
|
||||
class BtcpayManagerService < RestApiService
|
||||
private
|
||||
|
||||
def base_url
|
||||
@base_url ||= "#{Setting.btcpay_api_url}/stores/#{Setting.btcpay_store_id}"
|
||||
end
|
||||
def base_url
|
||||
@base_url ||= "#{Setting.btcpay_api_url}/stores/#{Setting.btcpay_store_id}"
|
||||
end
|
||||
|
||||
def auth_token
|
||||
@auth_token ||= Setting.btcpay_auth_token
|
||||
end
|
||||
def auth_token
|
||||
@auth_token ||= Setting.btcpay_auth_token
|
||||
end
|
||||
|
||||
def headers
|
||||
{
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "token #{auth_token}"
|
||||
}
|
||||
end
|
||||
def headers
|
||||
{
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "token #{auth_token}"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
12
app/services/mastodon_manager/fetch_user.rb
Normal file
12
app/services/mastodon_manager/fetch_user.rb
Normal file
@ -0,0 +1,12 @@
|
||||
module MastodonManager
|
||||
class FetchUser < MastodonManagerService
|
||||
def initialize(mastodon_id:)
|
||||
@mastodon_id = mastodon_id
|
||||
end
|
||||
|
||||
def call
|
||||
user = get "v1/admin/accounts/#{@mastodon_id}"
|
||||
user.with_indifferent_access
|
||||
end
|
||||
end
|
||||
end
|
14
app/services/mastodon_manager/find_user.rb
Normal file
14
app/services/mastodon_manager/find_user.rb
Normal file
@ -0,0 +1,14 @@
|
||||
module MastodonManager
|
||||
class FindUser < MastodonManagerService
|
||||
def initialize(username:)
|
||||
@username = username
|
||||
end
|
||||
|
||||
def call
|
||||
users = get "v2/admin/accounts?username=#{@username}&origin=local"
|
||||
users = users.map { |u| u.with_indifferent_access }
|
||||
# Results may contain partial matches
|
||||
users.find { |u| u.dig(:username).downcase == @username.downcase }
|
||||
end
|
||||
end
|
||||
end
|
57
app/services/mastodon_manager/sync_account_profiles.rb
Normal file
57
app/services/mastodon_manager/sync_account_profiles.rb
Normal file
@ -0,0 +1,57 @@
|
||||
module MastodonManager
|
||||
class SyncAccountProfiles < MastodonManagerService
|
||||
def initialize(direction: "down", overwrite: false)
|
||||
@direction = direction
|
||||
@overwrite = overwrite
|
||||
|
||||
if @direction != "down"
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
def call
|
||||
Rails.logger.debug { "Syncing account profiles (direction: #{@direction}, overwrite: #{@overwrite})"}
|
||||
|
||||
User.find_each do |user|
|
||||
if user.mastodon_id.blank?
|
||||
mastodon_user = MastodonManager::FindUser.call username: user.cn
|
||||
if mastodon_user
|
||||
Rails.logger.debug { "Setting mastodon_id for user #{user.cn}" }
|
||||
user.update! mastodon_id: @user[:id]
|
||||
else
|
||||
Rails.logger.debug { "No Mastodon user found for username #{user.cn}" }
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
next if user.avatar.attached? && user.display_name.present?
|
||||
|
||||
unless mastodon_user
|
||||
Rails.logger.debug { "Fetching Mastodon account with ID #{user.mastodon_id} for #{user.cn}" }
|
||||
mastodon_user = MastodonManager::FetchUser.call mastodon_id: user.mastodon_id
|
||||
end
|
||||
|
||||
if user.display_name.blank?
|
||||
if mastodon_display_name = mastodon_user.dig(:account, :display_name)
|
||||
Rails.logger.debug { "Setting display name for user #{user.cn} from Mastodon" }
|
||||
LdapManager::UpdateDisplayName.call(
|
||||
dn: user.dn, display_name: mastodon_display_name
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if !user.avatar.attached?
|
||||
if avatar_url = mastodon_user.dig(:account, :avatar_static)
|
||||
Rails.logger.debug { "Importing Mastodon avatar for user #{user.cn}" }
|
||||
UserManager::ImportRemoteAvatar.call(
|
||||
user: user, avatar_url: avatar_url
|
||||
)
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||
Rails.logger.error e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -4,19 +4,19 @@
|
||||
class MastodonManagerService < RestApiService
|
||||
private
|
||||
|
||||
def base_url
|
||||
@base_url ||= "#{Setting.mastodon_public_url}/api"
|
||||
end
|
||||
def base_url
|
||||
@base_url ||= "#{Setting.mastodon_public_url}/api"
|
||||
end
|
||||
|
||||
def auth_token
|
||||
@auth_token ||= Setting.mastodon_auth_token
|
||||
end
|
||||
def auth_token
|
||||
@auth_token ||= Setting.mastodon_auth_token
|
||||
end
|
||||
|
||||
def headers
|
||||
{
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
}
|
||||
end
|
||||
def headers
|
||||
{
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -1,27 +1,27 @@
|
||||
class RestApiService < ApplicationService
|
||||
private
|
||||
|
||||
def base_url
|
||||
raise NotImplementedError
|
||||
end
|
||||
def base_url
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def headers
|
||||
raise NotImplementedError
|
||||
end
|
||||
def headers
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def endpoint_url(path)
|
||||
"#{base_url}/#{path.gsub(/^\//, '')}"
|
||||
end
|
||||
def endpoint_url(path)
|
||||
"#{base_url}/#{path.gsub(/^\//, '')}"
|
||||
end
|
||||
|
||||
def get(path, params = {})
|
||||
res = Faraday.get endpoint_url(path), params, headers
|
||||
# TODO handle unsuccessful responses with no valid JSON body
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
def get(path, params = {})
|
||||
res = Faraday.get endpoint_url(path), params, headers
|
||||
# TODO handle unsuccessful responses with no valid JSON body
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def post(path, payload)
|
||||
res = Faraday.post endpoint_url(path), payload.to_json, headers
|
||||
# TODO handle unsuccessful responses with no valid JSON body
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
def post(path, payload)
|
||||
res = Faraday.post endpoint_url(path), payload.to_json, headers
|
||||
# TODO handle unsuccessful responses with no valid JSON body
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
end
|
||||
|
42
app/services/user_manager/import_remote_avatar.rb
Normal file
42
app/services/user_manager/import_remote_avatar.rb
Normal file
@ -0,0 +1,42 @@
|
||||
module UserManager
|
||||
class ImportRemoteAvatar < UserManagerService
|
||||
def initialize(user:, avatar_url:)
|
||||
@user = user
|
||||
@avatar_url = avatar_url
|
||||
end
|
||||
|
||||
def call
|
||||
if import_remote_avatar
|
||||
UserManager::UpdateAvatar.call(user: @user)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def import_remote_avatar
|
||||
tempfile = Down.download(@avatar_url)
|
||||
content_type = tempfile.content_type
|
||||
unless %w[image/jpeg image/png].include?(content_type)
|
||||
Rails.logger.warn { "Wrong content type of remote avatar for user #{user.cn}: '#{content_type}'" }
|
||||
return false
|
||||
end
|
||||
|
||||
img_data = UserManager::ProcessAvatar.call(io: tempfile)
|
||||
tempfile = Tempfile.create
|
||||
tempfile.binmode
|
||||
tempfile.write(img_data)
|
||||
tempfile.rewind
|
||||
|
||||
hash = Digest::SHA256.hexdigest(img_data)
|
||||
ext = content_type == "image/png" ? "png" : "jpg"
|
||||
filename = "#{hash}.#{ext}"
|
||||
key = "users/#{@user.cn}/avatars/#{filename}"
|
||||
|
||||
@user.avatar.attach io: tempfile, key: key, filename: filename
|
||||
rescue => e
|
||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||
Rails.logger.warn "Importing remote avatar failed: \"#{e.message}\""
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
21
app/services/user_manager/process_avatar.rb
Normal file
21
app/services/user_manager/process_avatar.rb
Normal file
@ -0,0 +1,21 @@
|
||||
module UserManager
|
||||
class ProcessAvatar < UserManagerService
|
||||
def initialize(io:)
|
||||
@io = io
|
||||
end
|
||||
|
||||
def call
|
||||
processed = ImageProcessing::Vips
|
||||
.source(@io)
|
||||
.resize_to_fill(400, 400)
|
||||
.saver(strip: true)
|
||||
.call
|
||||
@io.rewind
|
||||
processed.read
|
||||
rescue Vips::Error => e
|
||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||
Rails.logger.warn { "Image processing failed for avatar: #{e.message}" }
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
15
app/services/user_manager/update_avatar.rb
Normal file
15
app/services/user_manager/update_avatar.rb
Normal file
@ -0,0 +1,15 @@
|
||||
module UserManager
|
||||
class UpdateAvatar < UserManagerService
|
||||
def initialize(user:)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def call
|
||||
LdapManager::UpdateAvatar.call(user: @user)
|
||||
|
||||
if Setting.ejabberd_enabled?
|
||||
XmppSetAvatarJob.perform_later(user: @user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -89,14 +89,42 @@
|
||||
</section>
|
||||
|
||||
<section class="sm:flex-1 sm:pt-0">
|
||||
<h3>LDAP</h3>
|
||||
<h3>Avatar</h3>
|
||||
<% if @user.avatar.attached? %>
|
||||
<table class="divided">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="align-top">Image</th>
|
||||
<td class="align-top">
|
||||
<%= image_tag image_url_for(@user.avatar), class: "h-20 w-20 rounded-lg" %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Content type</th>
|
||||
<td>
|
||||
<%= @user.avatar.content_type %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Size</th>
|
||||
<td>
|
||||
<%= number_to_human_size(@user.avatar.blob.byte_size) %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="text-gray-500">No avatar uploaded</p>
|
||||
<% end %>
|
||||
|
||||
<h3 class="mt-12">LDAP</h3>
|
||||
<table class="divided">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Avatar</th>
|
||||
<td>
|
||||
<% if @ldap_avatar.present? %>
|
||||
JPEG size: <%= @ldap_avatar.size %>
|
||||
JPEG size: <%= number_to_human_size(@ldap_avatar.size) %>
|
||||
<% else %>
|
||||
—
|
||||
<% end %>
|
||||
|
Loading…
x
Reference in New Issue
Block a user