mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-16 01:09:55 +00:00
Merge commit 'a5a2c6dc7ec0d8af53594cd53a90da7d6fbefd5a' into glitch-soc/merge-upstream
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Fasp::DataSharing::V0::BackfillRequestsController < Api::Fasp::BaseController
|
||||
def create
|
||||
backfill_request = current_provider.fasp_backfill_requests.new(backfill_request_params)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if backfill_request.save
|
||||
render json: { backfillRequest: { id: backfill_request.id } }, status: 201
|
||||
else
|
||||
head 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def backfill_request_params
|
||||
params
|
||||
.permit(:category, :maxCount)
|
||||
.to_unsafe_h
|
||||
.transform_keys { |k| k.to_s.underscore }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Fasp::DataSharing::V0::ContinuationsController < Api::Fasp::BaseController
|
||||
def create
|
||||
backfill_request = current_provider.fasp_backfill_requests.find(params[:backfill_request_id])
|
||||
Fasp::BackfillWorker.perform_async(backfill_request.id)
|
||||
|
||||
head 204
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Fasp::DataSharing::V0::EventSubscriptionsController < Api::Fasp::BaseController
|
||||
def create
|
||||
subscription = current_provider.fasp_subscriptions.create!(subscription_params)
|
||||
|
||||
render json: { subscription: { id: subscription.id } }, status: 201
|
||||
end
|
||||
|
||||
def destroy
|
||||
subscription = current_provider.fasp_subscriptions.find(params[:id])
|
||||
subscription.destroy
|
||||
|
||||
head 204
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscription_params
|
||||
params
|
||||
.permit(:category, :subscriptionType, :maxBatchSize, threshold: {})
|
||||
.to_unsafe_h
|
||||
.transform_keys { |k| k.to_s.underscore }
|
||||
end
|
||||
end
|
||||
@@ -175,9 +175,8 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseUp = e => {
|
||||
handleHeaderClick = e => {
|
||||
// Only handle clicks on the empty space above the content
|
||||
|
||||
if (e.target !== e.currentTarget && e.detail >= 1) {
|
||||
return;
|
||||
}
|
||||
@@ -547,7 +546,7 @@ class Status extends ImmutablePureComponent {
|
||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
|
||||
<div onMouseUp={this.handleMouseUp} className='status__info'>
|
||||
<div onClick={this.handleHeaderClick} onAuxClick={this.handleHeaderClick} className='status__info'>
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time'>
|
||||
<span className='status__visibility-icon'><VisibilityIcon visibility={status.get('visibility')} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
"account.edit_profile": "Редактиране на профила",
|
||||
"account.enable_notifications": "Известяване при публикуване от @{name}",
|
||||
"account.endorse": "Представи в профила",
|
||||
"account.familiar_followers_many": "Последвано от {name1}, {name2} и {othersCount, plural, one {# друг} other {# други}}",
|
||||
"account.familiar_followers_one": "Последвано от {name1}",
|
||||
"account.familiar_followers_two": "Последвано от {name1} и {name2}",
|
||||
"account.featured": "Препоръчано",
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"account.edit_profile": "Kemmañ ar profil",
|
||||
"account.enable_notifications": "Ma c'hemenn pa vez embannet traoù gant @{name}",
|
||||
"account.endorse": "Lakaat war-wel war ar profil",
|
||||
"account.familiar_followers_many": "Heuilhet gant {name1}, {name2}, {othersCount, plural, one {hag # all} two {ha # all} few {ha # all} many {ha(g) # all} other {hag(g) # all}}",
|
||||
"account.familiar_followers_one": "Heuilhet gant {name1}",
|
||||
"account.familiar_followers_two": "Heuilhet gant {name1} ha {name2}",
|
||||
"account.featured_tags.last_status_at": "Toud diwezhañ : {date}",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Edita el perfil",
|
||||
"account.enable_notifications": "Notifica'm els tuts de @{name}",
|
||||
"account.endorse": "Recomana en el perfil",
|
||||
"account.familiar_followers_many": "Seguit per {name1}, {name2} i {othersCount, plural, one {# altre compte} other {# altres comptes}}",
|
||||
"account.familiar_followers_many": "Seguit per {name1}, {name2} i {othersCount, plural, one {# altre compte} other {# altres comptes}} que coneixeu",
|
||||
"account.familiar_followers_one": "Seguit per {name1}",
|
||||
"account.familiar_followers_two": "Seguit per {name1} i {name2}",
|
||||
"account.featured": "Destacat",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Upravit profil",
|
||||
"account.enable_notifications": "Oznamovat mi příspěvky @{name}",
|
||||
"account.endorse": "Zvýraznit na profilu",
|
||||
"account.familiar_followers_many": "Sleduje je {name1}, {name2} a {othersCount, plural, one {# další} few {# další} many {# dalších} other {# dalších}}",
|
||||
"account.familiar_followers_many": "Sleduje je {name1}, {name2} a {othersCount, plural, one {jeden další, které znáte} few {# další, které znáte} many {# dalších, které znáte} other {# dalších, které znáte}}",
|
||||
"account.familiar_followers_one": "Sleduje je {name1}",
|
||||
"account.familiar_followers_two": "Sleduje je {name1} a {name2}",
|
||||
"account.featured": "Zvýrazněné",
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
"account.edit_profile": "Golygu'r proffil",
|
||||
"account.enable_notifications": "Rhowch wybod i fi pan fydd @{name} yn postio",
|
||||
"account.endorse": "Dangos ar fy mhroffil",
|
||||
"account.familiar_followers_many": "Yn cael ei ddilyn gan {name1},{name2}, a {othersCount, plural, one {one other you know} other{# others you know}}",
|
||||
"account.familiar_followers_one": "Wedi'i ddilyn gan {name1}",
|
||||
"account.familiar_followers_two": "Wedi'i ddilyn gan {name1} a {name2}",
|
||||
"account.featured": "Nodwedd",
|
||||
"account.featured.accounts": "Proffilau",
|
||||
"account.featured.hashtags": "Hashnodau",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Redigér profil",
|
||||
"account.enable_notifications": "Advisér mig, når @{name} poster",
|
||||
"account.endorse": "Fremhæv på profil",
|
||||
"account.familiar_followers_many": "Følges af {name1}, {name2} og {othersCount, plural, one {# mere} other {# mere}}",
|
||||
"account.familiar_followers_many": "Følges af {name1}, {name2} og {othersCount, plural, one {# mere, man kender} other {# mere, man kender}}",
|
||||
"account.familiar_followers_one": "Følges af {name1}",
|
||||
"account.familiar_followers_two": "Følges af {name1} og {name2}",
|
||||
"account.featured": "Fremhævet",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Profil bearbeiten",
|
||||
"account.enable_notifications": "Benachrichtige mich wenn @{name} etwas postet",
|
||||
"account.endorse": "Im Profil vorstellen",
|
||||
"account.familiar_followers_many": "Gefolgt von {name1}, {name2} und {othersCount, plural, one {# Profil} other {# weiteren Profilen}}",
|
||||
"account.familiar_followers_many": "Gefolgt von {name1}, {name2} und {othersCount, plural, one {einem weiteren Profil, das dir bekannt ist} other {# weiteren Profilen, die dir bekannt sind}}",
|
||||
"account.familiar_followers_one": "Gefolgt von {name1}",
|
||||
"account.familiar_followers_two": "Gefolgt von {name1} und {name2}",
|
||||
"account.featured": "Vorgestellt",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.enable_notifications": "Notificarme cuando @{name} envíe mensajes",
|
||||
"account.endorse": "Destacar en el perfil",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural, one {# cuenta más} other {# cuentas más}}",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural, one {# cuenta más que conocés} other {# cuentas más que conocés}}",
|
||||
"account.familiar_followers_one": "Seguido por {name1}",
|
||||
"account.familiar_followers_two": "Seguido por {name1} y {name2}",
|
||||
"account.featured": "Destacados",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.enable_notifications": "Notificarme cuando @{name} publique algo",
|
||||
"account.endorse": "Destacar en mi perfil",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {# otro} other {# otros}}",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {otro que conoces}other {# otros que conoces}}",
|
||||
"account.familiar_followers_one": "Seguido por {name1}",
|
||||
"account.familiar_followers_two": "Seguid por {name1} y {name2}",
|
||||
"account.featured": "Destacado",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.enable_notifications": "Notificarme cuando @{name} publique algo",
|
||||
"account.endorse": "Destacar en el perfil",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural, one {# más} other {# más}}",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} y {othersCount, plural,one {otro que conoces}other {# otros que conoces}}",
|
||||
"account.familiar_followers_one": "Seguido por {name1}",
|
||||
"account.familiar_followers_two": "Seguido por {name1} y {name2}",
|
||||
"account.featured": "Destacado",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Muokkaa profiilia",
|
||||
"account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee",
|
||||
"account.endorse": "Suosittele profiilissa",
|
||||
"account.familiar_followers_many": "Seuraajina {name1}, {name2} ja {othersCount, plural, one {# muu} other {# muuta}}",
|
||||
"account.familiar_followers_many": "Seuraajina {name1}, {name2} ja {othersCount, plural, one {1 muu, jonka tunnet} other {# muuta, jotka tunnet}}",
|
||||
"account.familiar_followers_one": "Seuraajana {name1}",
|
||||
"account.familiar_followers_two": "Seuraajina {name1} ja {name2}",
|
||||
"account.featured": "Suositellut",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Broyt vanga",
|
||||
"account.enable_notifications": "Boða mær frá, tá @{name} skrivar",
|
||||
"account.endorse": "Víst á vangamyndini",
|
||||
"account.familiar_followers_many": "{name1}, {name2} og {othersCount, plural, one {# annar} other {# onnur}} fylgja",
|
||||
"account.familiar_followers_many": "{name1}, {name2} og {othersCount, plural, one {ein annar/onnur tú kennir} other {# onnur tú kennir}} fylgja",
|
||||
"account.familiar_followers_one": "{name1} fylgir",
|
||||
"account.familiar_followers_two": "{name1} og {name2} fylgja",
|
||||
"account.featured": "Tikin fram",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.enable_notifications": "Noficarme cando @{name} publique",
|
||||
"account.endorse": "Amosar no perfil",
|
||||
"account.familiar_followers_many": "Seguida por {name1}, {name2}, e {othersCount, plural,one {# máis} other {# máis}}",
|
||||
"account.familiar_followers_many": "Seguida por {name1}, {name2}, e {othersCount, plural, one {outra conta que coñeces} other {outras # contas que coñeces}}",
|
||||
"account.familiar_followers_one": "Seguida por {name1}",
|
||||
"account.familiar_followers_two": "Seguida por {name1} e {name2}",
|
||||
"account.featured": "Destacado",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "עריכת פרופיל",
|
||||
"account.enable_notifications": "שלח לי התראות כש@{name} מפרסם",
|
||||
"account.endorse": "קדם את החשבון בפרופיל",
|
||||
"account.familiar_followers_many": "החשבון נעקב על ידי {name1}, {name2} ועוד {othersCount, plural,one {אחד נוסף}other {# נוספים}}",
|
||||
"account.familiar_followers_many": "החשבון נעקב על ידי {name1}, {name2} ועוד {othersCount, plural,one {אחד נוסף שמוכר לך}other {# נוספים שמוכרים לך}}",
|
||||
"account.familiar_followers_one": "החשבון נעקב על ידי {name1}",
|
||||
"account.familiar_followers_two": "החשבון נעקב על ידי {name1} ו־{name2}",
|
||||
"account.featured": "מומלץ",
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
"account.edit_profile": "Profil szerkesztése",
|
||||
"account.enable_notifications": "Figyelmeztessen, ha @{name} bejegyzést tesz közzé",
|
||||
"account.endorse": "Kiemelés a profilodon",
|
||||
"account.familiar_followers_many": "{name1}, {name2} és {othersCount, plural, one {# másik} other {# másik}} követi",
|
||||
"account.familiar_followers_one": "{name1} követi",
|
||||
"account.familiar_followers_two": "{name1} és {name2} követi",
|
||||
"account.featured": "Kiemelt",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Breyta notandasniði",
|
||||
"account.enable_notifications": "Láta mig vita þegar @{name} sendir inn",
|
||||
"account.endorse": "Birta á notandasniði",
|
||||
"account.familiar_followers_many": "Fylgt af {name1}, {name2} og {othersCount, plural, one {# í viðbót} other {# í viðbót}}",
|
||||
"account.familiar_followers_many": "Fylgt af {name1}, {name2} og {othersCount, plural, one {einum öðrum sem þú þekkir} other {# öðrum sem þú þekkir}}",
|
||||
"account.familiar_followers_one": "Fylgt af {name1}",
|
||||
"account.familiar_followers_two": "Fylgt af {name1} og {name2}",
|
||||
"account.featured": "Með aukið vægi",
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
"account.edit_profile": "Modifica profilo",
|
||||
"account.enable_notifications": "Avvisami quando @{name} pubblica un post",
|
||||
"account.endorse": "In evidenza sul profilo",
|
||||
"account.familiar_followers_many": "Seguito da {name1}, {name2}, e {othersCount, plural, one {un altro che conosci} other {# altri che conosci}}",
|
||||
"account.familiar_followers_one": "Seguito da {name1}",
|
||||
"account.familiar_followers_two": "Seguito da {name1} e {name2}",
|
||||
"account.featured": "In primo piano",
|
||||
"account.featured.accounts": "Profili",
|
||||
"account.featured.hashtags": "Hashtag",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Profiel bewerken",
|
||||
"account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst",
|
||||
"account.endorse": "Op profiel weergeven",
|
||||
"account.familiar_followers_many": "Gevolgd door {name1}, {name2} en {othersCount, plural, one {# ander account} other {# andere accounts}}",
|
||||
"account.familiar_followers_many": "Gevolgd door {name1}, {name2} en {othersCount, plural, one {één ander bekend account} other {# andere bekende accounts}}",
|
||||
"account.familiar_followers_one": "Gevolgd door {name1}",
|
||||
"account.familiar_followers_two": "Gevolgd door {name1} en {name2}",
|
||||
"account.featured": "Uitgelicht",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.enable_notifications": "Notificar-me das publicações de @{name}",
|
||||
"account.endorse": "Destacar no perfil",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} e {othersCount, plural,one {# outro}other {# outros}}",
|
||||
"account.familiar_followers_many": "Seguido por {name1}, {name2} e {othersCount, plural,one {mais uma pessoa que conhece} other {# outras pessoas que conhece}}",
|
||||
"account.familiar_followers_one": "Seguido por {name1}",
|
||||
"account.familiar_followers_two": "Seguido por {name1} e {name2}",
|
||||
"account.featured": "Destaques",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Përpunoni profilin",
|
||||
"account.enable_notifications": "Njoftomë, kur poston @{name}",
|
||||
"account.endorse": "Pasqyrojeni në profil",
|
||||
"account.familiar_followers_many": "Ndjekur nga {name1}, {name2} dhe {othersCount, plural, one {# tjetër} other {# të tjerë}}",
|
||||
"account.familiar_followers_many": "Ndjekur nga {name1}, {name2} dhe {othersCount, plural, one {një tjetër që njihni} other {# të tjerë që njihni}}",
|
||||
"account.familiar_followers_one": "Ndjekur nga {name1}",
|
||||
"account.familiar_followers_two": "Ndjekur nga {name1} dhe {name2}",
|
||||
"account.featured": "Të zgjedhur",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Profili düzenle",
|
||||
"account.enable_notifications": "@{name} kişisinin gönderi bildirimlerini aç",
|
||||
"account.endorse": "Profilimde öne çıkar",
|
||||
"account.familiar_followers_many": "{name1}, {name2}, {othersCount, plural, one {# diğer} other {# diğer}} kişi tarafından takip ediliyor",
|
||||
"account.familiar_followers_many": "{name1}, {name2}, {othersCount, plural, one {# diğer} other {# diğer}} bildiğiniz kişi tarafından takip ediliyor",
|
||||
"account.familiar_followers_one": "{name1} tarafından takip ediliyor",
|
||||
"account.familiar_followers_two": "{name1} ve {name2} tarafından takip ediliyor",
|
||||
"account.featured": "Öne çıkan",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "Sửa hồ sơ",
|
||||
"account.enable_notifications": "Nhận thông báo khi @{name} đăng tút",
|
||||
"account.endorse": "Tôn vinh người này",
|
||||
"account.familiar_followers_many": "Theo dõi bởi {name1}, {name2} và {othersCount, plural, other {# người khác}}",
|
||||
"account.familiar_followers_many": "Theo dõi bởi {name1}, {name2} và {othersCount, plural, other {# người khác mà bạn biết}}",
|
||||
"account.familiar_followers_one": "Theo dõi bởi {name1}",
|
||||
"account.familiar_followers_two": "Theo dõi bởi {name1} và {name2}",
|
||||
"account.featured": "Nêu bật",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"account.edit_profile": "編輯個人檔案",
|
||||
"account.enable_notifications": "當 @{name} 嘟文時通知我",
|
||||
"account.endorse": "於個人檔案推薦對方",
|
||||
"account.familiar_followers_many": "被 {name1}、{name2}、及 {othersCount, plural, other {其他 # 人}} 跟隨",
|
||||
"account.familiar_followers_many": "被 {name1}、{name2}、及 {othersCount, plural, other {其他您認識的 # 人}} 跟隨",
|
||||
"account.familiar_followers_one": "被 {name1} 跟隨",
|
||||
"account.familiar_followers_two": "被 {name1} 與 {name2} 跟隨",
|
||||
"account.featured": "精選內容",
|
||||
|
||||
@@ -32,6 +32,7 @@ class Fasp::Request
|
||||
def request_headers(verb, url, body = '')
|
||||
result = {
|
||||
'accept' => 'application/json',
|
||||
'content-type' => 'application/json',
|
||||
'content-digest' => content_digest(body),
|
||||
}
|
||||
result.merge(signature_headers(verb, url, result))
|
||||
|
||||
@@ -85,6 +85,7 @@ class Account < ApplicationRecord
|
||||
include Account::Associations
|
||||
include Account::Avatar
|
||||
include Account::Counters
|
||||
include Account::FaspConcern
|
||||
include Account::FinderConcern
|
||||
include Account::Header
|
||||
include Account::Interactions
|
||||
|
||||
37
app/models/concerns/account/fasp_concern.rb
Normal file
37
app/models/concerns/account/fasp_concern.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Account::FaspConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_commit :announce_new_account_to_subscribed_fasp, on: :create
|
||||
after_commit :announce_updated_account_to_subscribed_fasp, on: :update
|
||||
after_commit :announce_deleted_account_to_subscribed_fasp, on: :destroy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announce_new_account_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless discoverable?
|
||||
|
||||
uri = ActivityPub::TagManager.instance.uri_for(self)
|
||||
Fasp::AnnounceAccountLifecycleEventWorker.perform_async(uri, 'new')
|
||||
end
|
||||
|
||||
def announce_updated_account_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless discoverable? || saved_change_to_discoverable?
|
||||
|
||||
uri = ActivityPub::TagManager.instance.uri_for(self)
|
||||
Fasp::AnnounceAccountLifecycleEventWorker.perform_async(uri, 'update')
|
||||
end
|
||||
|
||||
def announce_deleted_account_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless discoverable?
|
||||
|
||||
uri = ActivityPub::TagManager.instance.uri_for(self)
|
||||
Fasp::AnnounceAccountLifecycleEventWorker.perform_async(uri, 'delete')
|
||||
end
|
||||
end
|
||||
17
app/models/concerns/favourite/fasp_concern.rb
Normal file
17
app/models/concerns/favourite/fasp_concern.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Favourite::FaspConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_commit :announce_trends_to_subscribed_fasp, on: :create
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announce_trends_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
|
||||
Fasp::AnnounceTrendWorker.perform_async(status_id, 'favourite')
|
||||
end
|
||||
end
|
||||
53
app/models/concerns/status/fasp_concern.rb
Normal file
53
app/models/concerns/status/fasp_concern.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Status::FaspConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_commit :announce_new_content_to_subscribed_fasp, on: :create
|
||||
after_commit :announce_updated_content_to_subscribed_fasp, on: :update
|
||||
after_commit :announce_deleted_content_to_subscribed_fasp, on: :destroy
|
||||
after_commit :announce_trends_to_subscribed_fasp, on: :create
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announce_new_content_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless account_indexable? && public_visibility?
|
||||
|
||||
# We need the uri here, but it is set in another `after_commit`
|
||||
# callback. Hooks included from modules are run before the ones
|
||||
# in the class itself and can neither be reordered nor is there
|
||||
# a way to declare dependencies.
|
||||
store_uri if uri.nil?
|
||||
Fasp::AnnounceContentLifecycleEventWorker.perform_async(uri, 'new')
|
||||
end
|
||||
|
||||
def announce_updated_content_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless account_indexable? && public_visibility?
|
||||
|
||||
Fasp::AnnounceContentLifecycleEventWorker.perform_async(uri, 'update')
|
||||
end
|
||||
|
||||
def announce_deleted_content_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless account_indexable? && public_visibility?
|
||||
|
||||
Fasp::AnnounceContentLifecycleEventWorker.perform_async(uri, 'delete')
|
||||
end
|
||||
|
||||
def announce_trends_to_subscribed_fasp
|
||||
return unless Mastodon::Feature.fasp_enabled?
|
||||
return unless account_indexable?
|
||||
|
||||
candidate_id, trend_source =
|
||||
if reblog_of_id
|
||||
[reblog_of_id, 'reblog']
|
||||
elsif in_reply_to_id
|
||||
[in_reply_to_id, 'reply']
|
||||
end
|
||||
Fasp::AnnounceTrendWorker.perform_async(candidate_id, trend_source) if candidate_id
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Fasp
|
||||
DATA_CATEGORIES = %w(account content).freeze
|
||||
|
||||
def self.table_name_prefix
|
||||
'fasp_'
|
||||
end
|
||||
|
||||
67
app/models/fasp/backfill_request.rb
Normal file
67
app/models/fasp/backfill_request.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: fasp_backfill_requests
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# category :string not null
|
||||
# cursor :string
|
||||
# fulfilled :boolean default(FALSE), not null
|
||||
# max_count :integer default(100), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# fasp_provider_id :bigint(8) not null
|
||||
#
|
||||
class Fasp::BackfillRequest < ApplicationRecord
|
||||
belongs_to :fasp_provider, class_name: 'Fasp::Provider'
|
||||
|
||||
validates :category, presence: true, inclusion: Fasp::DATA_CATEGORIES
|
||||
validates :max_count, presence: true,
|
||||
numericality: { only_integer: true }
|
||||
|
||||
after_commit :queue_fulfillment_job, on: :create
|
||||
|
||||
def next_objects
|
||||
@next_objects ||= base_scope.to_a
|
||||
end
|
||||
|
||||
def next_uris
|
||||
next_objects.map { |o| ActivityPub::TagManager.instance.uri_for(o) }
|
||||
end
|
||||
|
||||
def more_objects_available?
|
||||
return false if next_objects.empty?
|
||||
|
||||
base_scope.where(id: ...(next_objects.last.id)).any?
|
||||
end
|
||||
|
||||
def advance!
|
||||
if more_objects_available?
|
||||
update!(cursor: next_objects.last.id)
|
||||
else
|
||||
update!(fulfilled: true)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base_scope
|
||||
result = category_scope.limit(max_count).order(id: :desc)
|
||||
result = result.where(id: ...cursor) if cursor.present?
|
||||
result
|
||||
end
|
||||
|
||||
def category_scope
|
||||
case category
|
||||
when 'account'
|
||||
Account.discoverable.without_instance_actor
|
||||
when 'content'
|
||||
Status.indexable
|
||||
end
|
||||
end
|
||||
|
||||
def queue_fulfillment_job
|
||||
Fasp::BackfillWorker.perform_async(id)
|
||||
end
|
||||
end
|
||||
@@ -22,7 +22,9 @@
|
||||
class Fasp::Provider < ApplicationRecord
|
||||
include DebugConcern
|
||||
|
||||
has_many :fasp_backfill_requests, inverse_of: :fasp_provider, class_name: 'Fasp::BackfillRequest', dependent: :delete_all
|
||||
has_many :fasp_debug_callbacks, inverse_of: :fasp_provider, class_name: 'Fasp::DebugCallback', dependent: :delete_all
|
||||
has_many :fasp_subscriptions, inverse_of: :fasp_provider, class_name: 'Fasp::Subscription', dependent: :delete_all
|
||||
|
||||
validates :name, presence: true
|
||||
validates :base_url, presence: true, url: true
|
||||
|
||||
43
app/models/fasp/subscription.rb
Normal file
43
app/models/fasp/subscription.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: fasp_subscriptions
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# category :string not null
|
||||
# max_batch_size :integer not null
|
||||
# subscription_type :string not null
|
||||
# threshold_likes :integer
|
||||
# threshold_replies :integer
|
||||
# threshold_shares :integer
|
||||
# threshold_timeframe :integer
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# fasp_provider_id :bigint(8) not null
|
||||
#
|
||||
class Fasp::Subscription < ApplicationRecord
|
||||
TYPES = %w(lifecycle trends).freeze
|
||||
|
||||
belongs_to :fasp_provider, class_name: 'Fasp::Provider'
|
||||
|
||||
validates :category, presence: true, inclusion: Fasp::DATA_CATEGORIES
|
||||
validates :subscription_type, presence: true,
|
||||
inclusion: TYPES
|
||||
|
||||
scope :category_content, -> { where(category: 'content') }
|
||||
scope :category_account, -> { where(category: 'account') }
|
||||
scope :lifecycle, -> { where(subscription_type: 'lifecycle') }
|
||||
scope :trends, -> { where(subscription_type: 'trends') }
|
||||
|
||||
def threshold=(threshold)
|
||||
self.threshold_timeframe = threshold['timeframe'] || 15
|
||||
self.threshold_shares = threshold['shares'] || 3
|
||||
self.threshold_likes = threshold['likes'] || 3
|
||||
self.threshold_replies = threshold['replies'] || 3
|
||||
end
|
||||
|
||||
def timeframe_start
|
||||
threshold_timeframe.minutes.ago
|
||||
end
|
||||
end
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
class Favourite < ApplicationRecord
|
||||
include Paginable
|
||||
include Favourite::FaspConcern
|
||||
|
||||
update_index('statuses', :status)
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ class Status < ApplicationRecord
|
||||
include Discard::Model
|
||||
include Paginable
|
||||
include RateLimitable
|
||||
include Status::FaspConcern
|
||||
include Status::FetchRepliesConcern
|
||||
include Status::SafeReblogInsert
|
||||
include Status::SearchConcern
|
||||
@@ -188,7 +189,7 @@ class Status < ApplicationRecord
|
||||
],
|
||||
thread: :account
|
||||
|
||||
delegate :domain, to: :account, prefix: true
|
||||
delegate :domain, :indexable?, to: :account, prefix: true
|
||||
|
||||
REAL_TIME_WINDOW = 6.hours
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ class ActivityPub::FetchAllRepliesWorker
|
||||
root_status_body = fetch_resource(root_status_uri, true)
|
||||
return if root_status_body.nil?
|
||||
|
||||
FetchReplyWorker.perform_async(root_status_uri, { **options, prefetched_body: root_status_body })
|
||||
FetchReplyWorker.perform_async(root_status_uri, { **options.deep_stringify_keys, 'prefetched_body' => root_status_body })
|
||||
|
||||
get_replies(root_status_body, MAX_PAGES, options)
|
||||
end
|
||||
|
||||
28
app/workers/fasp/announce_account_lifecycle_event_worker.rb
Normal file
28
app/workers/fasp/announce_account_lifecycle_event_worker.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Fasp::AnnounceAccountLifecycleEventWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'fasp', retry: 5
|
||||
|
||||
def perform(uri, event_type)
|
||||
Fasp::Subscription.includes(:fasp_provider).category_account.lifecycle.each do |subscription|
|
||||
announce(subscription, uri, event_type)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announce(subscription, uri, event_type)
|
||||
Fasp::Request.new(subscription.fasp_provider).post('/data_sharing/v0/announcements', body: {
|
||||
source: {
|
||||
subscription: {
|
||||
id: subscription.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'account',
|
||||
eventType: event_type,
|
||||
objectUris: [uri],
|
||||
})
|
||||
end
|
||||
end
|
||||
28
app/workers/fasp/announce_content_lifecycle_event_worker.rb
Normal file
28
app/workers/fasp/announce_content_lifecycle_event_worker.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Fasp::AnnounceContentLifecycleEventWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'fasp', retry: 5
|
||||
|
||||
def perform(uri, event_type)
|
||||
Fasp::Subscription.includes(:fasp_provider).category_content.lifecycle.each do |subscription|
|
||||
announce(subscription, uri, event_type)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announce(subscription, uri, event_type)
|
||||
Fasp::Request.new(subscription.fasp_provider).post('/data_sharing/v0/announcements', body: {
|
||||
source: {
|
||||
subscription: {
|
||||
id: subscription.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'content',
|
||||
eventType: event_type,
|
||||
objectUris: [uri],
|
||||
})
|
||||
end
|
||||
end
|
||||
61
app/workers/fasp/announce_trend_worker.rb
Normal file
61
app/workers/fasp/announce_trend_worker.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Fasp::AnnounceTrendWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'fasp', retry: 5
|
||||
|
||||
def perform(status_id, trend_source)
|
||||
status = ::Status.includes(:account).find(status_id)
|
||||
return unless status.account.indexable?
|
||||
|
||||
Fasp::Subscription.includes(:fasp_provider).category_content.trends.each do |subscription|
|
||||
announce(subscription, status.uri) if trending?(subscription, status, trend_source)
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# status might not exist anymore, in which case there is nothing to do
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trending?(subscription, status, trend_source)
|
||||
scope = scope_for(status, trend_source)
|
||||
threshold = threshold_for(subscription, trend_source)
|
||||
scope.where(created_at: subscription.timeframe_start..).count >= threshold
|
||||
end
|
||||
|
||||
def scope_for(status, trend_source)
|
||||
case trend_source
|
||||
when 'favourite'
|
||||
status.favourites
|
||||
when 'reblog'
|
||||
status.reblogs
|
||||
when 'reply'
|
||||
status.replies
|
||||
end
|
||||
end
|
||||
|
||||
def threshold_for(subscription, trend_source)
|
||||
case trend_source
|
||||
when 'favourite'
|
||||
subscription.threshold_likes
|
||||
when 'reblog'
|
||||
subscription.threshold_shares
|
||||
when 'reply'
|
||||
subscription.threshold_replies
|
||||
end
|
||||
end
|
||||
|
||||
def announce(subscription, uri)
|
||||
Fasp::Request.new(subscription.fasp_provider).post('/data_sharing/v0/announcements', body: {
|
||||
source: {
|
||||
subscription: {
|
||||
id: subscription.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'content',
|
||||
eventType: 'trending',
|
||||
objectUris: [uri],
|
||||
})
|
||||
end
|
||||
end
|
||||
32
app/workers/fasp/backfill_worker.rb
Normal file
32
app/workers/fasp/backfill_worker.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Fasp::BackfillWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'fasp', retry: 5
|
||||
|
||||
def perform(backfill_request_id)
|
||||
backfill_request = Fasp::BackfillRequest.find(backfill_request_id)
|
||||
|
||||
announce(backfill_request)
|
||||
|
||||
backfill_request.advance!
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# ignore missing backfill requests
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announce(backfill_request)
|
||||
Fasp::Request.new(backfill_request.fasp_provider).post('/data_sharing/v0/announcements', body: {
|
||||
source: {
|
||||
backfillRequest: {
|
||||
id: backfill_request.id.to_s,
|
||||
},
|
||||
},
|
||||
category: backfill_request.category,
|
||||
objectUris: backfill_request.next_uris,
|
||||
moreObjectsAvailable: backfill_request.more_objects_available?,
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -2030,6 +2030,10 @@ cy:
|
||||
limit: Rydych chi eisoes wedi pinio uchafswm nifer y postiadau
|
||||
ownership: Nid oes modd pinio postiad rhywun arall
|
||||
reblog: Nid oes modd pinio hwb
|
||||
quote_policies:
|
||||
followers: Dilynwyr a defnyddwyr wedi'u crybwyll
|
||||
nobody: Dim ond defnyddwyr wedi'u crybwyll
|
||||
public: Pawb
|
||||
title: '%{name}: "%{quote}"'
|
||||
visibilities:
|
||||
direct: Uniongyrchol
|
||||
|
||||
@@ -1860,6 +1860,10 @@ it:
|
||||
limit: Hai già fissato in cima il massimo numero di post
|
||||
ownership: Non puoi fissare in cima un post di qualcun altro
|
||||
reblog: Un toot condiviso non può essere fissato in cima
|
||||
quote_policies:
|
||||
followers: Seguaci e utenti menzionati
|
||||
nobody: Solo gli utenti menzionati
|
||||
public: Tutti
|
||||
title: '%{name}: "%{quote}"'
|
||||
visibilities:
|
||||
direct: Diretto
|
||||
|
||||
@@ -56,6 +56,7 @@ cy:
|
||||
scopes: Pa APIs y bydd y rhaglen yn cael mynediad iddynt. Os dewiswch gwmpas lefel uchaf, nid oes angen i chi ddewis rhai unigol.
|
||||
setting_aggregate_reblogs: Peidiwch â dangos hybiau newydd ar bostiadau sydd wedi cael eu hybu'n ddiweddar (dim ond yn effeithio ar hybiau newydd ei dderbyn)
|
||||
setting_always_send_emails: Fel arfer ni fydd hysbysiadau e-bost yn cael eu hanfon pan fyddwch chi wrthi'n defnyddio Mastodon
|
||||
setting_default_quote_policy: Mae defnyddwyr sy'n cael eu crybwyll yn cael dyfynnu bob amser. Dim ond ar gyfer postiadau a grëwyd gyda'r fersiwn nesaf o Mastodon y bydd y gosodiad hwn yn dod i rym, ond gallwch ddewis eich dewis wrth baratoi.
|
||||
setting_default_sensitive: Mae cyfryngau sensitif wedi'u cuddio yn rhagosodedig a gellir eu datgelu trwy glicio
|
||||
setting_display_media_default: Cuddio cyfryngau wedi eu marcio'n sensitif
|
||||
setting_display_media_hide_all: Cuddio cyfryngau bob tro
|
||||
@@ -148,6 +149,13 @@ cy:
|
||||
min_age: Ni ddylai fod yn is na'r isafswm oedran sy'n ofynnol gan gyfreithiau eich awdurdodaeth.
|
||||
user:
|
||||
chosen_languages: Wedi eu dewis, dim ond tŵtiau yn yr ieithoedd hyn bydd yn cael eu harddangos mewn ffrydiau cyhoeddus
|
||||
date_of_birth:
|
||||
few: Mae'n rhai i ni wneud yn siŵr eich bod o leiaf yn %{count} i ddefnyddio Mastodon. Fyddwn ni ddim yn cadw hwn.
|
||||
many: Mae'n rhai i ni wneud yn siŵr eich bod o leiaf yn %{count} i ddefnyddio Mastodon. Fyddwn ni ddim yn cadw hwn.
|
||||
one: Mae'n rhai i ni wneud yn siŵr eich bod o leiaf yn %{count} i ddefnyddio Mastodon. Fyddwn ni ddim yn cadw hwn.
|
||||
other: Mae'n rhai i ni wneud yn siŵr eich bod o leiaf yn %{count} i ddefnyddio Mastodon. Fyddwn ni ddim yn cadw hwn.
|
||||
two: Mae'n rhai i ni wneud yn siŵr eich bod o leiaf yn %{count} i ddefnyddio Mastodon. Fyddwn ni ddim yn cadw hwn.
|
||||
zero: Gwnewch yn siŵr eich bod o leiaf yn %{count} i ddefnyddio Mastodon. Fyddwn ni ddim yn cadw hwn.
|
||||
role: Mae'r rôl yn rheoli pa ganiatâd sydd gan y defnyddiwr.
|
||||
user_role:
|
||||
color: Lliw i'w ddefnyddio ar gyfer y rôl drwy'r UI, fel RGB mewn fformat hecs
|
||||
@@ -228,6 +236,7 @@ cy:
|
||||
setting_boost_modal: Dangos deialog cadarnhau cyn rhoi hwb
|
||||
setting_default_language: Iaith postio
|
||||
setting_default_privacy: Preifatrwydd cyhoeddi
|
||||
setting_default_quote_policy: Pwy sy'n gallu dyfynnu
|
||||
setting_default_sensitive: Marcio cyfryngau fel eu bod yn sensitif bob tro
|
||||
setting_delete_modal: Dangos deialog cadarnhau cyn dileu postiad
|
||||
setting_disable_hover_cards: Analluogi rhagolwg proffil ar lusgo
|
||||
|
||||
@@ -56,6 +56,7 @@ it:
|
||||
scopes: A quali API l'applicazione potrà avere accesso. Se selezionate un ambito di alto livello, non c'è bisogno di selezionare quelle singole.
|
||||
setting_aggregate_reblogs: Non mostrare nuove condivisioni per toot che sono stati condivisi di recente (ha effetto solo sulle nuove condivisioni)
|
||||
setting_always_send_emails: Normalmente le notifiche e-mail non vengono inviate quando si utilizza attivamente Mastodon
|
||||
setting_default_quote_policy: Gli utenti menzionati sono sempre in grado di citare. Questa impostazione avrà effetto solo per i post che verranno creati con la prossima versione di Mastodon, ma puoi selezionare le tue preferenze in preparazione del rilascio della prossima versione
|
||||
setting_default_sensitive: Media con contenuti sensibili sono nascosti in modo predefinito e possono essere rivelati con un click
|
||||
setting_display_media_default: Nascondi media segnati come sensibili
|
||||
setting_display_media_hide_all: Nascondi sempre tutti i media
|
||||
@@ -148,6 +149,9 @@ it:
|
||||
min_age: Non si dovrebbe avere un'età inferiore a quella minima richiesta, dalle leggi della tua giurisdizione.
|
||||
user:
|
||||
chosen_languages: Quando una o più lingue sono contrassegnate, nelle timeline pubbliche vengono mostrati solo i toot nelle lingue selezionate
|
||||
date_of_birth:
|
||||
one: Dobbiamo verificare che tu abbia almeno %{count} anno per usare Mastodon. Non archivieremo questa informazione.
|
||||
other: Dobbiamo verificare che tu abbia almeno %{count} anni per usare Mastodon. Non archivieremo questa informazione.
|
||||
role: Il ruolo controlla quali permessi ha l'utente.
|
||||
user_role:
|
||||
color: Colore da usare per il ruolo in tutta l'UI, come RGB in formato esadecimale
|
||||
@@ -228,6 +232,7 @@ it:
|
||||
setting_boost_modal: Mostra dialogo di conferma prima del boost
|
||||
setting_default_language: Lingua dei post
|
||||
setting_default_privacy: Privacy dei post
|
||||
setting_default_quote_policy: Chi può citare
|
||||
setting_default_sensitive: Segna sempre i media come sensibili
|
||||
setting_delete_modal: Mostra dialogo di conferma prima di eliminare un post
|
||||
setting_disable_hover_cards: Disabilita l'anteprima del profilo al passaggio del mouse
|
||||
|
||||
@@ -10,6 +10,16 @@ namespace :api, format: false do
|
||||
end
|
||||
end
|
||||
|
||||
namespace :data_sharing do
|
||||
namespace :v0 do
|
||||
resources :backfill_requests, only: [:create] do
|
||||
resource :continuation, only: [:create]
|
||||
end
|
||||
|
||||
resources :event_subscriptions, only: [:create, :destroy]
|
||||
end
|
||||
end
|
||||
|
||||
resource :registration, only: [:create]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- [mailers, 2]
|
||||
- [pull]
|
||||
- [scheduler]
|
||||
- [fasp]
|
||||
|
||||
:scheduler:
|
||||
:listened_queues_only: true
|
||||
|
||||
18
db/migrate/20241213130230_create_fasp_subscriptions.rb
Normal file
18
db/migrate/20241213130230_create_fasp_subscriptions.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateFaspSubscriptions < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :fasp_subscriptions do |t|
|
||||
t.string :category, null: false
|
||||
t.string :subscription_type, null: false
|
||||
t.integer :max_batch_size, null: false
|
||||
t.integer :threshold_timeframe
|
||||
t.integer :threshold_shares
|
||||
t.integer :threshold_likes
|
||||
t.integer :threshold_replies
|
||||
t.references :fasp_provider, null: false, foreign_key: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
15
db/migrate/20250103131909_create_fasp_backfill_requests.rb
Normal file
15
db/migrate/20250103131909_create_fasp_backfill_requests.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateFaspBackfillRequests < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :fasp_backfill_requests do |t|
|
||||
t.string :category, null: false
|
||||
t.integer :max_count, null: false, default: 100
|
||||
t.string :cursor
|
||||
t.boolean :fulfilled, null: false, default: false
|
||||
t.references :fasp_provider, null: false, foreign_key: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
27
db/schema.rb
27
db/schema.rb
@@ -445,6 +445,17 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_28_095029) do
|
||||
t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true
|
||||
end
|
||||
|
||||
create_table "fasp_backfill_requests", force: :cascade do |t|
|
||||
t.string "category", null: false
|
||||
t.integer "max_count", default: 100, null: false
|
||||
t.string "cursor"
|
||||
t.boolean "fulfilled", default: false, null: false
|
||||
t.bigint "fasp_provider_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["fasp_provider_id"], name: "index_fasp_backfill_requests_on_fasp_provider_id"
|
||||
end
|
||||
|
||||
create_table "fasp_debug_callbacks", force: :cascade do |t|
|
||||
t.bigint "fasp_provider_id", null: false
|
||||
t.string "ip", null: false
|
||||
@@ -471,6 +482,20 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_28_095029) do
|
||||
t.index ["base_url"], name: "index_fasp_providers_on_base_url", unique: true
|
||||
end
|
||||
|
||||
create_table "fasp_subscriptions", force: :cascade do |t|
|
||||
t.string "category", null: false
|
||||
t.string "subscription_type", null: false
|
||||
t.integer "max_batch_size", null: false
|
||||
t.integer "threshold_timeframe"
|
||||
t.integer "threshold_shares"
|
||||
t.integer "threshold_likes"
|
||||
t.integer "threshold_replies"
|
||||
t.bigint "fasp_provider_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["fasp_provider_id"], name: "index_fasp_subscriptions_on_fasp_provider_id"
|
||||
end
|
||||
|
||||
create_table "favourites", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: nil, null: false
|
||||
t.datetime "updated_at", precision: nil, null: false
|
||||
@@ -1325,7 +1350,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_28_095029) do
|
||||
add_foreign_key "custom_filter_statuses", "statuses", on_delete: :cascade
|
||||
add_foreign_key "custom_filters", "accounts", on_delete: :cascade
|
||||
add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade
|
||||
add_foreign_key "fasp_backfill_requests", "fasp_providers"
|
||||
add_foreign_key "fasp_debug_callbacks", "fasp_providers"
|
||||
add_foreign_key "fasp_subscriptions", "fasp_providers"
|
||||
add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade
|
||||
add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade
|
||||
add_foreign_key "featured_tags", "accounts", on_delete: :cascade
|
||||
|
||||
@@ -15,4 +15,5 @@ Fabricator(:account) do
|
||||
user { |attrs| attrs[:domain].nil? ? Fabricate.build(:user, account: nil) : nil }
|
||||
uri { |attrs| attrs[:domain].nil? ? '' : "https://#{attrs[:domain]}/users/#{attrs[:username]}" }
|
||||
discoverable true
|
||||
indexable true
|
||||
end
|
||||
|
||||
9
spec/fabricators/fasp/backfill_request_fabricator.rb
Normal file
9
spec/fabricators/fasp/backfill_request_fabricator.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:fasp_backfill_request, from: 'Fasp::BackfillRequest') do
|
||||
category 'content'
|
||||
max_count 10
|
||||
cursor nil
|
||||
fulfilled false
|
||||
fasp_provider
|
||||
end
|
||||
8
spec/fabricators/fasp/subscription_fabricator.rb
Normal file
8
spec/fabricators/fasp/subscription_fabricator.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:fasp_subscription, from: 'Fasp::Subscription') do
|
||||
category 'content'
|
||||
subscription_type 'lifecycle'
|
||||
max_batch_size 10
|
||||
fasp_provider
|
||||
end
|
||||
83
spec/models/concerns/account/fasp_concern_spec.rb
Normal file
83
spec/models/concerns/account/fasp_concern_spec.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Account::FaspConcern, feature: :fasp do
|
||||
describe '#create' do
|
||||
let(:discoverable_attributes) do
|
||||
Fabricate.attributes_for(:account).except('user_id')
|
||||
end
|
||||
let(:undiscoverable_attributes) do
|
||||
discoverable_attributes.merge('discoverable' => false)
|
||||
end
|
||||
|
||||
context 'when account is discoverable' do
|
||||
it 'queues a job to notify provider' do
|
||||
Account.create(discoverable_attributes)
|
||||
|
||||
expect(Fasp::AnnounceAccountLifecycleEventWorker).to have_enqueued_sidekiq_job
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is not discoverable' do
|
||||
it 'does not queue a job' do
|
||||
Account.create(undiscoverable_attributes)
|
||||
|
||||
expect(Fasp::AnnounceAccountLifecycleEventWorker).to_not have_enqueued_sidekiq_job
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
before do
|
||||
# Create account and clear sidekiq queue so we only catch
|
||||
# jobs queued as part of the update
|
||||
account
|
||||
Sidekiq::Worker.clear_all
|
||||
end
|
||||
|
||||
context 'when account is discoverable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
it 'queues a job to notify provider' do
|
||||
expect { account.touch }.to enqueue_sidekiq_job(Fasp::AnnounceAccountLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account was discoverable before' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
it 'queues a job to notify provider' do
|
||||
expect do
|
||||
account.update(discoverable: false)
|
||||
end.to enqueue_sidekiq_job(Fasp::AnnounceAccountLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account has not been discoverable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com', discoverable: false) }
|
||||
|
||||
it 'does not queue a job' do
|
||||
expect { account.touch }.to_not enqueue_sidekiq_job(Fasp::AnnounceAccountLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
context 'when account is discoverable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
it 'queues a job to notify provider' do
|
||||
expect { account.destroy }.to enqueue_sidekiq_job(Fasp::AnnounceAccountLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is not discoverable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com', discoverable: false) }
|
||||
|
||||
it 'does not queue a job' do
|
||||
expect { account.destroy }.to_not enqueue_sidekiq_job(Fasp::AnnounceAccountLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
11
spec/models/concerns/favourite/fasp_concern_spec.rb
Normal file
11
spec/models/concerns/favourite/fasp_concern_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Favourite::FaspConcern, feature: :fasp do
|
||||
describe '#create' do
|
||||
it 'queues a job to notify provider' do
|
||||
expect { Fabricate(:favourite) }.to enqueue_sidekiq_job(Fasp::AnnounceTrendWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
123
spec/models/concerns/status/fasp_concern_spec.rb
Normal file
123
spec/models/concerns/status/fasp_concern_spec.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Status::FaspConcern, feature: :fasp do
|
||||
describe '#create' do
|
||||
context 'when account is indexable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
context 'when status is public' do
|
||||
it 'queues a job to notify provider of new status' do
|
||||
expect do
|
||||
Fabricate(:status, account:)
|
||||
end.to enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is not public' do
|
||||
it 'does not queue a job' do
|
||||
expect do
|
||||
Fabricate(:status, account:, visibility: :unlisted)
|
||||
end.to_not enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is in reply to another' do
|
||||
it 'queues a job to notify provider of possible trend' do
|
||||
parent = Fabricate(:status)
|
||||
expect do
|
||||
Fabricate(:status, account:, thread: parent)
|
||||
end.to enqueue_sidekiq_job(Fasp::AnnounceTrendWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is a reblog of another' do
|
||||
it 'queues a job to notify provider of possible trend' do
|
||||
original = Fabricate(:status, account:)
|
||||
expect do
|
||||
Fabricate(:status, account:, reblog: original)
|
||||
end.to enqueue_sidekiq_job(Fasp::AnnounceTrendWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is not indexable' do
|
||||
let(:account) { Fabricate(:account, indexable: false) }
|
||||
|
||||
it 'does not queue a job' do
|
||||
expect do
|
||||
Fabricate(:status, account:)
|
||||
end.to_not enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
before do
|
||||
# Create status and clear sidekiq queues to only catch
|
||||
# jobs queued due to the update
|
||||
status
|
||||
Sidekiq::Worker.clear_all
|
||||
end
|
||||
|
||||
context 'when account is indexable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:status) { Fabricate(:status, account:, visibility:) }
|
||||
|
||||
context 'when status is public' do
|
||||
let(:visibility) { :public }
|
||||
|
||||
it 'queues a job to notify provider' do
|
||||
expect { status.touch }.to enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status has not been public' do
|
||||
let(:visibility) { :unlisted }
|
||||
|
||||
it 'does not queue a job' do
|
||||
expect do
|
||||
status.touch
|
||||
end.to_not enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is not indexable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com', indexable: false) }
|
||||
let(:status) { Fabricate(:status, account:) }
|
||||
|
||||
it 'does not queue a job' do
|
||||
expect { status.touch }.to_not enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
let(:status) { Fabricate(:status, account:) }
|
||||
|
||||
before do
|
||||
# Create status and clear sidekiq queues to only catch
|
||||
# jobs queued due to the update
|
||||
status
|
||||
Sidekiq::Worker.clear_all
|
||||
end
|
||||
|
||||
context 'when account is indexable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
it 'queues a job to notify provider' do
|
||||
expect { status.destroy }.to enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account is not indexable' do
|
||||
let(:account) { Fabricate(:account, domain: 'example.com', indexable: false) }
|
||||
|
||||
it 'does not queue a job' do
|
||||
expect { status.destroy }.to_not enqueue_sidekiq_job(Fasp::AnnounceContentLifecycleEventWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
spec/models/fasp/backfill_request_spec.rb
Normal file
93
spec/models/fasp/backfill_request_spec.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::BackfillRequest do
|
||||
describe '#next_objects' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
let!(:statuses) { Fabricate.times(3, :status, account:).sort_by(&:id) }
|
||||
|
||||
context 'with a new backfill request' do
|
||||
subject { Fabricate(:fasp_backfill_request, max_count: 2) }
|
||||
|
||||
it 'returns the newest two statuses' do
|
||||
expect(subject.next_objects).to eq [statuses[2], statuses[1]]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cursor set to second newest status' do
|
||||
subject do
|
||||
Fabricate(:fasp_backfill_request, max_count: 2, cursor: statuses[1].id)
|
||||
end
|
||||
|
||||
it 'returns the oldest status' do
|
||||
expect(subject.next_objects).to eq [statuses[0]]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all statuses are not `indexable`' do
|
||||
subject { Fabricate(:fasp_backfill_request) }
|
||||
|
||||
let(:account) { Fabricate(:account, indexable: false) }
|
||||
|
||||
it 'returns no statuses' do
|
||||
expect(subject.next_objects).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#next_uris' do
|
||||
subject { Fabricate(:fasp_backfill_request) }
|
||||
|
||||
let(:statuses) { Fabricate.times(2, :status) }
|
||||
|
||||
it 'returns uris of the next objects' do
|
||||
uris = statuses.map(&:uri)
|
||||
|
||||
expect(subject.next_uris).to match_array(uris)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#more_objects_available?' do
|
||||
subject { Fabricate(:fasp_backfill_request, max_count: 2) }
|
||||
|
||||
context 'when more objects are available' do
|
||||
before { Fabricate.times(3, :status) }
|
||||
|
||||
it 'returns `true`' do
|
||||
expect(subject.more_objects_available?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no more objects are available' do
|
||||
before { Fabricate.times(2, :status) }
|
||||
|
||||
it 'returns `false`' do
|
||||
expect(subject.more_objects_available?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#advance!' do
|
||||
subject { Fabricate(:fasp_backfill_request, max_count: 2) }
|
||||
|
||||
context 'when more objects are available' do
|
||||
before { Fabricate.times(3, :status) }
|
||||
|
||||
it 'updates `cursor`' do
|
||||
expect { subject.advance! }.to change(subject, :cursor)
|
||||
expect(subject).to be_persisted
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no more objects are available' do
|
||||
before { Fabricate.times(2, :status) }
|
||||
|
||||
it 'sets `fulfilled` to `true`' do
|
||||
expect { subject.advance! }.to change(subject, :fulfilled)
|
||||
.from(false).to(true)
|
||||
expect(subject).to be_persisted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
spec/models/fasp/subscription_spec.rb
Normal file
33
spec/models/fasp/subscription_spec.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::Subscription do
|
||||
describe '#threshold=' do
|
||||
subject { described_class.new }
|
||||
|
||||
it 'allows setting all threshold values at once' do
|
||||
subject.threshold = {
|
||||
'timeframe' => 30,
|
||||
'shares' => 5,
|
||||
'likes' => 8,
|
||||
'replies' => 7,
|
||||
}
|
||||
|
||||
expect(subject.threshold_timeframe).to eq 30
|
||||
expect(subject.threshold_shares).to eq 5
|
||||
expect(subject.threshold_likes).to eq 8
|
||||
expect(subject.threshold_replies).to eq 7
|
||||
end
|
||||
end
|
||||
|
||||
describe '#timeframe_start' do
|
||||
subject { described_class.new(threshold_timeframe: 45) }
|
||||
|
||||
it 'returns a Time representing the beginning of the timeframe' do
|
||||
travel_to Time.zone.local(2025, 4, 7, 16, 40) do
|
||||
expect(subject.timeframe_start).to eq Time.zone.local(2025, 4, 7, 15, 55)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::Fasp::DataSharing::V0::BackfillRequests', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
describe 'POST /api/fasp/data_sharing/v0/backfill_requests' do
|
||||
let(:provider) { Fabricate(:fasp_provider) }
|
||||
|
||||
context 'with valid parameters' do
|
||||
it 'creates a new backfill request' do
|
||||
params = { category: 'content', maxCount: 10 }
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_data_sharing_v0_backfill_requests_url,
|
||||
method: :post,
|
||||
body: params)
|
||||
|
||||
expect do
|
||||
post api_fasp_data_sharing_v0_backfill_requests_path, headers:, params:, as: :json
|
||||
end.to change(Fasp::BackfillRequest, :count).by(1)
|
||||
expect(response).to have_http_status(201)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'does not create a backfill request' do
|
||||
params = { category: 'unknown', maxCount: 10 }
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_data_sharing_v0_backfill_requests_url,
|
||||
method: :post,
|
||||
body: params)
|
||||
|
||||
expect do
|
||||
post api_fasp_data_sharing_v0_backfill_requests_path, headers:, params:, as: :json
|
||||
end.to_not change(Fasp::BackfillRequest, :count)
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
spec/requests/api/fasp/data_sharing/v0/continuations_spec.rb
Normal file
22
spec/requests/api/fasp/data_sharing/v0/continuations_spec.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::Fasp::DataSharing::V0::Continuations', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
describe 'POST /api/fasp/data_sharing/v0/backfill_requests/:id/continuations' do
|
||||
let(:backfill_request) { Fabricate(:fasp_backfill_request) }
|
||||
let(:provider) { backfill_request.fasp_provider }
|
||||
|
||||
it 'queues a job to continue the given backfill request' do
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_data_sharing_v0_backfill_request_continuation_url(backfill_request),
|
||||
method: :post)
|
||||
|
||||
post api_fasp_data_sharing_v0_backfill_request_continuation_path(backfill_request), headers:, as: :json
|
||||
expect(response).to have_http_status(204)
|
||||
expect(Fasp::BackfillWorker).to have_enqueued_sidekiq_job(backfill_request.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::Fasp::DataSharing::V0::EventSubscriptions', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
describe 'POST /api/fasp/data_sharing/v0/event_subscriptions' do
|
||||
let(:provider) { Fabricate(:fasp_provider) }
|
||||
|
||||
context 'with valid parameters' do
|
||||
it 'creates a new subscription' do
|
||||
params = { category: 'content', subscriptionType: 'lifecycle', maxBatchSize: 10 }
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_data_sharing_v0_event_subscriptions_url,
|
||||
method: :post,
|
||||
body: params)
|
||||
|
||||
expect do
|
||||
post api_fasp_data_sharing_v0_event_subscriptions_path, headers:, params:, as: :json
|
||||
end.to change(Fasp::Subscription, :count).by(1)
|
||||
expect(response).to have_http_status(201)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'does not create a subscription' do
|
||||
params = { category: 'unknown' }
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_data_sharing_v0_event_subscriptions_url,
|
||||
method: :post,
|
||||
body: params)
|
||||
|
||||
expect do
|
||||
post api_fasp_data_sharing_v0_event_subscriptions_path, headers:, params:, as: :json
|
||||
end.to_not change(Fasp::Subscription, :count)
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/fasp/data_sharing/v0/event_subscriptions/:id' do
|
||||
let(:subscription) { Fabricate(:fasp_subscription) }
|
||||
let(:provider) { subscription.fasp_provider }
|
||||
|
||||
it 'deletes the subscription' do
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_data_sharing_v0_event_subscription_url(subscription),
|
||||
method: :delete)
|
||||
|
||||
expect do
|
||||
delete api_fasp_data_sharing_v0_event_subscription_path(subscription), headers:, as: :json
|
||||
end.to change(Fasp::Subscription, :count).by(-1)
|
||||
expect(response).to have_http_status(204)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -124,8 +124,6 @@ RSpec.describe ActivityPub::FetchAllRepliesWorker do
|
||||
|
||||
before do
|
||||
stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_ENABLED', true)
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
allow(FetchReplyWorker).to receive(:perform_async)
|
||||
all_items.each do |item|
|
||||
next if [top_note_uri, reply_note_uri].include? item
|
||||
|
||||
@@ -150,7 +148,7 @@ RSpec.describe ActivityPub::FetchAllRepliesWorker do
|
||||
|
||||
it 'fetches the top status only once' do
|
||||
_ = subject.perform(status.id, { request_id: 0 })
|
||||
expect(FetchReplyWorker).to have_received(:perform_async).with(top_note_uri, { prefetched_body: top_object.deep_stringify_keys, request_id: 0 })
|
||||
expect(FetchReplyWorker).to have_enqueued_sidekiq_job(top_note_uri, { 'prefetched_body' => top_object.deep_stringify_keys, 'request_id' => 0 })
|
||||
expect(a_request(:get, top_note_uri)).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do
|
||||
include ProviderRequestHelper
|
||||
|
||||
let(:account_uri) { 'https://masto.example.com/accounts/1' }
|
||||
let(:subscription) do
|
||||
Fabricate(:fasp_subscription, category: 'account')
|
||||
end
|
||||
let(:provider) { subscription.fasp_provider }
|
||||
let!(:stubbed_request) do
|
||||
stub_provider_request(provider,
|
||||
method: :post,
|
||||
path: '/data_sharing/v0/announcements',
|
||||
response_body: {
|
||||
source: {
|
||||
subscription: {
|
||||
id: subscription.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'account',
|
||||
eventType: 'new',
|
||||
objectUris: [account_uri],
|
||||
})
|
||||
end
|
||||
|
||||
it 'sends the account uri to subscribed providers' do
|
||||
described_class.new.perform(account_uri, 'new')
|
||||
|
||||
expect(stubbed_request).to have_been_made
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do
|
||||
include ProviderRequestHelper
|
||||
|
||||
let(:status_uri) { 'https://masto.example.com/status/1' }
|
||||
let(:subscription) do
|
||||
Fabricate(:fasp_subscription)
|
||||
end
|
||||
let(:provider) { subscription.fasp_provider }
|
||||
let!(:stubbed_request) do
|
||||
stub_provider_request(provider,
|
||||
method: :post,
|
||||
path: '/data_sharing/v0/announcements',
|
||||
response_body: {
|
||||
source: {
|
||||
subscription: {
|
||||
id: subscription.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'content',
|
||||
eventType: 'new',
|
||||
objectUris: [status_uri],
|
||||
})
|
||||
end
|
||||
|
||||
it 'sends the status uri to subscribed providers' do
|
||||
described_class.new.perform(status_uri, 'new')
|
||||
|
||||
expect(stubbed_request).to have_been_made
|
||||
end
|
||||
end
|
||||
52
spec/workers/fasp/announce_trend_worker_spec.rb
Normal file
52
spec/workers/fasp/announce_trend_worker_spec.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::AnnounceTrendWorker do
|
||||
include ProviderRequestHelper
|
||||
|
||||
let(:status) { Fabricate(:status) }
|
||||
let(:subscription) do
|
||||
Fabricate(:fasp_subscription,
|
||||
category: 'content',
|
||||
subscription_type: 'trends',
|
||||
threshold_timeframe: 15,
|
||||
threshold_likes: 2)
|
||||
end
|
||||
let(:provider) { subscription.fasp_provider }
|
||||
let!(:stubbed_request) do
|
||||
stub_provider_request(provider,
|
||||
method: :post,
|
||||
path: '/data_sharing/v0/announcements',
|
||||
response_body: {
|
||||
source: {
|
||||
subscription: {
|
||||
id: subscription.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'content',
|
||||
eventType: 'trending',
|
||||
objectUris: [status.uri],
|
||||
})
|
||||
end
|
||||
|
||||
context 'when the configured threshold is met' do
|
||||
before do
|
||||
Fabricate.times(2, :favourite, status:)
|
||||
end
|
||||
|
||||
it 'sends the account uri to subscribed providers' do
|
||||
described_class.new.perform(status.id, 'favourite')
|
||||
|
||||
expect(stubbed_request).to have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the configured threshold is not met' do
|
||||
it 'does not notify any provider' do
|
||||
described_class.new.perform(status.id, 'favourite')
|
||||
|
||||
expect(stubbed_request).to_not have_been_made
|
||||
end
|
||||
end
|
||||
end
|
||||
32
spec/workers/fasp/backfill_worker_spec.rb
Normal file
32
spec/workers/fasp/backfill_worker_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::BackfillWorker do
|
||||
include ProviderRequestHelper
|
||||
|
||||
let(:backfill_request) { Fabricate(:fasp_backfill_request) }
|
||||
let(:provider) { backfill_request.fasp_provider }
|
||||
let(:status) { Fabricate(:status) }
|
||||
let!(:stubbed_request) do
|
||||
stub_provider_request(provider,
|
||||
method: :post,
|
||||
path: '/data_sharing/v0/announcements',
|
||||
response_body: {
|
||||
source: {
|
||||
backfillRequest: {
|
||||
id: backfill_request.id.to_s,
|
||||
},
|
||||
},
|
||||
category: 'content',
|
||||
objectUris: [status.uri],
|
||||
moreObjectsAvailable: false,
|
||||
})
|
||||
end
|
||||
|
||||
it 'sends status uri to provider that requested backfill' do
|
||||
described_class.new.perform(backfill_request.id)
|
||||
|
||||
expect(stubbed_request).to have_been_made
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user