Page MenuHomePhorge

No OneTemporary

Size
69 KB
Referenced Files
None
Subscribers
None
diff --git a/changelog.d/oban-queues.change b/changelog.d/oban-queues.change
new file mode 100644
index 000000000..16df6409a
--- /dev/null
+++ b/changelog.d/oban-queues.change
@@ -0,0 +1 @@
+Oban queues have refactored to simplify the queue design
diff --git a/config/config.exs b/config/config.exs
index 956d067f3..039d68379 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,952 +1,942 @@
# .i;;;;i.
# iYcviii;vXY:
# .YXi .i1c.
# .YC. . in7.
# .vc. ...... ;1c.
# i7, .. .;1;
# i7, .. ... .Y1i
# ,7v .6MMM@; .YX,
# .7;. ..IMMMMMM1 :t7.
# .;Y. ;$MMMMMM9. :tc.
# vY. .. .nMMM@MMU. ;1v.
# i7i ... .#MM@M@C. .....:71i
# it: .... $MMM@9;.,i;;;i,;tti
# :t7. ..... 0MMMWv.,iii:::,,;St.
# .nC. ..... IMMMQ..,::::::,.,czX.
# .ct: ....... .ZMMMI..,:::::::,,:76Y.
# c2: ......,i..Y$M@t..:::::::,,..inZY
# vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
# i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
# iIS. ......:ii::..;@MI....,............;Ez.
# .I9. ......:i::::...8M1..................C0z.
# .z9; ......:i::::,.. .i:...................zWX.
# vbv ......,i::::,,. ................. :AQY
# c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
# :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
# :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
# .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
# 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
# 7C...::::::::::::,,,,.. .................... vSi.
# ;1;...,,::::::,......... .................. Yz:
# v97,......... .voC.
# izAotX7777777777777777777777777777777777777777Y7n92:
# .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
#
# !!! ATTENTION !!!
# DO NOT EDIT THIS FILE! THIS FILE CONTAINS THE DEFAULT VALUES FOR THE CON-
# FIGURATION! EDIT YOUR SECRET FILE (either prod.secret.exs, dev.secret.exs).
#
# This file is responsible for configuring your application
# and its dependencies with the aid of the Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
import Config
# General application configuration
config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo,
telemetry_event: [Pleroma.Repo.Instrumenter],
migration_lock: nil
config :pleroma, Pleroma.Captcha,
enabled: true,
seconds_valid: 300,
method: Pleroma.Captcha.Native
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
# Upload configuration
config :pleroma, Pleroma.Upload,
uploader: Pleroma.Uploaders.Local,
filters: [Pleroma.Upload.Filter.Dedupe],
link_name: false,
proxy_remote: false,
filename_display_max_length: 30,
default_description: nil,
base_url: nil
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
bucket_namespace: nil,
truncated_namespace: nil,
streaming_enabled: true
config :ex_aws, :s3,
# host: "s3.wasabisys.com", # required if not Amazon AWS
access_key_id: nil,
secret_access_key: nil,
# region: "us-east-1", # may be required for Amazon AWS
scheme: "https://"
config :pleroma, Pleroma.Uploaders.IPFS,
post_gateway_url: nil,
get_gateway_url: nil
config :pleroma, :emoji,
shortcode_globs: ["/emoji/custom/**/*.png"],
pack_extensions: [".png", ".gif"],
groups: [
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
],
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json",
shared_pack_cache_seconds_per_file: 60
config :pleroma, :uri_schemes,
valid_schemes: [
"https",
"http",
"dat",
"dweb",
"gopher",
"hyper",
"ipfs",
"ipns",
"irc",
"ircs",
"magnet",
"mailto",
"mumble",
"ssb",
"xmpp"
]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "localhost"],
http: [
ip: {127, 0, 0, 1}
],
protocol: "https",
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
live_view: [signing_salt: "U5ELgdEwTD3n1+D5s0rY0AMg8/y1STxZ3Zvsl3bWh+oBcGrYdil0rXqPMRd3Glcq"],
signing_salt: "CqaoopA2",
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
pubsub_server: Pleroma.PubSub,
secure_cookie_flag: true,
extra_cookie_attrs: [
"SameSite=Lax"
]
# Configures Elixir's Logger
config :logger, :console,
level: :debug,
format: "\n$time $metadata[$level] $message\n",
metadata: [:actor, :path, :type, :user]
config :logger, :ex_syslogger,
level: :debug,
ident: "pleroma",
format: "$metadata[$level] $message",
metadata: [:actor, :path, :type, :user]
config :mime, :types, %{
"application/xml" => ["xml"],
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
"application/ld+json" => ["activity+json"]
}
config :tesla, adapter: Tesla.Adapter.Hackney
# Configures http settings, upstream proxy etc.
config :pleroma, :http,
proxy_url: nil,
send_user_agent: true,
user_agent: :default,
adapter: []
config :pleroma, :instance,
name: "Pleroma",
email: "example@example.com",
notify_email: "noreply@example.com",
description: "Pleroma: An efficient and flexible fediverse server",
short_description: "",
background_image: "/images/city.jpg",
instance_thumbnail: "/instance/thumbnail.jpeg",
favicon: "/favicon.png",
limit: 5_000,
description_limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000,
avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000,
poll_limits: %{
max_options: 20,
max_option_chars: 200,
min_expiration: 0,
max_expiration: 365 * 24 * 60 * 60
},
registrations_open: true,
invites_enabled: false,
account_activation_required: false,
account_approval_required: false,
federating: true,
federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7,
allow_relay: true,
public: true,
quarantined_instances: [],
static_dir: "instance/static/",
allowed_post_formats: [
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
],
autofollowed_nicknames: [],
autofollowing_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
max_report_comment_size: 1000,
report_strip_status: true,
safe_dm_mentions: false,
healthcheck: false,
remote_post_retention_days: 90,
skip_thread_containment: true,
limit_to_local_content: :unauthenticated,
user_bio_length: 5000,
user_name_length: 100,
max_account_fields: 10,
max_remote_account_fields: 20,
account_field_name_length: 512,
account_field_value_length: 2048,
registration_reason_length: 500,
external_user_synchronization: true,
extended_nickname_format: true,
cleanup_attachments: false,
multi_factor_authentication: [
totp: [
# digits 6 or 8
digits: 6,
period: 30
],
backup_codes: [
number: 5,
length: 16
]
],
show_reactions: true,
password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
admin_privileges: [
:users_read,
:users_manage_invites,
:users_manage_activation_state,
:users_manage_tags,
:users_manage_credentials,
:users_delete,
:messages_read,
:messages_delete,
:instances_delete,
:reports_manage_reports,
:moderation_log_read,
:announcements_manage_announcements,
:emoji_manage_emoji,
:statistics_read
],
moderator_privileges: [:messages_delete, :reports_manage_reports],
max_endorsed_users: 20,
birthday_required: false,
birthday_min_age: 0,
max_media_attachments: 1_000
config :pleroma, :welcome,
direct_message: [
enabled: false,
sender_nickname: nil,
message: nil
],
chat_message: [
enabled: false,
sender_nickname: nil,
message: nil
],
email: [
enabled: false,
sender: nil,
subject: "Welcome to <%= instance_name %>",
html: "Welcome to <%= instance_name %>",
text: "Welcome to <%= instance_name %>"
]
config :pleroma, :feed,
post_title: %{
max_length: 100,
omission: "..."
}
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
# of custom emoji. Issue #275 discusses defanging that somehow.
allow_inline_images: true,
allow_headings: false,
allow_tables: false,
allow_fonts: false,
scrub_policy: [
Pleroma.HTML.Scrubber.Default,
Pleroma.HTML.Transform.MediaProxy
]
config :pleroma, :frontend_configurations,
pleroma_fe: %{
alwaysShowSubjectInput: true,
background: "/images/city.jpg",
collapseMessageWithSubject: false,
disableChat: false,
greentext: false,
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.svg",
logoMargin: ".1em",
logoMask: true,
minimalScopesMode: false,
noAttachmentLinks: false,
nsfwCensorImage: "",
postContentType: "text/plain",
redirectRootLogin: "/main/friends",
redirectRootNoLogin: "/main/all",
scopeCopy: true,
sidebarRight: false,
showFeaturesPanel: true,
showInstanceSpecificPanel: false,
subjectLineBehavior: "email",
theme: "pleroma-dark",
webPushNotifications: false
}
config :pleroma, :assets,
mascots: [
pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png",
mime_type: "image/png"
},
pleroma_fox_tan_shy: %{
url: "/images/pleroma-fox-tan-shy.png",
mime_type: "image/png"
}
],
default_mascot: :pleroma_fox_tan
config :pleroma, :manifest,
icons: [
%{
src: "/static/logo.svg",
sizes: "144x144",
purpose: "any",
type: "image/svg+xml"
}
],
theme_color: "#282c37",
background_color: "#191b22"
config :pleroma, :activitypub,
unfollow_blocked: true,
outgoing_blocks: true,
blockers_visible: true,
follow_handshake_timeout: 500,
note_replies_output_limit: 5,
sign_object_fetches: true,
authorized_fetch_mode: false
config :pleroma, :streamer,
workers: 3,
overflow_workers: 2
config :pleroma, :user, deny_follow_blocked: true
config :pleroma, :mrf_normalize_markup, scrub_policy: Pleroma.HTML.Scrubber.Default
config :pleroma, :mrf_rejectnonpublic,
allow_followersonly: false,
allow_direct: false
config :pleroma, :mrf_hellthread,
delist_threshold: 10,
reject_threshold: 20
config :pleroma, :mrf_simple,
media_removal: [],
media_nsfw: [],
federated_timeline_removal: [],
report_removal: [],
reject: [],
followers_only: [],
accept: [],
avatar_removal: [],
banner_removal: [],
reject_deletes: []
config :pleroma, :mrf_keyword,
reject: [],
federated_timeline_removal: [],
replace: []
config :pleroma, :mrf_emoji,
remove_url: [],
remove_shortcode: [],
federated_timeline_removal_url: [],
federated_timeline_removal_shortcode: []
config :pleroma, :mrf_hashtag,
sensitive: ["nsfw"],
reject: [],
federated_timeline_removal: []
config :pleroma, :mrf_subchain, match_actor: %{}
config :pleroma, :mrf_activity_expiration, days: 365
config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
config :pleroma, :mrf_dnsrbl,
nameserver: "127.0.0.1",
port: 53,
zone: "bl.pleroma.com"
# threshold of 7 days
config :pleroma, :mrf_object_age,
threshold: 604_800,
actions: [:delist, :strip_followers]
config :pleroma, :mrf_nsfw_api,
url: "http://127.0.0.1:5000/",
threshold: 0.7,
mark_sensitive: true,
unlist: false,
reject: false
config :pleroma, :mrf_follow_bot, follower_nickname: nil
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
config :pleroma, :mrf_force_mention,
mention_parent: true,
mention_quoted: true
config :pleroma, :mrf_antimentionspam, user_age_limit: 30_000
config :pleroma, :rich_media,
enabled: true,
ignore_hosts: [],
ignore_tld: ["local", "localdomain", "lan"],
parsers: [
Pleroma.Web.RichMedia.Parsers.TwitterCard,
Pleroma.Web.RichMedia.Parsers.OEmbed
],
failure_backoff: 60_000,
ttl_setters: [
Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl,
Pleroma.Web.RichMedia.Parser.TTL.Opengraph
],
max_body: 5_000_000
config :pleroma, :media_proxy,
enabled: false,
invalidation: [
enabled: false,
provider: Pleroma.Web.MediaProxy.Invalidation.Script
],
proxy_opts: [
redirect_on_failure: false,
max_body_length: 25 * 1_048_576,
# Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1
max_read_duration: 30_000,
http: [
follow_redirect: true,
pool: :media
]
],
whitelist: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
method: :purge,
headers: [],
options: []
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
script_path: nil,
url_format: nil
# Note: media preview proxy depends on media proxy to be enabled
config :pleroma, :media_preview_proxy,
enabled: false,
thumbnail_max_width: 600,
thumbnail_max_height: 600,
image_quality: 85,
min_content_length: 100 * 1024
config :pleroma, :shout,
enabled: true,
limit: 5_000
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason
config :phoenix, :json_library, Jason
config :phoenix, :filter_parameters, ["password", "confirm"]
config :pleroma, :gopher,
enabled: false,
ip: {0, 0, 0, 0},
port: 9999
config :pleroma, Pleroma.Web.Metadata,
providers: [
Pleroma.Web.Metadata.Providers.OpenGraph,
Pleroma.Web.Metadata.Providers.TwitterCard
],
unfurl_nsfw: false
config :pleroma, Pleroma.Web.Preload,
providers: [
Pleroma.Web.Preload.Providers.Instance
]
config :pleroma, :http_security,
enabled: true,
sts: false,
sts_max_age: 31_536_000,
ct_max_age: 2_592_000,
referrer_policy: "same-origin",
allow_unsafe_eval: false
config :cors_plug,
max_age: 86_400,
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
expose: [
"Link",
"X-RateLimit-Reset",
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-Request-Id",
"Idempotency-Key"
],
credentials: true,
headers: ["Authorization", "Content-Type", "Idempotency-Key"]
config :pleroma, Pleroma.User,
restricted_nicknames: [
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"check_password",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"user_exists",
"users",
"web",
"verify_credentials",
"update_credentials",
"relationships",
"search",
"confirmation_resend",
"mfa"
],
email_blacklist: []
config :pleroma, Oban,
repo: Pleroma.Repo,
log: false,
queues: [
activity_expiration: 10,
- token_expiration: 5,
- filter_expiration: 1,
- backup: 1,
federator_incoming: 5,
federator_outgoing: 5,
ingestion_queue: 50,
web_push: 50,
- mailer: 10,
transmogrifier: 20,
- scheduled_activities: 10,
- poll_notifications: 10,
background: 5,
- remote_fetcher: 2,
- attachments_cleanup: 1,
- new_users_digest: 1,
- mute_expire: 5,
search_indexing: [limit: 10, paused: true],
- rich_media_expiration: 2
+ slow: 1
],
plugins: [Oban.Plugins.Pruner],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
]
config :pleroma, :workers,
retries: [
federator_incoming: 5,
federator_outgoing: 5,
search_indexing: 2
]
config :pleroma, Pleroma.Formatter,
class: false,
rel: "ugc",
new_window: false,
truncate: false,
strip_prefix: false,
extra: true,
validate_tld: :no_scheme
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST") || "localhost",
port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn"
oauth_consumer_strategies =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|> to_string()
|> String.split()
|> Enum.map(&hd(String.split(&1, ":")))
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
strategy_module = String.to_atom(strategy_module_name)
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
end
config :ueberauth,
Ueberauth,
base_path: "/oauth",
providers: ueberauth_providers
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
config :pleroma, Pleroma.Emails.UserEmail,
logo: nil,
styling: %{
link_color: "#d8a070",
background_color: "#2C3645",
content_background_color: "#1B2635",
header_color: "#d8a070",
text_color: "#b9b9ba",
text_muted_color: "#b9b9ba"
}
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
config :pleroma, Pleroma.PromEx,
disabled: false,
manual_metrics_start_delay: :no_delay,
drop_metrics_groups: [],
grafana: [
host: System.get_env("GRAFANA_HOST", "http://localhost:3000"),
auth_token: System.get_env("GRAFANA_TOKEN"),
upload_dashboards_on_start: false,
folder_name: "BEAM",
annotate_app_lifecycle: true
],
metrics_server: [
port: 4021,
path: "/metrics",
protocol: :http,
pool_size: 5,
cowboy_opts: [],
auth_strategy: :none
],
datasource: "Prometheus"
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 25,
total_user_limit: 300,
enabled: true
config :pleroma, :email_notifications,
digest: %{
active: false,
interval: 7,
inactivity_threshold: 7
}
config :pleroma, :oauth2,
token_expires_in: 3600 * 24 * 365 * 100,
issue_new_refresh_token: true,
clean_expired_tokens: false
config :pleroma, :database, rum_enabled: false
config :pleroma, :features, improved_hashtag_timeline: :auto
config :pleroma, :populate_hashtags_table, fault_rate_allowance: 0.01
config :pleroma, :delete_context_objects, fault_rate_allowance: 0.01
config :pleroma, :env, Mix.env()
config :http_signatures,
adapter: Pleroma.Signature
config :pleroma, :rate_limit,
authentication: {60_000, 15},
timeline: {500, 3},
search: [{1000, 10}, {1000, 30}],
app_account_creation: {1_800_000, 25},
relations_actions: {10_000, 10},
relation_id_action: {60_000, 2},
statuses_actions: {10_000, 15},
status_id_action: {60_000, 3},
password_reset: {1_800_000, 5},
account_confirmation_resend: {8_640_000, 5},
ap_routes: {60_000, 15}
config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
config :pleroma, Pleroma.Web.Plugs.RemoteIp,
enabled: true,
headers: ["x-forwarded-for"],
proxies: [],
reserved: [
"127.0.0.0/8",
"::1/128",
"fc00::/7",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
]
config :pleroma, :static_fe, enabled: false
# Example of frontend configuration
# This example will make us serve the primary frontend from the
# frontends directory within your `:pleroma, :instance, static_dir`.
# e.g., instance/static/frontends/pleroma/develop/
#
# With no frontend configuration, the bundled files from the `static` directory will
# be used.
#
# config :pleroma, :frontends,
# primary: %{"name" => "pleroma-fe", "ref" => "develop"},
# admin: %{"name" => "admin-fe", "ref" => "stable"},
# available: %{...}
config :pleroma, :frontends,
available: %{
"kenoma" => %{
"name" => "kenoma",
"git" => "https://git.pleroma.social/lambadalambda/kenoma",
"build_url" =>
"https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "master"
},
"pleroma-fe" => %{
"name" => "pleroma-fe",
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"fedi-fe" => %{
"name" => "fedi-fe",
"git" => "https://git.pleroma.social/pleroma/fedi-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build_release",
"ref" => "master",
"custom-http-headers" => [
{"service-worker-allowed", "/"}
]
},
"admin-fe" => %{
"name" => "admin-fe",
"git" => "https://git.pleroma.social/pleroma/admin-fe",
"build_url" =>
"https://git.pleroma.social/pleroma/admin-fe/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "develop"
},
"soapbox" => %{
"name" => "soapbox",
"git" => "https://gitlab.com/soapbox-pub/soapbox",
"build_url" =>
"https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/${ref}/download?job=build-production",
"ref" => "v3.0.0-beta.1",
"build_dir" => "static"
},
"glitch-lily" => %{
"name" => "glitch-lily",
"git" => "https://lily-is.land/infra/glitch-lily",
"build_url" =>
"https://lily-is.land/infra/glitch-lily/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "servant",
"build_dir" => "public"
}
}
config :pleroma, :web_cache_ttl,
activity_pub: nil,
activity_pub_question: 30_000
config :pleroma, :modules, runtime_dir: "instance/modules"
config :pleroma, configurable_from_database: false
config :pleroma, Pleroma.Repo,
parameters: [gin_fuzzy_search_limit: "500", jit: "off"],
prepare: :unnamed
config :pleroma, :connections_pool,
reclaim_multiplier: 0.1,
connection_acquisition_wait: 250,
connection_acquisition_retries: 5,
max_connections: 250,
max_idle_time: 30_000,
retry: 0,
connect_timeout: 5_000
config :pleroma, :pools,
federation: [
size: 50,
max_waiting: 10,
recv_timeout: 10_000
],
media: [
size: 50,
max_waiting: 20,
recv_timeout: 15_000
],
upload: [
size: 25,
max_waiting: 5,
recv_timeout: 15_000
],
default: [
size: 10,
max_waiting: 2,
recv_timeout: 5_000
]
config :pleroma, :hackney_pools,
federation: [
max_connections: 50,
timeout: 150_000
],
media: [
max_connections: 50,
timeout: 150_000
],
upload: [
max_connections: 25,
timeout: 300_000
]
config :pleroma, :majic_pool, size: 2
private_instance? = :if_instance_is_private
config :pleroma, :restrict_unauthenticated,
timelines: %{local: private_instance?, federated: private_instance?},
profiles: %{local: private_instance?, remote: private_instance?},
activities: %{local: private_instance?, remote: private_instance?}
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
config :pleroma, :mrf,
policies: [
Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
Pleroma.Web.ActivityPub.MRF.TagPolicy,
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
],
transparency: true,
transparency_exclusions: []
config :tzdata, :http_client, Pleroma.HTTP.Tzdata
config :ex_aws, http_client: Pleroma.HTTP.ExAws
config :web_push_encryption, http_client: Pleroma.HTTP.WebPush
config :pleroma, :instances_favicons, enabled: false
config :floki, :html_parser, Floki.HTMLParser.FastHtml
config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
config :pleroma, Pleroma.User.Backup,
purge_after_days: 30,
limit_days: 7,
dir: nil,
process_wait_time: 30_000,
process_chunk_size: 100
config :pleroma, ConcurrentLimiter, [
{Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]},
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]},
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
]
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
config :pleroma, Pleroma.Search.Meilisearch,
url: "http://127.0.0.1:7700/",
private_key: nil,
initial_indexing_chunk_size: 100_000
config :pleroma, Pleroma.Application,
background_migrators: true,
internal_fetch: true,
load_custom_modules: true,
max_restarts: 3,
streamer_registry: true
config :pleroma, Pleroma.Uploaders.Uploader, timeout: 30_000
config :pleroma, Pleroma.Search.QdrantSearch,
qdrant_url: "http://127.0.0.1:6333/",
qdrant_api_key: "",
openai_url: "http://127.0.0.1:11345",
# The healthcheck url has to be set to nil when used with the real openai
# API, as it doesn't have a healthcheck endpoint.
openai_healthcheck_url: "http://127.0.0.1:11345/health",
openai_model: "snowflake/snowflake-arctic-embed-xs",
openai_api_key: "",
qdrant_index_configuration: %{
vectors: %{size: 384, distance: "Cosine"}
}
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex
index 63c6cb45b..c361d7d89 100644
--- a/lib/pleroma/scheduled_activity.ex
+++ b/lib/pleroma/scheduled_activity.ex
@@ -1,211 +1,211 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ScheduledActivity do
use Ecto.Schema
alias Ecto.Multi
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Workers.ScheduledActivityWorker
import Ecto.Query
import Ecto.Changeset
@type t :: %__MODULE__{}
@min_offset :timer.minutes(5)
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
schema "scheduled_activities" do
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:scheduled_at, :naive_datetime)
field(:params, :map)
timestamps()
end
def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
scheduled_activity
|> cast(attrs, [:scheduled_at, :params])
|> validate_required([:scheduled_at, :params])
|> validate_scheduled_at()
|> with_media_attachments()
end
defp with_media_attachments(
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
)
when is_list(media_ids) do
media_attachments =
Utils.attachments_from_ids(
%{media_ids: media_ids},
User.get_cached_by_id(changeset.data.user_id)
)
params =
params
|> Map.put("media_attachments", media_attachments)
|> Map.put("media_ids", media_ids)
put_change(changeset, :params, params)
end
defp with_media_attachments(changeset), do: changeset
def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
scheduled_activity
|> cast(attrs, [:scheduled_at])
|> validate_required([:scheduled_at])
|> validate_scheduled_at()
end
def validate_scheduled_at(changeset) do
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
cond do
not far_enough?(scheduled_at) ->
[scheduled_at: "must be at least 5 minutes from now"]
exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
[scheduled_at: "daily limit exceeded"]
exceeds_total_user_limit?(changeset.data.user_id) ->
[scheduled_at: "total limit exceeded"]
true ->
[]
end
end)
end
def exceeds_daily_user_limit?(user_id, scheduled_at) do
ScheduledActivity
|> where(user_id: ^user_id)
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
|> select([sa], count(sa.id))
|> Repo.one()
|> Kernel.>=(@config_impl.get([ScheduledActivity, :daily_user_limit]))
end
def exceeds_total_user_limit?(user_id) do
ScheduledActivity
|> where(user_id: ^user_id)
|> select([sa], count(sa.id))
|> Repo.one()
|> Kernel.>=(@config_impl.get([ScheduledActivity, :total_user_limit]))
end
def far_enough?(scheduled_at) when is_binary(scheduled_at) do
with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
far_enough?(scheduled_at)
else
_ -> false
end
end
def far_enough?(scheduled_at) do
now = NaiveDateTime.utc_now()
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
diff > @min_offset
end
def new(%User{} = user, attrs) do
changeset(%ScheduledActivity{user_id: user.id}, attrs)
end
@doc """
Creates ScheduledActivity and add to queue to perform at scheduled_at date
"""
@spec create(User.t(), map()) :: {:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def create(%User{} = user, attrs) do
Multi.new()
|> Multi.insert(:scheduled_activity, new(user, attrs))
|> maybe_add_jobs(@config_impl.get([ScheduledActivity, :enabled]))
|> Repo.transaction()
|> transaction_response
end
defp maybe_add_jobs(multi, true) do
multi
|> Multi.run(:scheduled_activity_job, fn _repo, %{scheduled_activity: activity} ->
%{activity_id: activity.id}
|> ScheduledActivityWorker.new(scheduled_at: activity.scheduled_at)
|> Oban.insert()
end)
end
defp maybe_add_jobs(multi, _), do: multi
def get(%User{} = user, scheduled_activity_id) do
ScheduledActivity
|> where(user_id: ^user.id)
|> where(id: ^scheduled_activity_id)
|> Repo.one()
end
@spec update(ScheduledActivity.t(), map()) ::
{:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def update(%ScheduledActivity{id: id} = scheduled_activity, attrs) do
with {:error, %Ecto.Changeset{valid?: true} = changeset} <-
{:error, update_changeset(scheduled_activity, attrs)} do
Multi.new()
|> Multi.update(:scheduled_activity, changeset)
|> Multi.update_all(:scheduled_job, job_query(id),
set: [scheduled_at: get_field(changeset, :scheduled_at)]
)
|> Repo.transaction()
|> transaction_response
end
end
@doc "Deletes a ScheduledActivity and linked jobs."
@spec delete(ScheduledActivity.t() | binary() | integer) ::
{:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def delete(%ScheduledActivity{id: id} = scheduled_activity) do
Multi.new()
|> Multi.delete(:scheduled_activity, scheduled_activity, stale_error_field: :id)
|> Multi.delete_all(:jobs, job_query(id))
|> Repo.transaction()
|> transaction_response
end
def delete(id) when is_binary(id) or is_integer(id) do
delete(%__MODULE__{id: id})
end
defp transaction_response(result) do
case result do
{:ok, %{scheduled_activity: scheduled_activity}} ->
{:ok, scheduled_activity}
{:error, _, changeset, _} ->
{:error, changeset}
end
end
def for_user_query(%User{} = user) do
ScheduledActivity
|> where(user_id: ^user.id)
end
def due_activities(offset \\ 0) do
naive_datetime =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(offset, :millisecond)
ScheduledActivity
|> where([sa], sa.scheduled_at < ^naive_datetime)
|> Repo.all()
end
def job_query(scheduled_activity_id) do
from(j in Oban.Job,
- where: j.queue == "scheduled_activities",
+ where: j.queue == "federator_outgoing",
where: fragment("args ->> 'activity_id' = ?::text", ^to_string(scheduled_activity_id))
)
end
end
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index 1f2c3835a..4b30fd21d 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -1,121 +1,121 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator do
alias Pleroma.Activity
alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Workers.PublisherWorker
alias Pleroma.Workers.ReceiverWorker
require Logger
@behaviour Pleroma.Web.Federator.Publishing
@doc """
Returns `true` if the distance to target object does not exceed max configured value.
Serves to prevent fetching of very long threads, especially useful on smaller instances.
Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161).
Applies to fetching of both ancestor (reply-to) and child (reply) objects.
"""
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
def allowed_thread_distance?(distance) do
max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
if max_distance && max_distance >= 0 do
# Default depth is 0 (an object has zero distance from itself in its thread)
(distance || 0) <= max_distance
else
true
end
end
# Client API
def incoming_ap_doc(%{params: params, req_headers: req_headers}) do
ReceiverWorker.enqueue(
"incoming_ap_doc",
%{"req_headers" => req_headers, "params" => params, "timeout" => :timer.seconds(20)},
priority: 2
)
end
def incoming_ap_doc(%{"type" => "Delete"} = params) do
- ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3)
+ ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3, queue: :slow)
end
def incoming_ap_doc(params) do
ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params})
end
@impl true
def publish(%{id: "pleroma:fakeid"} = activity) do
perform(:publish, activity)
end
@impl true
def publish(%Pleroma.Activity{data: %{"type" => type}} = activity) do
PublisherWorker.enqueue("publish", %{"activity_id" => activity.id},
priority: publish_priority(type)
)
end
defp publish_priority("Delete"), do: 3
defp publish_priority(_), do: 0
# Job Worker Callbacks
@spec perform(atom(), any()) :: {:ok, any()} | {:error, any()}
def perform(:publish_one, params), do: Publisher.publish_one(params)
def perform(:publish, activity) do
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
%User{} = actor = User.get_cached_by_ap_id(activity.data["actor"])
Publisher.publish(actor, activity)
end
def perform(:incoming_ap_doc, params) do
Logger.debug("Handling incoming AP activity")
actor =
params
|> Map.get("actor")
|> Utils.get_ap_id()
# NOTE: we use the actor ID to do the containment, this is fine because an
# actor shouldn't be acting on objects outside their own AP server.
with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
nil <- Activity.normalize(params["id"]),
{_, :ok} <-
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, activity}
else
{:correct_origin?, _} ->
Logger.debug("Origin containment failure for #{params["id"]}")
{:error, :origin_containment_failed}
%Activity{} ->
Logger.debug("Already had #{params["id"]}")
{:error, :already_present}
{:actor, e} ->
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
{:error, e}
{:error, {:validate_object, _}} = e ->
Logger.error("Incoming AP doc validation error: #{inspect(e)}")
Logger.debug(Jason.encode!(params, pretty: true))
e
e ->
# Just drop those for now
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
{:error, e}
end
end
end
diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex
index 4c1764053..0b570b70b 100644
--- a/lib/pleroma/workers/attachments_cleanup_worker.ex
+++ b/lib/pleroma/workers/attachments_cleanup_worker.ex
@@ -1,112 +1,112 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.AttachmentsCleanupWorker do
import Ecto.Query
alias Pleroma.Object
alias Pleroma.Repo
- use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
+ use Pleroma.Workers.WorkerHelper, queue: "slow"
@impl Oban.Worker
def perform(%Job{
args: %{
"op" => "cleanup_attachments",
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
}
}) do
if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
attachments
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|> fetch_objects
|> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
|> filter_objects
|> do_clean
end
{:ok, :success}
end
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(900)
defp do_clean({object_ids, attachment_urls}) do
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
base_url =
String.trim_trailing(
Pleroma.Upload.base_url(),
"/"
)
Enum.each(attachment_urls, fn href ->
href
|> String.trim_leading("#{base_url}")
|> uploader.delete_file()
end)
delete_objects(object_ids)
end
defp delete_objects([_ | _] = object_ids) do
Repo.delete_all(from(o in Object, where: o.id in ^object_ids))
end
defp delete_objects(_), do: :ok
# we should delete 1 object for any given attachment, but don't delete
# files if there are more than 1 object for it
defp filter_objects(objects) do
Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} ->
with 1 <- count do
{ids ++ [id], hrefs ++ [href]}
else
_ -> {ids ++ [id], hrefs}
end
end)
end
defp prepare_objects(objects, actor, names) do
objects
|> Enum.reduce(%{}, fn %{
id: id,
data: %{
"url" => [%{"href" => href}],
"actor" => obj_actor,
"name" => name
}
},
acc ->
Map.update(acc, href, %{id: id, count: 1}, fn val ->
case obj_actor == actor and name in names do
true ->
# set id of the actor's object that will be deleted
%{val | id: id, count: val.count + 1}
false ->
# another actor's object, just increase count to not delete file
%{val | count: val.count + 1}
end
end)
end)
end
defp fetch_objects(hrefs) do
from(o in Object,
where:
fragment(
"to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
o.data,
o.data,
^hrefs
)
)
# The query above can be time consumptive on large instances until we
# refactor how uploads are stored
|> Repo.all(timeout: :infinity)
end
end
diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex
index a485ddb4b..54ac31a3c 100644
--- a/lib/pleroma/workers/backup_worker.ex
+++ b/lib/pleroma/workers/backup_worker.ex
@@ -1,74 +1,74 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.BackupWorker do
- use Oban.Worker, queue: :backup, max_attempts: 1
+ use Oban.Worker, queue: :slow, max_attempts: 1
alias Oban.Job
alias Pleroma.User.Backup
def process(backup, admin_user_id \\ nil) do
%{"op" => "process", "backup_id" => backup.id, "admin_user_id" => admin_user_id}
|> new()
|> Oban.insert()
end
def schedule_deletion(backup) do
days = Pleroma.Config.get([Backup, :purge_after_days])
time = 60 * 60 * 24 * days
scheduled_at = Calendar.NaiveDateTime.add!(backup.inserted_at, time)
%{"op" => "delete", "backup_id" => backup.id}
|> new(scheduled_at: scheduled_at)
|> Oban.insert()
end
def delete(backup) do
%{"op" => "delete", "backup_id" => backup.id}
|> new()
|> Oban.insert()
end
@impl Oban.Worker
def perform(%Job{
args: %{"op" => "process", "backup_id" => backup_id, "admin_user_id" => admin_user_id}
}) do
with {:ok, %Backup{} = backup} <-
backup_id |> Backup.get() |> Backup.process(),
{:ok, _job} <- schedule_deletion(backup),
:ok <- Backup.remove_outdated(backup),
:ok <- maybe_deliver_email(backup, admin_user_id) do
{:ok, backup}
end
end
def perform(%Job{args: %{"op" => "delete", "backup_id" => backup_id}}) do
case Backup.get(backup_id) do
%Backup{} = backup -> Backup.delete(backup)
nil -> :ok
end
end
@impl Oban.Worker
def timeout(_job), do: :infinity
defp has_email?(user) do
not is_nil(user.email) and user.email != ""
end
defp maybe_deliver_email(backup, admin_user_id) do
has_mailer = Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled])
backup = backup |> Pleroma.Repo.preload(:user)
if has_email?(backup.user) and has_mailer do
backup
|> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id)
|> Pleroma.Emails.Mailer.deliver()
:ok
else
:ok
end
end
end
diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex
index 1c3e445aa..d2abb2d3b 100644
--- a/lib/pleroma/workers/cron/new_users_digest_worker.ex
+++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex
@@ -1,63 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
- use Pleroma.Workers.WorkerHelper, queue: "mailer"
+ use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker
def perform(_job) do
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
a_day_ago =
today
|> Timex.shift(days: -1)
|> Timex.beginning_of_day()
users_and_statuses =
%{
local: true,
order_by: :inserted_at
}
|> User.Query.build()
|> where([u], u.inserted_at >= ^a_day_ago and u.inserted_at < ^today)
|> Repo.all()
|> Enum.map(fn user ->
latest_status =
Activity
|> Activity.Queries.by_actor(user.ap_id)
|> Activity.Queries.by_type("Create")
|> Activity.with_preloaded_object()
|> order_by(desc: :inserted_at)
|> limit(1)
|> Repo.one()
total_statuses =
Activity
|> Activity.Queries.by_actor(user.ap_id)
|> Activity.Queries.by_type("Create")
|> Repo.aggregate(:count, :id)
{user, total_statuses, latest_status}
end)
if users_and_statuses != [] do
%{is_admin: true}
|> User.Query.build()
|> where([u], not is_nil(u.email))
|> Repo.all()
|> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses))
|> Enum.each(&Pleroma.Emails.Mailer.deliver/1)
end
end
:ok
end
end
diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex
index 940716558..652bf77e0 100644
--- a/lib/pleroma/workers/mailer_worker.ex
+++ b/lib/pleroma/workers/mailer_worker.ex
@@ -1,18 +1,18 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MailerWorker do
- use Pleroma.Workers.WorkerHelper, queue: "mailer"
+ use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
encoded_email
|> Base.decode64!()
|> :erlang.binary_to_term()
|> Pleroma.Emails.Mailer.deliver(config)
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
index 8ce458d48..8ad287a7f 100644
--- a/lib/pleroma/workers/mute_expire_worker.ex
+++ b/lib/pleroma/workers/mute_expire_worker.ex
@@ -1,23 +1,23 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MuteExpireWorker do
- use Pleroma.Workers.WorkerHelper, queue: "mute_expire"
+ use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
Pleroma.User.unmute(muter_id, mutee_id)
:ok
end
def perform(%Job{
args: %{"op" => "unmute_conversation", "user_id" => user_id, "activity_id" => activity_id}
}) do
Pleroma.Web.CommonAPI.remove_mute(user_id, activity_id)
:ok
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex
index 022d026f8..70df54193 100644
--- a/lib/pleroma/workers/poll_worker.ex
+++ b/lib/pleroma/workers/poll_worker.ex
@@ -1,48 +1,48 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PollWorker do
@moduledoc """
Generates notifications when a poll ends.
"""
- use Pleroma.Workers.WorkerHelper, queue: "poll_notifications"
+ use Pleroma.Workers.WorkerHelper, queue: "background"
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
@impl Oban.Worker
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
with %Activity{} = activity <- find_poll_activity(activity_id) do
Notification.create_poll_notifications(activity)
end
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
defp find_poll_activity(activity_id) do
with nil <- Activity.get_by_id(activity_id) do
{:error, :poll_activity_not_found}
end
end
def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
Object.normalize(activity),
{:ok, end_time} <- NaiveDateTime.from_iso8601(closed),
:gt <- NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) do
%{
op: "poll_end",
activity_id: activity_id
}
|> new(scheduled_at: end_time)
|> Oban.insert()
else
_ -> {:error, activity}
end
end
def schedule_poll_end(activity), do: {:error, activity}
end
diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex
index e554684fe..a65593b6e 100644
--- a/lib/pleroma/workers/purge_expired_activity.ex
+++ b/lib/pleroma/workers/purge_expired_activity.ex
@@ -1,75 +1,75 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PurgeExpiredActivity do
@moduledoc """
Worker which purges expired activity.
"""
- use Oban.Worker, queue: :activity_expiration, max_attempts: 1, unique: [period: :infinity]
+ use Oban.Worker, queue: :slow, max_attempts: 1, unique: [period: :infinity]
import Ecto.Query
alias Pleroma.Activity
@spec enqueue(map()) ::
{:ok, Oban.Job.t()}
| {:error, :expired_activities_disabled}
| {:error, :expiration_too_close}
def enqueue(args) do
with true <- enabled?() do
{scheduled_at, args} = Map.pop(args, :expires_at)
args
|> new(scheduled_at: scheduled_at)
|> Oban.insert()
end
end
@impl true
def perform(%Oban.Job{args: %{"activity_id" => id}}) do
with %Activity{} = activity <- find_activity(id),
%Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do
Pleroma.Web.CommonAPI.delete(activity.id, user)
end
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
defp enabled? do
with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do
{:error, :expired_activities_disabled}
end
end
defp find_activity(id) do
with nil <- Activity.get_by_id_with_object(id) do
{:error, :activity_not_found}
end
end
defp find_user(ap_id) do
with nil <- Pleroma.User.get_by_ap_id(ap_id) do
{:error, :user_not_found}
end
end
def get_expiration(id) do
from(j in Oban.Job,
where: j.state == "scheduled",
- where: j.queue == "activity_expiration",
+ where: j.queue == "slow",
where: fragment("?->>'activity_id' = ?", j.args, ^id)
)
|> Pleroma.Repo.one()
end
@spec expires_late_enough?(DateTime.t()) :: boolean()
def expires_late_enough?(scheduled_at) do
now = DateTime.utc_now()
diff = DateTime.diff(scheduled_at, now, :millisecond)
min_lifetime = Pleroma.Config.get([__MODULE__, :min_lifetime], 600)
diff > :timer.seconds(min_lifetime)
end
end
diff --git a/lib/pleroma/workers/purge_expired_filter.ex b/lib/pleroma/workers/purge_expired_filter.ex
index 9114aeb7f..1f6931e4c 100644
--- a/lib/pleroma/workers/purge_expired_filter.ex
+++ b/lib/pleroma/workers/purge_expired_filter.ex
@@ -1,46 +1,46 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PurgeExpiredFilter do
@moduledoc """
Worker which purges expired filters
"""
- use Oban.Worker, queue: :filter_expiration, max_attempts: 1, unique: [period: :infinity]
+ use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity]
import Ecto.Query
alias Oban.Job
alias Pleroma.Repo
@spec enqueue(%{filter_id: integer(), expires_at: DateTime.t()}) ::
{:ok, Job.t()} | {:error, Ecto.Changeset.t()}
def enqueue(args) do
{scheduled_at, args} = Map.pop(args, :expires_at)
args
|> new(scheduled_at: scheduled_at)
|> Oban.insert()
end
@impl true
def perform(%Job{args: %{"filter_id" => id}}) do
Pleroma.Filter
|> Repo.get(id)
|> Repo.delete()
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
@spec get_expiration(pos_integer()) :: Job.t() | nil
def get_expiration(id) do
from(j in Job,
where: j.state == "scheduled",
- where: j.queue == "filter_expiration",
+ where: j.queue == "background",
where: fragment("?->'filter_id' = ?", j.args, ^id)
)
|> Repo.one()
end
end
diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex
index 2ccd9e80b..1854bf561 100644
--- a/lib/pleroma/workers/purge_expired_token.ex
+++ b/lib/pleroma/workers/purge_expired_token.ex
@@ -1,32 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PurgeExpiredToken do
@moduledoc """
Worker which purges expired OAuth tokens
"""
- use Oban.Worker, queue: :token_expiration, max_attempts: 1
+ use Oban.Worker, queue: :background, max_attempts: 1
@spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) ::
{:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()}
def enqueue(args) do
{scheduled_at, args} = Map.pop(args, :valid_until)
args
|> __MODULE__.new(scheduled_at: scheduled_at)
|> Oban.insert()
end
@impl true
def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do
module
|> String.to_existing_atom()
|> Pleroma.Repo.get(id)
|> Pleroma.Repo.delete()
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex
index c26418483..ed04c54b2 100644
--- a/lib/pleroma/workers/remote_fetcher_worker.ex
+++ b/lib/pleroma/workers/remote_fetcher_worker.ex
@@ -1,35 +1,35 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.RemoteFetcherWorker do
alias Pleroma.Object.Fetcher
- use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
+ use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
{:ok, _object} ->
:ok
{:error, :forbidden} ->
{:discard, :forbidden}
{:error, :not_found} ->
{:discard, :not_found}
{:error, :allowed_depth} ->
{:discard, :allowed_depth}
{:error, _} = e ->
e
e ->
{:error, e}
end
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(10)
end
diff --git a/lib/pleroma/workers/rich_media_expiration_worker.ex b/lib/pleroma/workers/rich_media_expiration_worker.ex
index d7ae497a7..0b74687cf 100644
--- a/lib/pleroma/workers/rich_media_expiration_worker.ex
+++ b/lib/pleroma/workers/rich_media_expiration_worker.ex
@@ -1,15 +1,15 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.RichMediaExpirationWorker do
alias Pleroma.Web.RichMedia.Card
use Oban.Worker,
- queue: :rich_media_expiration
+ queue: :background
@impl Oban.Worker
def perform(%Job{args: %{"url" => url} = _args}) do
Card.delete(url)
end
end
diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex
index 4df84d00f..ab62686f4 100644
--- a/lib/pleroma/workers/scheduled_activity_worker.ex
+++ b/lib/pleroma/workers/scheduled_activity_worker.ex
@@ -1,61 +1,61 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ScheduledActivityWorker do
@moduledoc """
The worker to post scheduled activity.
"""
- use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
+ use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
require Logger
@impl Oban.Worker
def perform(%Job{args: %{"activity_id" => activity_id}}) do
with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id),
%User{} = user <- find_user(scheduled_activity.user_id) do
params = atomize_keys(scheduled_activity.params)
Repo.transaction(fn ->
{:ok, activity} = Pleroma.Web.CommonAPI.post(user, params)
{:ok, _} = ScheduledActivity.delete(scheduled_activity)
activity
end)
else
{:error, :scheduled_activity_not_found} = error ->
Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
error
{:error, :user_not_found} = error ->
Logger.error("#{__MODULE__} Couldn't find user for scheduled activity: #{activity_id}")
error
end
end
@impl Oban.Worker
def timeout(_job), do: :timer.seconds(5)
defp find_scheduled_activity(id) do
with nil <- Repo.get(ScheduledActivity, id) do
{:error, :scheduled_activity_not_found}
end
end
defp find_user(id) do
with nil <- User.get_cached_by_id(id) do
{:error, :user_not_found}
end
end
defp atomize_keys(map) do
Map.new(map, fn
{key, value} when is_map(value) -> {String.to_existing_atom(key), atomize_keys(value)}
{key, value} -> {String.to_existing_atom(key), value}
end)
end
end
diff --git a/priv/repo/migrations/20240527144418_oban_queues_refactor.exs b/priv/repo/migrations/20240527144418_oban_queues_refactor.exs
new file mode 100644
index 000000000..64ee28dfd
--- /dev/null
+++ b/priv/repo/migrations/20240527144418_oban_queues_refactor.exs
@@ -0,0 +1,32 @@
+defmodule Pleroma.Repo.Migrations.ObanQueuesRefactor do
+ use Ecto.Migration
+
+ @changed_queues [
+ {"attachments_cleanup", "slow"},
+ {"mailer", "background"},
+ {"mute_expire", "background"},
+ {"poll_notifications", "background"},
+ {"activity_expiration", "slow"},
+ {"filter_expiration", "background"},
+ {"token_expiration", "background"},
+ {"remote_fetcher", "background"},
+ {"rich_media_expiration", "background"}
+ ]
+
+ def up do
+ Enum.each(@changed_queues, fn {old, new} ->
+ execute("UPDATE oban_jobs SET queue = '#{new}' WHERE queue = '#{old}';")
+ end)
+
+ # Handled special as reverting this would not be ideal and leaving it is harmless
+ execute(
+ "UPDATE oban_jobs SET queue = 'federator_outgoing' WHERE queue = 'scheduled_activities';"
+ )
+ end
+
+ def down do
+ # Just move all slow queue jobs to background queue if we are reverting
+ # as the slow queue will not be processing jobs
+ execute("UPDATE oban_jobs SET queue = 'background' WHERE queue = 'slow';")
+ end
+end
diff --git a/test/pleroma/scheduled_activity_test.exs b/test/pleroma/scheduled_activity_test.exs
index 4818e8bcf..aaf643cfc 100644
--- a/test/pleroma/scheduled_activity_test.exs
+++ b/test/pleroma/scheduled_activity_test.exs
@@ -1,122 +1,121 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ScheduledActivityTest do
use Pleroma.DataCase, async: true
alias Pleroma.ScheduledActivity
alias Pleroma.Test.StaticConfig
alias Pleroma.UnstubbedConfigMock, as: ConfigMock
import Mox
import Pleroma.Factory
describe "creation" do
test "scheduled activities with jobs when ScheduledActivity enabled" do
ConfigMock
|> stub(:get, fn
[ScheduledActivity, :enabled] -> true
path -> StaticConfig.get(path)
end)
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, sa1} = ScheduledActivity.create(user, attrs)
{:ok, sa2} = ScheduledActivity.create(user, attrs)
- jobs =
- Repo.all(from(j in Oban.Job, where: j.queue == "scheduled_activities", select: j.args))
+ jobs = Repo.all(from(j in Oban.Job, where: j.queue == "federator_outgoing", select: j.args))
assert jobs == [%{"activity_id" => sa1.id}, %{"activity_id" => sa2.id}]
end
test "scheduled activities without jobs when ScheduledActivity disabled" do
ConfigMock
|> stub(:get, fn
[ScheduledActivity, :enabled] -> false
path -> StaticConfig.get(path)
end)
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, _sa1} = ScheduledActivity.create(user, attrs)
{:ok, _sa2} = ScheduledActivity.create(user, attrs)
jobs =
Repo.all(from(j in Oban.Job, where: j.queue == "scheduled_activities", select: j.args))
assert jobs == []
end
test "when daily user limit is exceeded" do
ConfigMock
|> stub_with(StaticConfig)
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, attrs)
{:error, changeset} = ScheduledActivity.create(user, attrs)
assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}]
end
test "when total user limit is exceeded" do
ConfigMock
|> stub_with(StaticConfig)
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
tomorrow =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|> NaiveDateTime.to_iso8601()
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
{:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
assert changeset.errors == [scheduled_at: {"total limit exceeded", []}]
end
test "when scheduled_at is earlier than 5 minute from now" do
ConfigMock
|> stub_with(StaticConfig)
user = insert(:user)
scheduled_at =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(4), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: scheduled_at}
{:error, changeset} = ScheduledActivity.create(user, attrs)
assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}]
end
end
end
diff --git a/test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs
index 632242221..2d6b2aee2 100644
--- a/test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs
@@ -1,147 +1,154 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
+ use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.UnstubbedConfigMock, as: ConfigMock
import Ecto.Query
import Mox
import Pleroma.Factory
setup do
ConfigMock
|> stub(:get, fn
[ScheduledActivity, :enabled] -> true
path -> Pleroma.Test.StaticConfig.get(path)
end)
:ok
end
test "shows scheduled activities" do
%{user: user, conn: conn} = oauth_access(["read:statuses"])
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
# min_id
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
result = json_response_and_validate_schema(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
# since_id
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
result = json_response_and_validate_schema(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
# max_id
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
result = json_response_and_validate_schema(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
end
test "shows a scheduled activity" do
%{user: user, conn: conn} = oauth_access(["read:statuses"])
scheduled_activity = insert(:scheduled_activity, user: user)
res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(res_conn, 200)
assert scheduled_activity_id == scheduled_activity.id |> to_string()
res_conn = get(conn, "/api/v1/scheduled_statuses/404")
assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
end
test "updates a scheduled activity" do
%{user: user, conn: conn} = oauth_access(["write:statuses"])
scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 60)
{:ok, scheduled_activity} =
ScheduledActivity.create(
user,
%{
scheduled_at: scheduled_at,
params: build(:note).data
}
)
- job = Repo.one(from(j in Oban.Job, where: j.queue == "scheduled_activities"))
+ job = Repo.one(from(j in Oban.Job, where: j.queue == "federator_outgoing"))
assert job.args == %{"activity_id" => scheduled_activity.id}
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at)
new_scheduled_at =
NaiveDateTime.utc_now()
|> Timex.shift(minutes: 120)
|> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime)
res_conn =
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
scheduled_at: new_scheduled_at
})
assert %{"scheduled_at" => expected_scheduled_at} =
json_response_and_validate_schema(res_conn, 200)
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
job = refresh_record(job)
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at)
res_conn =
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
end
test "deletes a scheduled activity" do
%{user: user, conn: conn} = oauth_access(["write:statuses"])
scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 60)
{:ok, scheduled_activity} =
ScheduledActivity.create(
user,
%{
scheduled_at: scheduled_at,
params: build(:note).data
}
)
- job = Repo.one(from(j in Oban.Job, where: j.queue == "scheduled_activities"))
-
- assert job.args == %{"activity_id" => scheduled_activity.id}
+ assert_enqueued(
+ worker: Pleroma.Workers.ScheduledActivityWorker,
+ args: %{"activity_id" => scheduled_activity.id},
+ queue: :federator_outgoing
+ )
res_conn =
conn
|> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{} = json_response_and_validate_schema(res_conn, 200)
refute Repo.get(ScheduledActivity, scheduled_activity.id)
- refute Repo.get(Oban.Job, job.id)
+
+ refute_enqueued(
+ worker: Pleroma.Workers.ScheduledActivityWorker,
+ args: %{"activity_id" => scheduled_activity.id}
+ )
res_conn =
conn
|> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 12:14 AM (16 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1768516
Default Alt Text
(69 KB)

Event Timeline