forked from berserker/microblog
Conflicts: - `app/controllers/settings/preferences_controller.rb`: Upstream dropping `digest` from notifications emails while we have more notification emails settings. Removed `digest` from our list while keeping our extra settings. - `app/javascript/packs/admin.js`: Conflicts caused by glitch-soc's theming system. Applied the changes to `app/javascript/core/admin.js`. - `app/views/settings/preferences/other/show.html.haml`: Upstream removed a setting close to a glitch-soc-only setting. Applied upstream's change.main
commit
077183a121
72 changed files with 1796 additions and 830 deletions
@ -0,0 +1,99 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController |
||||
include Authorization |
||||
include AccountableConcern |
||||
|
||||
LIMIT = 100 |
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:canonical_email_blocks' }, only: [:index, :show, :test] |
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:canonical_email_blocks' }, except: [:index, :show, :test] |
||||
|
||||
before_action :set_canonical_email_blocks, only: :index |
||||
before_action :set_canonical_email_blocks_from_test, only: [:test] |
||||
before_action :set_canonical_email_block, only: [:show, :destroy] |
||||
|
||||
after_action :verify_authorized |
||||
after_action :insert_pagination_headers, only: :index |
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze |
||||
|
||||
def index |
||||
authorize :canonical_email_block, :index? |
||||
render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer |
||||
end |
||||
|
||||
def show |
||||
authorize @canonical_email_block, :show? |
||||
render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer |
||||
end |
||||
|
||||
def test |
||||
authorize :canonical_email_block, :test? |
||||
render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer |
||||
end |
||||
|
||||
def create |
||||
authorize :canonical_email_block, :create? |
||||
|
||||
@canonical_email_block = CanonicalEmailBlock.create!(resource_params) |
||||
log_action :create, @canonical_email_block |
||||
|
||||
render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer |
||||
end |
||||
|
||||
def destroy |
||||
authorize @canonical_email_block, :destroy? |
||||
|
||||
@canonical_email_block.destroy! |
||||
log_action :destroy, @canonical_email_block |
||||
|
||||
render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer |
||||
end |
||||
|
||||
private |
||||
|
||||
def resource_params |
||||
params.permit(:canonical_email_hash, :email) |
||||
end |
||||
|
||||
def set_canonical_email_blocks |
||||
@canonical_email_blocks = CanonicalEmailBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) |
||||
end |
||||
|
||||
def set_canonical_email_blocks_from_test |
||||
@canonical_email_blocks = CanonicalEmailBlock.matching_email(params[:email]) |
||||
end |
||||
|
||||
def set_canonical_email_block |
||||
@canonical_email_block = CanonicalEmailBlock.find(params[:id]) |
||||
end |
||||
|
||||
def insert_pagination_headers |
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
|
||||
def next_path |
||||
api_v1_admin_canonical_email_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue? |
||||
end |
||||
|
||||
def prev_path |
||||
api_v1_admin_canonical_email_blocks_url(pagination_params(min_id: pagination_since_id)) unless @canonical_email_blocks.empty? |
||||
end |
||||
|
||||
def pagination_max_id |
||||
@canonical_email_blocks.last.id |
||||
end |
||||
|
||||
def pagination_since_id |
||||
@canonical_email_blocks.first.id |
||||
end |
||||
|
||||
def records_continue? |
||||
@canonical_email_blocks.size == limit_param(LIMIT) |
||||
end |
||||
|
||||
def pagination_params(core_params) |
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) |
||||
end |
||||
end |
@ -0,0 +1,90 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController |
||||
include Authorization |
||||
include AccountableConcern |
||||
|
||||
LIMIT = 100 |
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:email_domain_blocks' }, only: [:index, :show] |
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:email_domain_blocks' }, except: [:index, :show] |
||||
before_action :set_email_domain_blocks, only: :index |
||||
before_action :set_email_domain_block, only: [:show, :destroy] |
||||
|
||||
after_action :verify_authorized |
||||
after_action :insert_pagination_headers, only: :index |
||||
|
||||
PAGINATION_PARAMS = %i( |
||||
limit |
||||
).freeze |
||||
|
||||
def create |
||||
authorize :email_domain_block, :create? |
||||
|
||||
@email_domain_block = EmailDomainBlock.create!(resource_params) |
||||
log_action :create, @email_domain_block |
||||
|
||||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer |
||||
end |
||||
|
||||
def index |
||||
authorize :email_domain_block, :index? |
||||
render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer |
||||
end |
||||
|
||||
def show |
||||
authorize @email_domain_block, :show? |
||||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer |
||||
end |
||||
|
||||
def destroy |
||||
authorize @email_domain_block, :destroy? |
||||
|
||||
@email_domain_block.destroy! |
||||
log_action :destroy, @email_domain_block |
||||
|
||||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_email_domain_blocks |
||||
@email_domain_blocks = EmailDomainBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) |
||||
end |
||||
|
||||
def set_email_domain_block |
||||
@email_domain_block = EmailDomainBlock.find(params[:id]) |
||||
end |
||||
|
||||
def resource_params |
||||
params.permit(:domain) |
||||
end |
||||
|
||||
def insert_pagination_headers |
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
|
||||
def next_path |
||||
api_v1_admin_email_domain_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue? |
||||
end |
||||
|
||||
def prev_path |
||||
api_v1_admin_email_domain_blocks_url(pagination_params(min_id: pagination_since_id)) unless @email_domain_blocks.empty? |
||||
end |
||||
|
||||
def pagination_max_id |
||||
@email_domain_blocks.last.id |
||||
end |
||||
|
||||
def pagination_since_id |
||||
@email_domain_blocks.first.id |
||||
end |
||||
|
||||
def records_continue? |
||||
@email_domain_blocks.size == limit_param(LIMIT) |
||||
end |
||||
|
||||
def pagination_params(core_params) |
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) |
||||
end |
||||
end |
@ -0,0 +1,99 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::Admin::IpBlocksController < Api::BaseController |
||||
include Authorization |
||||
include AccountableConcern |
||||
|
||||
LIMIT = 100 |
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:ip_blocks' }, only: [:index, :show] |
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:ip_blocks' }, except: [:index, :show] |
||||
before_action :set_ip_blocks, only: :index |
||||
before_action :set_ip_block, only: [:show, :update, :destroy] |
||||
|
||||
after_action :verify_authorized |
||||
after_action :insert_pagination_headers, only: :index |
||||
|
||||
PAGINATION_PARAMS = %i( |
||||
limit |
||||
).freeze |
||||
|
||||
def create |
||||
authorize :ip_block, :create? |
||||
|
||||
@ip_block = IpBlock.create!(resource_params) |
||||
log_action :create, @ip_block |
||||
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer |
||||
end |
||||
|
||||
def index |
||||
authorize :ip_block, :index? |
||||
render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer |
||||
end |
||||
|
||||
def show |
||||
authorize @ip_block, :show? |
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer |
||||
end |
||||
|
||||
def update |
||||
authorize @ip_block, :update? |
||||
|
||||
@ip_block.update(resource_params) |
||||
log_action :update, @ip_block |
||||
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer |
||||
end |
||||
|
||||
def destroy |
||||
authorize @ip_block, :destroy? |
||||
|
||||
@ip_block.destroy! |
||||
log_action :destroy, @ip_block |
||||
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_ip_blocks |
||||
@ip_blocks = IpBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) |
||||
end |
||||
|
||||
def set_ip_block |
||||
@ip_block = IpBlock.find(params[:id]) |
||||
end |
||||
|
||||
def resource_params |
||||
params.permit(:ip, :severity, :comment, :expires_in) |
||||
end |
||||
|
||||
def insert_pagination_headers |
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
|
||||
def next_path |
||||
api_v1_admin_ip_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue? |
||||
end |
||||
|
||||
def prev_path |
||||
api_v1_admin_ip_blocks_url(pagination_params(min_id: pagination_since_id)) unless @ip_blocks.empty? |
||||
end |
||||
|
||||
def pagination_max_id |
||||
@ip_blocks.last.id |
||||
end |
||||
|
||||
def pagination_since_id |
||||
@ip_blocks.first.id |
||||
end |
||||
|
||||
def records_continue? |
||||
@ip_blocks.size == limit_param(LIMIT) |
||||
end |
||||
|
||||
def pagination_params(core_params) |
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) |
||||
end |
||||
end |
@ -1,27 +0,0 @@ |
||||
export default () => new Promise((resolve, reject) => { |
||||
// ServiceWorker is required to synchronize the login state.
|
||||
// Microsoft Edge 17 does not support getAll according to:
|
||||
// Catalog of standard and vendor APIs across browsers - Microsoft Edge Development
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/catalog/?q=specName%3Aindexeddb
|
||||
if (!('caches' in self && 'getAll' in IDBObjectStore.prototype)) { |
||||
reject(); |
||||
return; |
||||
} |
||||
|
||||
const request = indexedDB.open('mastodon'); |
||||
|
||||
request.onerror = reject; |
||||
request.onsuccess = ({ target }) => resolve(target.result); |
||||
|
||||
request.onupgradeneeded = ({ target }) => { |
||||
const accounts = target.result.createObjectStore('accounts', { autoIncrement: true }); |
||||
const statuses = target.result.createObjectStore('statuses', { autoIncrement: true }); |
||||
|
||||
accounts.createIndex('id', 'id', { unique: true }); |
||||
accounts.createIndex('moved', 'moved'); |
||||
|
||||
statuses.createIndex('id', 'id', { unique: true }); |
||||
statuses.createIndex('account', 'account'); |
||||
statuses.createIndex('reblog', 'reblog'); |
||||
}; |
||||
}); |
@ -1,211 +0,0 @@ |
||||
import openDB from './db'; |
||||
|
||||
const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static']; |
||||
const storageMargin = 8388608; |
||||
const storeLimit = 1024; |
||||
|
||||
// navigator.storage is not present on:
|
||||
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.100 Safari/537.36 Edge/16.16299
|
||||
// estimate method is not present on Chrome 57.0.2987.98 on Linux.
|
||||
export const storageFreeable = 'storage' in navigator && 'estimate' in navigator.storage; |
||||
|
||||
function openCache() { |
||||
// ServiceWorker and Cache API is not available on iOS 11
|
||||
// https://webkit.org/status/#specification-service-workers
|
||||
return self.caches ? caches.open('mastodon-system') : Promise.reject(); |
||||
} |
||||
|
||||
function printErrorIfAvailable(error) { |
||||
if (error) { |
||||
console.warn(error); |
||||
} |
||||
} |
||||
|
||||
function put(name, objects, onupdate, oncreate) { |
||||
return openDB().then(db => (new Promise((resolve, reject) => { |
||||
const putTransaction = db.transaction(name, 'readwrite'); |
||||
const putStore = putTransaction.objectStore(name); |
||||
const putIndex = putStore.index('id'); |
||||
|
||||
objects.forEach(object => { |
||||
putIndex.getKey(object.id).onsuccess = retrieval => { |
||||
function addObject() { |
||||
putStore.add(object); |
||||
} |
||||
|
||||
function deleteObject() { |
||||
putStore.delete(retrieval.target.result).onsuccess = addObject; |
||||
} |
||||
|
||||
if (retrieval.target.result) { |
||||
if (onupdate) { |
||||
onupdate(object, retrieval.target.result, putStore, deleteObject); |
||||
} else { |
||||
deleteObject(); |
||||
} |
||||
} else { |
||||
if (oncreate) { |
||||
oncreate(object, addObject); |
||||
} else { |
||||
addObject(); |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
putTransaction.oncomplete = () => { |
||||
const readTransaction = db.transaction(name, 'readonly'); |
||||
const readStore = readTransaction.objectStore(name); |
||||
const count = readStore.count(); |
||||
|
||||
count.onsuccess = () => { |
||||
const excess = count.result - storeLimit; |
||||
|
||||
if (excess > 0) { |
||||
const retrieval = readStore.getAll(null, excess); |
||||
|
||||
retrieval.onsuccess = () => resolve(retrieval.result); |
||||
retrieval.onerror = reject; |
||||
} else { |
||||
resolve([]); |
||||
} |
||||
}; |
||||
|
||||
count.onerror = reject; |
||||
}; |
||||
|
||||
putTransaction.onerror = reject; |
||||
})).then(resolved => { |
||||
db.close(); |
||||
return resolved; |
||||
}, error => { |
||||
db.close(); |
||||
throw error; |
||||
})); |
||||
} |
||||
|
||||
function evictAccountsByRecords(records) { |
||||
return openDB().then(db => { |
||||
const transaction = db.transaction(['accounts', 'statuses'], 'readwrite'); |
||||
const accounts = transaction.objectStore('accounts'); |
||||
const accountsIdIndex = accounts.index('id'); |
||||
const accountsMovedIndex = accounts.index('moved'); |
||||
const statuses = transaction.objectStore('statuses'); |
||||
const statusesIndex = statuses.index('account'); |
||||
|
||||
function evict(toEvict) { |
||||
toEvict.forEach(record => { |
||||
openCache() |
||||
.then(cache => accountAssetKeys.forEach(key => cache.delete(records[key]))) |
||||
.catch(printErrorIfAvailable); |
||||
|
||||
accountsMovedIndex.getAll(record.id).onsuccess = ({ target }) => evict(target.result); |
||||
|
||||
statusesIndex.getAll(record.id).onsuccess = |
||||
({ target }) => evictStatusesByRecords(target.result); |
||||
|
||||
accountsIdIndex.getKey(record.id).onsuccess = |
||||
({ target }) => target.result && accounts.delete(target.result); |
||||
}); |
||||
} |
||||
|
||||
evict(records); |
||||
|
||||
db.close(); |
||||
}).catch(printErrorIfAvailable); |
||||
} |
||||
|
||||
export function evictStatus(id) { |
||||
evictStatuses([id]); |
||||
} |
||||
|
||||
export function evictStatuses(ids) { |
||||
return openDB().then(db => { |
||||
const transaction = db.transaction('statuses', 'readwrite'); |
||||
const store = transaction.objectStore('statuses'); |
||||
const idIndex = store.index('id'); |
||||
const reblogIndex = store.index('reblog'); |
||||
|
||||
ids.forEach(id => { |
||||
reblogIndex.getAllKeys(id).onsuccess = |
||||
({ target }) => target.result.forEach(reblogKey => store.delete(reblogKey)); |
||||
|
||||
idIndex.getKey(id).onsuccess = |
||||
({ target }) => target.result && store.delete(target.result); |
||||
}); |
||||
|
||||
db.close(); |
||||
}).catch(printErrorIfAvailable); |
||||
} |
||||
|
||||
function evictStatusesByRecords(records) { |
||||
return evictStatuses(records.map(({ id }) => id)); |
||||
} |
||||
|
||||
export function putAccounts(records, avatarStatic) { |
||||
const avatarKey = avatarStatic ? 'avatar_static' : 'avatar'; |
||||
const newURLs = []; |
||||
|
||||
put('accounts', records, (newRecord, oldKey, store, oncomplete) => { |
||||
store.get(oldKey).onsuccess = ({ target }) => { |
||||
accountAssetKeys.forEach(key => { |
||||
const newURL = newRecord[key]; |
||||
const oldURL = target.result[key]; |
||||
|
||||
if (newURL !== oldURL) { |
||||
openCache() |
||||
.then(cache => cache.delete(oldURL)) |
||||
.catch(printErrorIfAvailable); |
||||
} |
||||
}); |
||||
|
||||
const newURL = newRecord[avatarKey]; |
||||
const oldURL = target.result[avatarKey]; |
||||
|
||||
if (newURL !== oldURL) { |
||||
newURLs.push(newURL); |
||||
} |
||||
|
||||
oncomplete(); |
||||
}; |
||||
}, (newRecord, oncomplete) => { |
||||
newURLs.push(newRecord[avatarKey]); |
||||
oncomplete(); |
||||
}).then(records => Promise.all([ |
||||
evictAccountsByRecords(records), |
||||
openCache().then(cache => cache.addAll(newURLs)), |
||||
])).then(freeStorage, error => { |
||||
freeStorage(); |
||||
throw error; |
||||
}).catch(printErrorIfAvailable); |
||||
} |
||||
|
||||
export function putStatuses(records) { |
||||
put('statuses', records) |
||||
.then(evictStatusesByRecords) |
||||
.catch(printErrorIfAvailable); |
||||
} |
||||
|
||||
export function freeStorage() { |
||||
return storageFreeable && navigator.storage.estimate().then(({ quota, usage }) => { |
||||
if (usage + storageMargin < quota) { |
||||
return null; |
||||
} |
||||
|
||||
return openDB().then(db => new Promise((resolve, reject) => { |
||||
const retrieval = db.transaction('accounts', 'readonly').objectStore('accounts').getAll(null, 1); |
||||
|
||||
retrieval.onsuccess = () => { |
||||
if (retrieval.result.length > 0) { |
||||
resolve(evictAccountsByRecords(retrieval.result).then(freeStorage)); |
||||
} else { |
||||
resolve(caches.delete('mastodon-system')); |
||||
} |
||||
}; |
||||
|
||||
retrieval.onerror = reject; |
||||
|
||||
db.close(); |
||||
})); |
||||
}); |
||||
} |
@ -0,0 +1,23 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class CanonicalEmailBlockPolicy < ApplicationPolicy |
||||
def index? |
||||
role.can?(:manage_blocks) |
||||
end |
||||
|
||||
def show? |
||||
role.can?(:manage_blocks) |
||||
end |
||||
|
||||
def test? |
||||
role.can?(:manage_blocks) |
||||
end |
||||
|
||||
def create? |
||||
role.can?(:manage_blocks) |
||||
end |
||||
|
||||
def destroy? |
||||
role.can?(:manage_blocks) |
||||
end |
||||
end |
@ -0,0 +1,9 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class REST::Admin::CanonicalEmailBlockSerializer < ActiveModel::Serializer |
||||
attributes :id, :canonical_email_hash |
||||
|
||||
def id |
||||
object.id.to_s |
||||
end |
||||
end |
@ -0,0 +1,9 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class REST::Admin::EmailDomainBlockSerializer < ActiveModel::Serializer |
||||
attributes :id, :domain, :created_at, :history |
||||
|
||||
def id |
||||
object.id.to_s |
||||
end |
||||
end |
@ -0,0 +1,14 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class REST::Admin::IpBlockSerializer < ActiveModel::Serializer |
||||
attributes :id, :ip, :severity, :comment, |
||||
:created_at, :expires_at |
||||
|
||||
def id |
||||
object.id.to_s |
||||
end |
||||
|
||||
def ip |
||||
"#{object.ip}/#{object.ip.prefix}" |
||||
end |
||||
end |
@ -1,44 +0,0 @@ |
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.email-body |
||||
.email-container |
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.content-cell.darker.hero-with-button |
||||
.email-row |
||||
.col-6 |
||||
%table.column{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.column-cell.text-center.padded |
||||
%h1= t 'notification_mailer.digest.title' |
||||
%p.lead= t('notification_mailer.digest.body', since: l((@me.user_current_sign_in_at || @since).to_date, format: :short), instance: site_hostname) |
||||
%table.button{ align: 'center', cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.button-primary |
||||
= link_to web_url do |
||||
%span= t 'notification_mailer.digest.action' |
||||
|
||||
- @notifications.each_with_index do |n, i| |
||||
= render 'status', status: n.target_status, i: i |
||||
|
||||
- unless @follows_since.zero? |
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.email-body |
||||
.email-container |
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.content-cell.content-start.border-top |
||||
.email-row |
||||
.col-6 |
||||
%table.column{ cellspacing: 0, cellpadding: 0 } |
||||
%tbody |
||||
%tr |
||||
%td.column-cell.text-center |
||||
%p= t('notification_mailer.digest.new_followers_summary', count: @follows_since) |
@ -1,15 +0,0 @@ |
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %> |
||||
|
||||
<%= raw t('notification_mailer.digest.body', since: l(@me.user_current_sign_in_at || @since), instance: root_url) %> |
||||
<% @notifications.each do |notification| %> |
||||
|
||||
* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.pretty_acct) %> |
||||
|
||||
<%= raw extract_status_plain_text(notification.target_status) %> |
||||
|
||||
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %> |
||||
<% end %> |
||||
<% if @follows_since > 0 %> |
||||
|
||||
<%= raw t('notification_mailer.digest.new_followers_summary', count: @follows_since) %> |
||||
<% end %> |
@ -1,21 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class DigestMailerWorker |
||||
include Sidekiq::Worker |
||||
|
||||
sidekiq_options queue: 'mailers' |
||||
|
||||
attr_reader :user |
||||
|
||||
def perform(user_id) |
||||
@user = User.find(user_id) |
||||
deliver_digest if @user.allows_digest_emails? |
||||
end |
||||
|
||||
private |
||||
|
||||
def deliver_digest |
||||
NotificationMailer.digest(user.account).deliver_now! |
||||
user.touch(:last_emailed_at) |
||||
end |
||||
end |
@ -1,25 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Scheduler::EmailScheduler |
||||
include Sidekiq::Worker |
||||
|
||||
sidekiq_options retry: 0 |
||||
|
||||
FREQUENCY = 7.days.freeze |
||||
SIGN_IN_OFFSET = 1.day.freeze |
||||
|
||||
def perform |
||||
eligible_users.reorder(nil).find_each do |user| |
||||
next unless user.allows_digest_emails? |
||||
DigestMailerWorker.perform_async(user.id) |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def eligible_users |
||||
User.emailable |
||||
.where('current_sign_in_at < ?', (FREQUENCY + SIGN_IN_OFFSET).ago) |
||||
.where('last_emailed_at IS NULL OR last_emailed_at < ?', FREQUENCY.ago) |
||||
end |
||||
end |
@ -0,0 +1,7 @@ |
||||
class AddHumanIdentifierToAdminActionLogs < ActiveRecord::Migration[6.1] |
||||
def change |
||||
add_column :admin_action_logs, :human_identifier, :string |
||||
add_column :admin_action_logs, :route_param, :string |
||||
add_column :admin_action_logs, :permalink, :string |
||||
end |
||||
end |
@ -0,0 +1,5 @@ |
||||
class ChangeCanonicalEmailBlocksNullable < ActiveRecord::Migration[6.1] |
||||
def change |
||||
safety_assured { change_column :canonical_email_blocks, :reference_account_id, :bigint, null: true, default: nil } |
||||
end |
||||
end |
@ -0,0 +1,20 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class FixCustomFilterKeywordsIdSeq < ActiveRecord::Migration[6.1] |
||||
disable_ddl_transaction! |
||||
|
||||
def up |
||||
# 20220613110711 manually inserts items with set `id` in the database, but |
||||
# we also need to bump the sequence number, otherwise |
||||
safety_assured do |
||||
execute <<-SQL.squish |
||||
BEGIN; |
||||
LOCK TABLE custom_filter_keywords IN EXCLUSIVE MODE; |
||||
SELECT setval('custom_filter_keywords_id_seq'::regclass, id) FROM custom_filter_keywords ORDER BY id DESC LIMIT 1; |
||||
COMMIT; |
||||
SQL |
||||
end |
||||
end |
||||
|
||||
def down; end |
||||
end |
@ -0,0 +1,9 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class RemoveRecordedChangesFromAdminActionLogs < ActiveRecord::Migration[5.2] |
||||
disable_ddl_transaction! |
||||
|
||||
def change |
||||
safety_assured { remove_column :admin_action_logs, :recorded_changes, :text } |
||||
end |
||||
end |
@ -1 +1 @@ |
||||
assets/sw.js |
||||
packs/sw.js |
@ -0,0 +1 @@ |
||||
packs/sw.js.map |
@ -1,36 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
describe DigestMailerWorker do |
||||
describe 'perform' do |
||||
let(:user) { Fabricate(:user, last_emailed_at: 3.days.ago) } |
||||
|
||||
context 'for a user who receives digests' do |
||||
it 'sends the email' do |
||||
service = double(deliver_now!: nil) |
||||
allow(NotificationMailer).to receive(:digest).and_return(service) |
||||
update_user_digest_setting(true) |
||||
described_class.perform_async(user.id) |
||||
|
||||
expect(NotificationMailer).to have_received(:digest) |
||||
expect(user.reload.last_emailed_at).to be_within(1).of(Time.now.utc) |
||||
end |
||||
end |
||||
|
||||
context 'for a user who does not receive digests' do |
||||
it 'does not send the email' do |
||||
allow(NotificationMailer).to receive(:digest) |
||||
update_user_digest_setting(false) |
||||
described_class.perform_async(user.id) |
||||
|
||||
expect(NotificationMailer).not_to have_received(:digest) |
||||
expect(user.last_emailed_at).to be_within(1).of(3.days.ago) |
||||
end |
||||
end |
||||
|
||||
def update_user_digest_setting(value) |
||||
user.settings['notification_emails'] = user.settings['notification_emails'].merge('digest' => value) |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue