Merge pull request #2535 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to 4a7395d989
th-downstream
Claire 11 months ago committed by GitHub
commit dac2b56932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -50,6 +50,10 @@ You can contribute in the following ways:
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
## API Changes and Additions
Please note that any changes or additions made to the API should have an accompanying pull request on [our documentation repository](https://github.com/mastodon/documentation).
## Bug reports
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.

@ -118,7 +118,7 @@ GEM
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
addressable (2.8.5)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
android_key_attestation (0.3.0)
@ -220,9 +220,9 @@ GEM
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.4)
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
debug (1.9.0)
irb (~> 1.10)
reline (>= 0.3.8)
debug_inspector (1.1.0)
devise (4.9.3)
bcrypt (~> 3.0)
@ -484,8 +484,8 @@ GEM
nokogiri (1.15.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.2)
bigdecimal (~> 3.1)
oj (3.16.3)
bigdecimal (>= 3.0)
omniauth (2.1.1)
hashie (>= 3.4.6)
rack (>= 2.2.3)

@ -114,7 +114,7 @@ module ApplicationHelper
end
def fa_icon(icon, attributes = {})
class_names = attributes[:class]&.split(' ') || []
class_names = attributes[:class]&.split || []
class_names << 'fa'
class_names += icon.split.map { |cl| "fa-#{cl}" }

@ -18,6 +18,6 @@ export const notificationsUpdate = createAction(
playSound: boolean;
}) => ({
payload: args,
meta: { playSound: playSound ? { sound: 'boop' } : undefined },
meta: { sound: playSound ? 'boop' : undefined },
}),
);

@ -21,6 +21,7 @@
"account.blocked": "Blocked",
"account.browse_more_on_origin_server": "Browse more on the original profile",
"account.cancel_follow_request": "Cancel follow",
"account.copy": "Copy link to profile",
"account.direct": "Privately mention @{name}",
"account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocked": "Domain blocked",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Mark as read",
"conversation.open": "View conversation",
"conversation.with": "With {names}",
"copy_icon_button.copied": "Copied to clipboard",
"copypaste.copied": "Copied",
"copypaste.copy_to_clipboard": "Copy to clipboard",
"directory.federated": "From known fediverse",
@ -222,6 +224,7 @@
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
"empty_column.account_suspended": "Account suspended",
"empty_column.account_timeline": "No posts here!",
"empty_column.account_unavailable": "Profile unavailable",
@ -478,6 +481,8 @@
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Personalize your home feed",
"onboarding.profile.discoverable": "Make my profile discoverable",
"onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
"onboarding.profile.display_name": "Display name",
"onboarding.profile.display_name_hint": "Your full name or your fun name…",
"onboarding.profile.lead": "You can always complete this later in the settings, where even more customisation options are available.",
@ -530,6 +535,7 @@
"privacy.unlisted.short": "Unlisted",
"privacy_policy.last_updated": "Last updated {date}",
"privacy_policy.title": "Privacy Policy",
"recommended": "Recommended",
"refresh": "Refresh",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
@ -600,6 +606,7 @@
"search.quick_action.status_search": "Posts matching {x}",
"search.search_or_paste": "Search or paste URL",
"search_popout.full_text_search_disabled_message": "Unavailable on {domain}.",
"search_popout.full_text_search_logged_out_message": "Only available when logged in.",
"search_popout.language_code": "ISO language code",
"search_popout.options": "Search options",
"search_popout.quick_actions": "Quick actions",

@ -21,6 +21,7 @@
"account.blocked": "Blokita",
"account.browse_more_on_origin_server": "Foliumi pli ĉe la originala profilo",
"account.cancel_follow_request": "Nuligi peton por sekvado",
"account.copy": "Kopii ligilon al profilo",
"account.direct": "Private mencii @{name}",
"account.disable_notifications": "Ne plu sciigi min, kiam @{name} mesaĝas",
"account.domain_blocked": "Domajno blokita",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marki legita",
"conversation.open": "Vidi konversacion",
"conversation.with": "Kun {names}",
"copy_icon_button.copied": "Kopiis al kliptabulo",
"copypaste.copied": "Kopiita",
"copypaste.copy_to_clipboard": "Kopii al dosierujo",
"directory.federated": "El konata fediverso",
@ -202,7 +204,9 @@
"dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
"dismissable_banner.dismiss": "Eksigi",
"dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
"dismissable_banner.explore_statuses": "Ĉi tioj estas afiŝoj de socia reto kiu populariĝas hodiau.",
"dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.",
"dismissable_banner.public_timeline": "Ĉi tioj estas plej lastaj publikaj afiŝoj de personoj ĉe socia reto kiu personoj ĉe {domain} sekvas.",
"embed.instructions": "Enkorpigu ĉi tiun afiŝon en vian retejon per kopio de la suba kodo.",
"embed.preview": "Ĝi aperos tiel:",
"emoji_button.activity": "Agadoj",
@ -220,6 +224,7 @@
"emoji_button.search_results": "Serĉaj rezultoj",
"emoji_button.symbols": "Simboloj",
"emoji_button.travel": "Vojaĝoj kaj lokoj",
"empty_column.account_hides_collections": "Ĉi tiu uzanto elektis ne disponebligi ĉi tiu informon",
"empty_column.account_suspended": "Konto suspendita",
"empty_column.account_timeline": "Neniu afiŝo ĉi tie!",
"empty_column.account_unavailable": "Profilo ne disponebla",
@ -229,6 +234,8 @@
"empty_column.direct": "Vi ankoraŭ ne havas privatan mencion. Kiam vi sendos aŭ ricevos iun, tiu aperos ĉi tie.",
"empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.",
"empty_column.explore_statuses": "Nenio tendencas nun. Rekontrolu poste!",
"empty_column.favourited_statuses": "Vi ankoraŭ ne havas stelumitan afiŝon.",
"empty_column.favourites": "Ankoraŭ neniu stelumis tiun afiŝon.",
"empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
"empty_column.followed_tags": "Vi ankoraŭ ne sekvas iujn kradvortojn. Kiam vi faras, ili aperos ĉi tie.",
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
@ -292,19 +299,36 @@
"hashtag.column_settings.tag_mode.any": "Iu ajn",
"hashtag.column_settings.tag_mode.none": "Neniu",
"hashtag.column_settings.tag_toggle": "Aldoni pliajn etikedojn por ĉi tiu kolumno",
"hashtag.counter_by_accounts": "{count, plural,one {{counter} partoprenanto} other {{counter} partoprenantoj}}",
"hashtag.counter_by_uses": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}",
"hashtag.counter_by_uses_today": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}} hodiau",
"hashtag.follow": "Sekvi la kradvorton",
"hashtag.unfollow": "Ne plu sekvi la kradvorton",
"hashtags.and_other": "…kaj {count, plural,other {# pli}}",
"home.actions.go_to_explore": "Vidi kio populariĝas",
"home.actions.go_to_suggestions": "Trovi homojn por sekvi",
"home.column_settings.basic": "Bazaj agordoj",
"home.column_settings.show_reblogs": "Montri diskonigojn",
"home.column_settings.show_replies": "Montri respondojn",
"home.explore_prompt.body": "Via hejmafiŝaro havos miksitajn afiŝojn de kradvortoj kiujn vi elektis sekvi, personoj kiujn vi elektis sekvi, kaj afiŝoj kiujn ili suprenigis.",
"home.explore_prompt.title": "Ĉi tio estas via hejma paĝo en Mastodon.",
"home.hide_announcements": "Kaŝi la anoncojn",
"home.pending_critical_update.body": "Ĝisdatigu vian servilon de Mastodon kiel eble plej baldau!",
"home.pending_critical_update.link": "Vidi ĝisdatigojn",
"home.pending_critical_update.title": "Kritika sekurĝisdatigo estas disponebla!",
"home.show_announcements": "Montri anoncojn",
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumiti ĉi tiun afiŝon por sciigi la afiŝanton ke vi aprezigas ŝin kaj konservas por la estonteco.",
"interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povos sekvi {name} por vidi ties mesaĝojn en via hejmo.",
"interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
"interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu mesaĝo.",
"interaction_modal.login.action": "Prenu min hejmen",
"interaction_modal.login.prompt": "Domajno de via hejma servilo, ekz. mastodon.social",
"interaction_modal.no_account_yet": "Ĉu ne estas ĉe Mastodon?",
"interaction_modal.on_another_server": "En alia servilo",
"interaction_modal.on_this_server": "En ĉi tiu servilo",
"interaction_modal.sign_in": "Vi ne estas ensalutita al ĉi tiu servilo.",
"interaction_modal.sign_in_hint": "Gvideto: Tio estas la retejo kie vi registris. Vi ankau povas tajpi vian plenan uzantonomon!",
"interaction_modal.title.favourite": "Stelumi la afiŝon de {name}",
"interaction_modal.title.follow": "Sekvi {name}",
"interaction_modal.title.reblog": "Akceli la afiŝon de {name}",
"interaction_modal.title.reply": "Respondi al la afiŝo de {name}",
@ -320,6 +344,8 @@
"keyboard_shortcuts.direct": "por malfermi la kolumnon pri privataj mencioj",
"keyboard_shortcuts.down": "iri suben en la listo",
"keyboard_shortcuts.enter": "malfermi mesaĝon",
"keyboard_shortcuts.favourite": "Stelumi afiŝon",
"keyboard_shortcuts.favourites": "Malfermi la liston de la stelumoj",
"keyboard_shortcuts.federated": "Malfermi la frataran templinion",
"keyboard_shortcuts.heading": "Klavaraj mallongigoj",
"keyboard_shortcuts.home": "Malfermi la hejman templinion",
@ -366,6 +392,7 @@
"lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
"lists.subheading": "Viaj listoj",
"load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
"loading_indicator.label": "Ŝargado…",
"media_gallery.toggle_visible": "{number, plural, one {Kaŝi la bildon} other {Kaŝi la bildojn}}",
"moved_to_account_banner.text": "Via konto {disabledAccount} estas malvalidigita ĉar vi movis ĝin al {movedToAccount}.",
"mute_modal.duration": "Daŭro",
@ -382,6 +409,7 @@
"navigation_bar.domain_blocks": "Blokitaj domajnoj",
"navigation_bar.edit_profile": "Redakti profilon",
"navigation_bar.explore": "Esplori",
"navigation_bar.favourites": "Stelumoj",
"navigation_bar.filters": "Silentigitaj vortoj",
"navigation_bar.follow_requests": "Petoj de sekvado",
"navigation_bar.followed_tags": "Sekvataj kradvortoj",
@ -389,6 +417,7 @@
"navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Adiaŭi",
"navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.opened_in_classic_interface": "Afiŝoj, kontoj, kaj aliaj specifaj paĝoj kiuj estas malfermititaj defaulta en la klasika reta interfaco.",
"navigation_bar.personal": "Persone",
"navigation_bar.pins": "Alpinglitaj mesaĝoj",
"navigation_bar.preferences": "Preferoj",
@ -398,6 +427,7 @@
"not_signed_in_indicator.not_signed_in": "Necesas saluti por aliri tiun rimedon.",
"notification.admin.report": "{name} raportis {target}",
"notification.admin.sign_up": "{name} kreis konton",
"notification.favourite": "{name} stelumis vian afiŝon",
"notification.follow": "{name} eksekvis vin",
"notification.follow_request": "{name} petis sekvi vin",
"notification.mention": "{name} menciis vin",
@ -411,6 +441,7 @@
"notifications.column_settings.admin.report": "Novaj raportoj:",
"notifications.column_settings.admin.sign_up": "Novaj registriĝoj:",
"notifications.column_settings.alert": "Sciigoj de la retumilo",
"notifications.column_settings.favourite": "Stelumoj:",
"notifications.column_settings.filter_bar.advanced": "Montri ĉiujn kategoriojn",
"notifications.column_settings.filter_bar.category": "Rapida filtra breto",
"notifications.column_settings.filter_bar.show_bar": "Montri la breton de filtrilo",
@ -428,6 +459,7 @@
"notifications.column_settings.update": "Redaktoj:",
"notifications.filter.all": "Ĉiuj",
"notifications.filter.boosts": "Diskonigoj",
"notifications.filter.favourites": "Stelumoj",
"notifications.filter.follows": "Sekvoj",
"notifications.filter.mentions": "Mencioj",
"notifications.filter.polls": "Balotenketaj rezultoj",
@ -441,14 +473,29 @@
"notifications_permission_banner.enable": "Ŝalti retumilajn sciigojn",
"notifications_permission_banner.how_to_control": "Por ricevi sciigojn kiam Mastodon ne estas malfermita, ebligu labortablajn sciigojn. Vi povas regi precize kiuj specoj de interagoj generas labortablajn sciigojn per la supra butono {icon} post kiam ili estas ebligitaj.",
"notifications_permission_banner.title": "Neniam preterlasas iun ajn",
"onboarding.action.back": "Prenu min reen",
"onboarding.actions.back": "Prenu min reen",
"onboarding.actions.go_to_explore": "See what's trending",
"onboarding.actions.go_to_home": "Go to your home feed",
"onboarding.compose.template": "Saluton #Mastodon!",
"onboarding.follows.empty": "Bedaŭrinde, neniu rezulto estas montrebla nuntempe. Vi povas provi serĉi aŭ foliumi la esploran paĝon por trovi kontojn por sekvi, aŭ retrovi baldaŭ.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Trovebligi mian profilon",
"onboarding.profile.discoverable_hint": "Kiam vi aliĝi al trovebleco ĉe Mastodon, viaj afiŝoj eble aperos en serĉaj rezultoj kaj populariĝoj, kaj via profilo eble estas sugestota al personoj kun similaj intereseoj al vi.",
"onboarding.profile.display_name": "Publika nomo",
"onboarding.profile.display_name_hint": "Via plena nomo aŭ via kromnomo…",
"onboarding.profile.lead": "Vi ĉiam povas plenigi ĉi tion poste en la agordoj, kie eĉ pli da personecigagordoj estas disponeblaj.",
"onboarding.profile.note": "Sinprezento",
"onboarding.profile.note_hint": "Vi povas @mencii aliajn homojn aŭ #kradvortojn…",
"onboarding.profile.save_and_continue": "Konservi kaj daŭrigi",
"onboarding.profile.title": "Profila fikso",
"onboarding.profile.upload_avatar": "Alŝuti profilbildon",
"onboarding.profile.upload_header": "Alŝuti profilkapbildon",
"onboarding.share.lead": "Sciigi personojn pri kiel ili povas trovi vin ĉe Mastodon!",
"onboarding.share.message": "Mi estas {username} en #Mastodon! Sekvu min ĉe {url}",
"onboarding.share.next_steps": "Eblaj malantauaj paŝoj:",
"onboarding.share.title": "Disvastigi vian profilon",
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
"onboarding.start.skip": "Want to skip right ahead?",
"onboarding.start.title": "Vi atingas ĝin!",
@ -460,6 +507,9 @@
"onboarding.steps.setup_profile.title": "Customize your profile",
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
"onboarding.steps.share_profile.title": "Share your profile",
"onboarding.tips.2fa": "<strong>Ĉu vi scias?</strong> Vi povas sekurigi vian konton per efektivigi dufaktora autentigo en via kontoagordoj.",
"onboarding.tips.accounts_from_other_servers": "<strong>Ĉu vi scias?</strong> Ĉar Mastodon estas sencentra, kelkaj profiloj kiujn vi trovi estas gastigitaj ĉe aliaj serviloj kiuj ne estas via.",
"onboarding.tips.migration": "<strong>Ĉu vi scias?</strong> Se vi sentas ke {domain} ne estas bona servilelekto por vi en la estonteco, vi povas translokiĝi al alia servilo de Mastodon sen malgajni viajn sekvantojn.",
"password_confirmation.mismatching": "Pasvorto konfirmo ne kongruas",
"picture_in_picture.restore": "Remetu ĝin",
"poll.closed": "Finita",

@ -702,7 +702,7 @@
"timeline_hint.resources.followers": "Les abonnés",
"timeline_hint.resources.follows": "Les abonnements",
"timeline_hint.resources.statuses": "Messages plus anciens",
"trends.counter_by_accounts": "{count, plural, one {{counter} personne} other {{counter} personnes}} au cours {days, plural, one {des dernières 24h} other {des {days} derniers jours}}",
"trends.counter_by_accounts": "{count, plural, one {{counter} pers.} other {{counter} pers.}} sur {days, plural, one {les dernières 24h} other {les {days} derniers jours}}",
"trends.trending_now": "Tendance en ce moment",
"ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
"units.short.billion": "{count}Md",

@ -479,7 +479,7 @@
"onboarding.actions.go_to_home": "Ugrás a saját hírfolyamra",
"onboarding.compose.template": "Üdvözlet, #Mastodon!",
"onboarding.follows.empty": "Sajnos jelenleg nem jeleníthető meg eredmény. Kipróbálhatod a keresést vagy böngészheted a felfedező oldalon a követni kívánt személyeket, vagy próbáld meg később.",
"onboarding.follows.lead": "A saját hírfolyamod az elsődleges tapasztalás a Mastodonon. Minél több embert követsz, annál aktívabb és érdekesebb a dolog. Az induláshoz itt van néhány javaslat:",
"onboarding.follows.lead": "A kezdőlapod a Mastodon használatának elsődleges módja. Minél több embert követsz, annál aktívabbak és érdekesebbek lesznek a dolgok. Az induláshoz itt van néhány javaslat:",
"onboarding.follows.title": "Szabd személyre a kezdőlapodat",
"onboarding.profile.discoverable": "Saját profil beállítása felfedezhetőként",
"onboarding.profile.discoverable_hint": "A Mastodonon a felfedezhetőség választása esetén a saját bejegyzéseid megjelenhetnek a keresési eredmények és a felkapott tartalmak között, valamint a profilod a hozzád hasonló érdeklődési körrel rendelkező embereknél is ajánlásra kerülhet.",
@ -720,7 +720,7 @@
"upload_form.undo": "Törlés",
"upload_form.video_description": "Leírás siket, hallássérült, vak vagy gyengénlátó emberek számára",
"upload_modal.analyzing_picture": "Kép elemzése…",
"upload_modal.apply": "Alkalmazás",
"upload_modal.apply": "Alkalmaz",
"upload_modal.applying": "Alkalmazás…",
"upload_modal.choose_image": "Kép kiválasztása",
"upload_modal.description_placeholder": "A gyors, barna róka átugrik a lusta kutya fölött",

@ -239,7 +239,7 @@
"empty_column.follow_requests": "아직 팔로우 요청이 없습니다. 요청을 받았을 때 여기에 나타납니다.",
"empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.",
"empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
"empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람을 팔로우 하여 채워보세요. {suggestions}",
"empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람을 팔로우하여 채워보세요. {suggestions}",
"empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.",
"empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.",
"empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",

@ -1,6 +1,6 @@
{
"about.contact": "Ratio:",
"about.domain_blocks.no_reason_available": "ratio abdere est",
"about.domain_blocks.no_reason_available": "Ratio abdere est",
"account.account_note_header": "Annotatio",
"account.badges.bot": "Robotum",
"account.badges.group": "Congregatio",
@ -49,7 +49,7 @@
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
"embed.instructions": "Embed this status on your website by copying the code below.",
"emoji_button.food": "cibus et potus",
"emoji_button.food": "Cibus et potus",
"emoji_button.people": "Homines",
"emoji_button.search": "Quaerere...",
"empty_column.account_timeline": "Hic nulla contributa!",
@ -57,13 +57,13 @@
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"explore.trending_statuses": "Contributa",
"generic.saved": "servavit",
"generic.saved": "Servavit",
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.back": "Re navigare",
"keyboard_shortcuts.blocked": "Aperire listam usorum obstructorum",
"keyboard_shortcuts.boost": "Inlustrare publicatio",
"keyboard_shortcuts.column": "Columnam dirigere",
"keyboard_shortcuts.compose": "TextArea Compositi Attendere",
"keyboard_shortcuts.description": "Descriptio",
"keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.down": "to move down in the list",

@ -21,6 +21,7 @@
"account.blocked": "ဘလော့ထားသည်",
"account.browse_more_on_origin_server": "မူရင်းပရိုဖိုင်တွင် ပိုမိုကြည့်ရှုပါ။",
"account.cancel_follow_request": "စောင့်ကြည့်မှု ပယ်ဖျက်ခြင်း",
"account.copy": "လင့်ခ်ကို ပရိုဖိုင်သို့ ကူးယူပါ",
"account.direct": "@{name} သီးသန့် သိရှိနိုင်အောင် မန်းရှင်းခေါ်မည်",
"account.disable_notifications": "@{name} ပို့စ်တင်သည့်အခါ ကျွန်ုပ်ထံ အသိပေးခြင်း မပြုလုပ်ရန်။",
"account.domain_blocked": "ဒိုမိန်း ပိတ်ပင်ထားခဲ့သည်",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "ဖတ်ပြီးသားအဖြစ်မှတ်ထားပါ",
"conversation.open": "Conversation ကိုကြည့်မည်",
"conversation.with": "{အမည်များ} ဖြင့်",
"copy_icon_button.copied": "ကလစ်ဘုတ်သို့ ကူးပါ",
"copypaste.copied": "ကူယူပြီးပါပြီ",
"copypaste.copy_to_clipboard": "ကလစ်ဘုတ်သို့ ကူးပါ",
"directory.federated": "သင် သိသော ဖက်ဒီမှ",
@ -389,6 +391,7 @@
"lists.search": "မိမိဖောလိုးထားသူများမှရှာဖွေမည်",
"lists.subheading": "သင့်၏စာရင်းများ",
"load_pending": "{count, plural, one {# new item} other {# new items}}",
"loading_indicator.label": "လုပ်ဆောင်နေသည်…",
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
"moved_to_account_banner.text": "{movedToAccount} အကောင့်သို့ပြောင်းလဲထားသဖြင့် {disabledAccount} အကောင့်မှာပိတ်ထားသည်",
"mute_modal.duration": "ကြာချိန်",
@ -477,6 +480,13 @@
"onboarding.follows.empty": "ယခုအချိန် မည်သည့်ရလဒ်ကိုမျှ မပြသနိုင်ပါ။ လူများကိုစောင့်ကြည့်ရန်အတွက် Explore စာမျက်နှာကို အသုံးပြု၍ စမ်းကြည့်နိုင်သည် သို့မဟုတ် နောက်မှ ထပ်စမ်းကြည့်ပါ။",
"onboarding.follows.lead": "သင့်ကိုယ်ပိုင်ပို့စ်များ တင်နိုင်သည်။ သင်စောင့်ကြည့်သူ များလေလေ၊ စိတ်ဝင်စားစရာကောင်းသောပို့စ်များ တွေ့ရလေဖြစ်သည်။ ဤပရိုဖိုင်များမှာ ကောင်းမွန်သောအစပြုမှုတစ်ခုဖြစ်ပြီး ၎င်းတို့ကိုစောင့်ကြည့်ခြင်းမှလည်း အချိန်မရွေး ပယ်ဖျက်နိုင်ပါသည်။",
"onboarding.follows.title": "Mastodon တွင် ရေပန်းစားခြင်း",
"onboarding.profile.discoverable": "ပရိုဖိုင် ရှာဖွေနိုင်ပါမည်",
"onboarding.profile.display_name": "ဖော်ပြမည့်အမည်",
"onboarding.profile.display_name_hint": "သင့်အမည်အပြည့်အစုံ သို့မဟုတ် သင့်အမည်ပြောင်။",
"onboarding.profile.note": "ကိုယ်ရေးအကျဉ်း",
"onboarding.profile.save_and_continue": "သိမ်းပြီး ဆက်လုပ်ပါ",
"onboarding.profile.title": "ပရိုဖိုင်စနစ် ထည့်သွင်းခြင်း",
"onboarding.profile.upload_avatar": "ပရိုဖိုင်ပုံ အပ်လုဒ်လုပ်ပါ",
"onboarding.share.lead": "Mastodon တွင် သင့်အား မည်သို့ရှာတွေ့နိုင်သည်ကို အသိပေးပါ။",
"onboarding.share.message": "Mastodon ရှိ ကျွန်ုပ်၏အမည်မှာ {username} ဖြစ်သည်။ ကျွန်ုပ်ကို {url} တွင် စောင့်ကြည့်နိုင်ပါသည်",
"onboarding.share.next_steps": "ဖြစ်နိုင်ချေရှိသော နောက်အဆင့်များ -",
@ -520,6 +530,7 @@
"privacy.unlisted.short": "စာရင်းမသွင်းထားပါ",
"privacy_policy.last_updated": "နောက်ဆုံး ပြင်ဆင်ခဲ့သည့်ရက်စွဲ {date}",
"privacy_policy.title": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
"recommended": "အကြံပြုသည်",
"refresh": "ပြန်လည်စတင်ပါ",
"regeneration_indicator.label": "လုပ်ဆောင်နေသည်…",
"regeneration_indicator.sublabel": "သင့်ပင်မစာမျက်နှာကို ပြင်ဆင်နေပါသည်။",
@ -590,6 +601,7 @@
"search.quick_action.status_search": "{x} နှင့် ကိုက်ညီသော ပို့စ်များ",
"search.search_or_paste": "URL ရိုက်ထည့်ပါ သို့မဟုတ် ရှာဖွေပါ",
"search_popout.full_text_search_disabled_message": "{domain} တွင် မရနိုင်ပါ။",
"search_popout.full_text_search_logged_out_message": "အကောင့်ဝင်ထားမှသာ ရနိုင်သည်။",
"search_popout.language_code": "ISO ဘာသာစကားကုဒ်",
"search_popout.options": "ရွေးချယ်ထားသည်များ ရှာဖွေရန်",
"search_popout.quick_actions": "အမြန်လုပ်ဆောင်မှုများ",

@ -1 +1,53 @@
{}
{
"about.contact": "सम्पर्क:",
"about.disclaimer": "Mastodon नि:शुल्क, खुला स्रोत सफ्टवेयर, र Mastodon gGmbH को ट्रेडमार्क हो।",
"about.domain_blocks.no_reason_available": "कारण उपलब्ध छैन",
"about.domain_blocks.preamble": "Mastodon ले तपाइँलाई सामान्यतया फेडिभर्समा कुनै पनि अन्य सर्भरका सामग्री हेर्न र प्रयोगकर्ताहरूसँग अन्तरक्रिया गर्न दिन्छ। यी अपवादहरू हुन् जुन यस विशेष सर्भरमा बनाइएका छन्।",
"about.domain_blocks.silenced.title": "सीमित",
"about.domain_blocks.suspended.explanation": "यस सर्भरबाट कुनै पनि डेटा प्रशोधन, भण्डारण वा आदानप्रदान गरिने छैन, जसले यस सर्भरका प्रयोगकर्ताहरूसँग कुनै पनि अन्तरक्रिया वा सञ्चारलाई असम्भव बनाउँछ।",
"about.domain_blocks.suspended.title": "निलम्बित",
"about.not_available": "यो जानकारी यस सर्भरमा उपलब्ध गराइएको छैन।",
"about.powered_by": "{mastodon} द्वारा संचालित विकेन्द्रीकृत सामाजिक मिडिया",
"about.rules": "सर्भर नियमहरू",
"account.add_or_remove_from_list": "सूचीबाट थप्नुहोस् वा हटाउनुहोस्",
"account.badges.group": "समूह",
"account.block": "@{name} लाई ब्लक गर्नुहोस्",
"account.block_domain": "{domain} डोमेनलाई ब्लक गर्नुहोस्",
"account.block_short": "ब्लक",
"account.blocked": "ब्लक गरिएको",
"account.browse_more_on_origin_server": "मूल प्रोफाइलमा थप ब्राउज गर्नुहोस्",
"account.cancel_follow_request": "फलो अनुरोध रद्द गर्नुहोस",
"account.copy": "प्रोफाइलको लिङ्क प्रतिलिपि गर्नुहोस्",
"account.direct": "@{name} लाई निजी रूपमा उल्लेख गर्नुहोस्",
"account.disable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित नगर्नुहोस्",
"account.domain_blocked": "डोमेन ब्लक गरिएको छ",
"account.edit_profile": "प्रोफाइल सम्पादन गर्नुहोस्",
"account.enable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित गर्नुहोस्",
"account.endorse": "प्रोफाइलमा फिचर गर्नुहोस्",
"account.featured_tags.last_status_never": "कुनै पोस्ट छैन",
"account.follow": "फलो गर्नुहोस",
"account.followers.empty": "यस प्रयोगकर्तालाई अहिलेसम्म कसैले फलो गर्दैन।",
"account.follows.empty": "यो प्रयोगकर्ताले अहिलेसम्म कसैलाई फलो गरेको छैन।",
"account.go_to_profile": "प्रोफाइलमा जानुहोस्",
"account.hide_reblogs": "@{name} को बूस्टहरू लुकाउनुहोस्",
"account.link_verified_on": "यस लिङ्कको स्वामित्व {date} मा जाँच गरिएको थियो",
"account.media": "मिडिया",
"account.mention": "@{name} लाई उल्लेख गर्नुहोस्",
"account.no_bio": "कुनै विवरण प्रदान गरिएको छैन।",
"account.posts": "पोस्टहरू",
"account.requested": "स्वीकृतिको पर्खाइमा। फलो अनुरोध रद्द गर्न क्लिक गर्नुहोस्",
"account.requested_follow": "{name} ले तपाईंलाई फलो गर्न अनुरोध गर्नुभएको छ",
"account.share": "@{name} को प्रोफाइल सेयर गर्नुहोस्",
"account.show_reblogs": "@{name} को बूस्टहरू देखाउनुहोस्",
"account.statuses_counter": "{count, plural, one {{counter} पोस्ट} other {{counter} पोस्टहरू}}",
"account.unblock": "@{name} लाई अनब्लक गर्नुहोस्",
"account.unblock_domain": "{domain} डोमेनलाई अनब्लक गर्नुहोस्",
"account.unblock_short": "अनब्लक गर्नुहोस्",
"account.unendorse": "प्रोफाइलमा फिचर नगर्नुहोस्",
"account.unfollow": "अनफलो गर्नुहोस्",
"account_note.placeholder": "नोट लेख्न क्लिक गर्नुहोस्",
"admin.dashboard.retention.average": "औसत",
"admin.dashboard.retention.cohort_size": "नयाँ प्रयोगकर्ताहरू",
"alert.rate_limited.message": "कृपया {retry_time, time, medium} पछि पुन: प्रयास गर्नुहोस्।",
"alert.unexpected.message": "एउटा अनपेक्षित त्रुटि भयो।"
}

@ -7,7 +7,7 @@
"about.domain_blocks.silenced.explanation": "Normalmente não verá perfis e conteúdo deste servidor, a menos que os procure explicitamente ou opte por os seguir.",
"about.domain_blocks.silenced.title": "Limitados",
"about.domain_blocks.suspended.explanation": "Nenhum dado deste servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os utilizadores dessas instâncias.",
"about.domain_blocks.suspended.title": "Supensos",
"about.domain_blocks.suspended.title": "Suspensos",
"about.not_available": "Esta informação não foi disponibilizada neste servidor.",
"about.powered_by": "Rede social descentralizada baseada no {mastodon}",
"about.rules": "Regras do servidor",

@ -224,6 +224,7 @@
"emoji_button.search_results": "Výsledky hľadania",
"emoji_button.symbols": "Symboly",
"emoji_button.travel": "Cestovanie a miesta",
"empty_column.account_hides_collections": "Tento užívateľ si zvolil nesprístupniť túto informáciu",
"empty_column.account_suspended": "Účet bol pozastavený",
"empty_column.account_timeline": "Nie sú tu žiadne príspevky!",
"empty_column.account_unavailable": "Profil nedostupný",

@ -8,7 +8,7 @@
"about.domain_blocks.silenced.title": "已受限",
"about.domain_blocks.suspended.explanation": "來自此伺服器的資料都不會被處理、儲存或交換,也無法與此伺服器上的使用者互動或交流。",
"about.domain_blocks.suspended.title": "已停權",
"about.not_available": "無法於伺服器上使用此資訊。",
"about.not_available": "無法於伺服器上使用此資訊。",
"about.powered_by": "由 {mastodon} 提供的去中心化社群媒體",
"about.rules": "伺服器規則",
"account.account_note_header": "備註",
@ -34,9 +34,9 @@
"account.follow": "跟隨",
"account.followers": "跟隨者",
"account.followers.empty": "尚未有人跟隨這位使用者。",
"account.followers_counter": "被 {count, plural,one {{counter} 人}other {{counter} 人}}跟隨",
"account.followers_counter": "被 {count, plural, other {{counter} 人}}跟隨",
"account.following": "跟隨中",
"account.following_counter": "正在跟隨 {count, plural, one {{counter} 人} other {{counter} 人}}",
"account.following_counter": "正在跟隨 {count,plural,other {{counter} 人}}",
"account.follows.empty": "這位使用者尚未跟隨任何人。",
"account.follows_you": "跟隨了您",
"account.go_to_profile": "前往個人檔案",
@ -72,8 +72,8 @@
"account.unmute_notifications_short": "取消靜音推播通知",
"account.unmute_short": "解除靜音",
"account_note.placeholder": "按此新增備註",
"admin.dashboard.daily_retention": "註冊後使用者存留率(日)",
"admin.dashboard.monthly_retention": "註冊後使用者存留率(月)",
"admin.dashboard.daily_retention": "註冊後使用者存留率(日)",
"admin.dashboard.monthly_retention": "註冊後使用者存留率(月)",
"admin.dashboard.retention.average": "平均",
"admin.dashboard.retention.cohort": "註冊月份",
"admin.dashboard.retention.cohort_size": "新使用者",
@ -103,9 +103,9 @@
"bundle_modal_error.message": "載入此元件時發生錯誤。",
"bundle_modal_error.retry": "重試",
"closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。",
"closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon 。",
"closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon。",
"closed_registrations_modal.find_another_server": "尋找另一個伺服器",
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架一個自己的伺服器!",
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架一個自己的伺服器!",
"closed_registrations_modal.title": "註冊 Mastodon",
"column.about": "關於",
"column.blocks": "已封鎖的使用者",
@ -155,7 +155,7 @@
"compose_form.publish_form": "嘟出去",
"compose_form.publish_loud": "{publish}",
"compose_form.save_changes": "儲存變更",
"compose_form.sensitive.hide": "標記媒體為敏感內容",
"compose_form.sensitive.hide": "{count, plural, other {將媒體標記為敏感內容}}",
"compose_form.sensitive.marked": "此媒體被標記為敏感內容",
"compose_form.sensitive.unmarked": "此媒體未被標記為敏感內容",
"compose_form.spoiler.marked": "移除內容警告",
@ -207,14 +207,14 @@
"dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。",
"dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。",
"dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。",
"embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。",
"embed.instructions": "如要將此嘟文嵌入您的網站,請複製以下程式碼。",
"embed.preview": "它將顯示成這樣:",
"emoji_button.activity": "活動",
"emoji_button.clear": "清除",
"emoji_button.custom": "自訂",
"emoji_button.flags": "旗幟",
"emoji_button.food": "食物 & 飲料",
"emoji_button.label": "插入表情符號",
"emoji_button.label": "插入表情圖案",
"emoji_button.nature": "自然",
"emoji_button.not_found": "啊就沒這表情符號吼!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "物件",
@ -353,11 +353,11 @@
"keyboard_shortcuts.legend": "顯示此說明選單",
"keyboard_shortcuts.local": "開啟本站時間軸",
"keyboard_shortcuts.mention": "提及作者",
"keyboard_shortcuts.muted": "開啟靜音使用者列表",
"keyboard_shortcuts.muted": "開啟靜音使用者清單",
"keyboard_shortcuts.my_profile": "開啟個人檔案頁面",
"keyboard_shortcuts.notifications": "開啟通知欄",
"keyboard_shortcuts.open_media": "開啟媒體",
"keyboard_shortcuts.pinned": "開啟釘選的嘟文列表",
"keyboard_shortcuts.pinned": "開啟釘選的嘟文清單",
"keyboard_shortcuts.profile": "開啟作者的個人檔案頁面",
"keyboard_shortcuts.reply": "回應嘟文",
"keyboard_shortcuts.requests": "開啟跟隨請求列表",
@ -386,7 +386,7 @@
"lists.new.create": "新增列表",
"lists.new.title_placeholder": "新列表標題",
"lists.replies_policy.followed": "任何跟隨的使用者",
"lists.replies_policy.list": "列表成員",
"lists.replies_policy.list": "成員清單",
"lists.replies_policy.none": "沒有人",
"lists.replies_policy.title": "顯示回覆:",
"lists.search": "搜尋您跟隨的使用者",
@ -452,7 +452,7 @@
"notifications.column_settings.push": "推播通知",
"notifications.column_settings.reblog": "轉嘟:",
"notifications.column_settings.show": "於欄位中顯示",
"notifications.column_settings.sound": "播放音",
"notifications.column_settings.sound": "播放",
"notifications.column_settings.status": "新嘟文:",
"notifications.column_settings.unread_notifications.category": "未讀通知",
"notifications.column_settings.unread_notifications.highlight": "突顯未讀通知",
@ -477,7 +477,7 @@
"onboarding.actions.back": "返回",
"onboarding.actions.go_to_explore": "看看發生什麼新鮮事",
"onboarding.actions.go_to_home": "前往您的首頁時間軸",
"onboarding.compose.template": "哈囉 #Mastodon",
"onboarding.compose.template": "你好 #Mastodon",
"onboarding.follows.empty": "很遺憾,目前未能顯示任何結果。您可以嘗試使用搜尋、瀏覽探索頁面以找尋人們跟隨、或稍候再試。",
"onboarding.follows.lead": "您的首頁時間軸是 Mastodon 的核心體驗。若您跟隨更多人的話,它將會變得更活躍有趣。這些個人檔案也許是個好起點,您可以隨時取消跟隨他們!",
"onboarding.follows.title": "客製化您的首頁時間軸",
@ -540,7 +540,7 @@
"regeneration_indicator.label": "載入中…",
"regeneration_indicator.sublabel": "您的首頁時間軸正在準備中!",
"relative_time.days": "{number} 天",
"relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
"relative_time.full.days": "{number, plural, other {# 天}}前",
"relative_time.full.hours": "{number, plural, one {# 小時} other {# 小時}}前",
"relative_time.full.just_now": "剛剛",
"relative_time.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}前",
@ -620,7 +620,7 @@
"search_results.see_all": "檢視全部",
"search_results.statuses": "嘟文",
"search_results.title": "搜尋:{q}",
"server_banner.about_active_users": "最近三十日內使用此伺服器的人 (月活躍使用者)",
"server_banner.about_active_users": "最近三十日內使用此伺服器的人(月活躍使用者)",
"server_banner.active_users": "活躍使用者",
"server_banner.administered_by": "管理者:",
"server_banner.introduction": "{domain} 是由 {mastodon} 提供之去中心化社群網路一部分。",
@ -687,7 +687,7 @@
"status.translated_from_with": "透過 {provider} 翻譯 {lang}",
"status.uncached_media_warning": "無法預覽",
"status.unmute_conversation": "解除此對話的靜音",
"status.unpin": "個人檔案頁面取消釘選",
"status.unpin": "個人檔案頁面取消釘選",
"subscribed_languages.lead": "僅選定語言的嘟文才會出現於您的首頁上,並於變更後列出時間軸。選取「無」以接收所有語言的嘟文。",
"subscribed_languages.save": "儲存變更",
"subscribed_languages.target": "變更 {target} 的訂閱語言",

@ -4,7 +4,8 @@ class AttachmentBatch
# Maximum amount of objects you can delete in an S3 API call. It's
# important to remember that this does not correspond to the number
# of records in the batch, since records can have multiple attachments
LIMIT = 1_000
LIMIT = ENV.fetch('S3_BATCH_DELETE_LIMIT', 1000).to_i
MAX_RETRY = ENV.fetch('S3_BATCH_DELETE_RETRY', 3).to_i
# Attributes generated and maintained by Paperclip (not all of them
# are always used on every class, however)
@ -95,6 +96,7 @@ class AttachmentBatch
# objects can be processed at once, so we have to potentially
# separate them into multiple calls.
retries = 0
keys.each_slice(LIMIT) do |keys_slice|
logger.debug { "Deleting #{keys_slice.size} objects" }
@ -102,6 +104,17 @@ class AttachmentBatch
objects: keys_slice.map { |key| { key: key } },
quiet: true,
})
rescue => e
retries += 1
if retries < MAX_RETRY
logger.debug "Retry #{retries}/#{MAX_RETRY} after #{e.message}"
sleep 2**retries
retry
else
logger.error "Batch deletion from S3 failed after #{e.message}"
raise e
end
end
end

@ -14,9 +14,8 @@ class Vacuum::PreviewCardsVacuum
private
def vacuum_cached_images!
preview_cards_past_retention_period.find_each do |preview_card|
preview_card.image.destroy
preview_card.save
preview_cards_past_retention_period.find_in_batches do |preview_card|
AttachmentBatch.new(PreviewCard, preview_card).clear
end
end

@ -9,9 +9,7 @@
# url :string
#
class PreviewCardsStatus < ApplicationRecord
# Composite primary keys are not properly supported in Rails. However,
# we shouldn't need this anyway...
self.primary_key = nil
self.primary_key = [:preview_card_id, :status_id]
belongs_to :preview_card
belongs_to :status

@ -84,8 +84,7 @@ class Status < ApplicationRecord
has_and_belongs_to_many :tags
# Because of a composite primary key, the `dependent` option cannot be used on this association
has_one :preview_cards_status, inverse_of: :status # rubocop:disable Rails/HasManyOrHasOneDependent
has_one :preview_cards_status, inverse_of: :status, dependent: :delete
has_one :notification, as: :activity, dependent: :destroy
has_one :status_stat, inverse_of: :status, dependent: nil
@ -153,7 +152,6 @@ class Status < ApplicationRecord
# The `prepend: true` option below ensures this runs before
# the `dependent: destroy` callbacks remove relevant records
before_destroy :unlink_from_conversations!, prepend: true
before_destroy :reset_preview_card!
cache_associated :application,
:media_attachments,

@ -76,6 +76,15 @@ class FanOutOnWriteService < BaseService
LocalNotificationWorker.push_bulk(mentions) do |mention|
[mention.account_id, mention.id, 'Mention', 'mention']
end
next unless update?
# This may result in duplicate update payloads, but this ensures clients
# are aware of edits to posts only appearing in mention notifications
# (e.g. private mentions or mentions by people they do not follow)
PushUpdateWorker.push_bulk(mentions.filter { |mention| subscribed_to_streaming_api?(mention.account_id) }) do |mention|
[mention.account_id, @status.id, "timeline:#{mention.account_id}:notifications", { 'update' => true }]
end
end
end
@ -170,4 +179,8 @@ class FanOutOnWriteService < BaseService
def broadcastable?
@status.public_visibility? && !@account.silenced? && (!@status.reblog? || Setting.show_reblogs_in_public_timelines)
end
def subscribed_to_streaming_api?(account_id)
redis.exists?("subscribed:timeline:#{account_id}") || redis.exists?("subscribed:timeline:#{account_id}:notifications")
end
end

@ -100,7 +100,7 @@ class FetchOEmbedService
end
def validate(oembed)
oembed if oembed[:version].to_s == '1.0' && oembed[:type].present?
oembed if oembed.present? && oembed[:version].to_s == '1.0' && oembed[:type].present?
end
def html

@ -6,7 +6,9 @@ hr:
expires_at: Krajnji rok
options: Opcije
user:
agreement: Ugovor o uslugama
email: E-mail adresa
locale: Lokalitet
password: Lozinka
user/account:
username: Korisničko ime
@ -18,3 +20,16 @@ hr:
attributes:
username:
invalid: mora sadržavati samo slova, brojeve i _
reserved: je rezervisano
admin/webhook:
attributes:
url:
invalid: nije validan URL
doorkeeper/application:
attributes:
website:
invalid: nije validan URL
import:
attributes:
data:
malformed: je neispravan

@ -36,7 +36,7 @@ ru:
status:
attributes:
reblog:
taken: поста уже существует
taken: пост уже существует
user:
attributes:
email:

@ -28,7 +28,7 @@ zh-TW:
doorkeeper/application:
attributes:
website:
invalid: 不是有效的 URL
invalid: 不是有效的網址
import:
attributes:
data:

@ -29,7 +29,7 @@ zh-TW:
edit:
title: 編輯應用程式
form:
error: 唉呦!請看看表單以排查錯誤
error: 糟糕!請檢查表單以排查錯誤
help:
native_redirect_uri: 請使用 %{native_redirect_uri} 作本站測試
redirect_uri: 每行輸入一個 URI

@ -534,6 +534,7 @@ en-GB:
total_reported: Reports about them
total_storage: Media attachments
totals_time_period_hint_html: The totals displayed below include data for all time.
unknown_instance: There is currently no record of this domain on this server.
invites:
deactivate_all: Deactivate all
filter:
@ -610,6 +611,7 @@ en-GB:
created_at: Reported
delete_and_resolve: Delete posts
forwarded: Forwarded
forwarded_replies_explanation: This report is from a remote user and about remote content. It has been forwarded to you because the reported content is in reply to one of your users.
forwarded_to: Forwarded to %{domain}
mark_as_resolved: Mark as resolved
mark_as_sensitive: Mark as sensitive
@ -1038,6 +1040,14 @@ en-GB:
hint_html: Just one more thing! We need to confirm you're a human (this is so we can keep the spam out!). Solve the CAPTCHA below and click "Continue".
title: Security check
confirmations:
awaiting_review: Your e-mail address is confirmed! The %{domain} staff is now reviewing your registration. You will receive an e-mail if they approve your account!
awaiting_review_title: Your registration is being reviewed
clicking_this_link: clicking this link
login_link: log in
proceed_to_login_html: You can now proceed to %{login_link}.
redirect_to_app_html: You should have been redirected to the <strong>%{app_name}</strong> app. If that did not happen, try %{clicking_this_link} or manually return to the app.
registration_complete: Your registration on %{domain} is now complete!
welcome_title: Welcome, %{name}!
wrong_email_hint: If that e-mail address is not correct, you can change it in account settings.
delete_account: Delete account
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
@ -1099,6 +1109,7 @@ en-GB:
functional: Your account is fully operational.
pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved.
redirecting_to: Your account is inactive because it is currently redirecting to %{acct}.
self_destruct: As %{domain} is closing down, you will only get limited access to your account.
view_strikes: View past strikes against your account
too_fast: Form submitted too fast, try again.
use_security_key: Use security key
@ -1356,6 +1367,7 @@ en-GB:
'86400': 1 day
expires_in_prompt: Never
generate: Generate invite link
invalid: This invite is not valid
invited_by: 'You were invited by:'
max_uses:
one: 1 use
@ -1568,6 +1580,9 @@ en-GB:
over_daily_limit: You have exceeded the limit of %{limit} scheduled posts for today
over_total_limit: You have exceeded the limit of %{limit} scheduled posts
too_soon: The scheduled date must be in the future
self_destruct:
lead_html: Unfortunately, <strong>%{domain}</strong> is permanently closing down. If you had an account there, you will not be able to continue using it, but you can still request a backup of your data.
title: This server is closing down
sessions:
activity: Last activity
browser: Browser
@ -1736,6 +1751,7 @@ en-GB:
default: "%b %d, %Y, %H:%M"
month: "%b %Y"
time: "%H:%M"
with_time_zone: "%b %d, %Y, %H:%M %Z"
translation:
errors:
quota_exceeded: The server-wide usage quota for the translation service has been exceeded.

@ -1324,6 +1324,7 @@ my:
'86400': ၁ ရက်
expires_in_prompt: ဘယ်တော့မှ
generate: ဖိတ်ကြားချက်လင့်ခ် ဖန်တီးပါ
invalid: ဤဖိတ်ကြားချက်မှာ မမှန်ကန်ပါ
invited_by: သင့်ကို ဖိတ်ခေါ်ထားသည် -
max_uses:
other: "%{count} အသုံးပြုမှုများ"

@ -16,7 +16,7 @@ zh-TW:
acct: 指定要移動至的帳號的「使用者名稱@網域名稱」
account_warning_preset:
text: 您可使用嘟文語法,例如網址、「#」標籤與提及功能
title: 可選。不會向收件者顯示
title: 可選。不會向收件者顯示
admin_account_action:
include_statuses: 使用者可看到導致檢舉或警告的嘟文
send_email_notification: 使用者將收到帳號發生之事情的解釋

@ -656,6 +656,10 @@ sk:
rules_check:
action: Spravuj serverové pravidlá
message_html: Neurčil/a si žiadne serverové pravidlá.
software_version_critical_check:
action: Pozri dostupné aktualizácie
software_version_patch_check:
action: Pozri dostupné aktualizácie
upload_check_privacy_error:
action: Pozri tu pre viac informácií
upload_check_privacy_error_object_storage:

@ -467,7 +467,7 @@ zh-TW:
other: 錯誤嘗試於 %{count} 天。
no_failures_recorded: 報告中沒有錯誤。
title: 可用狀態
warning: 上一次嘗試連線至伺服器失敗
warning: 上一次嘗試連線至伺服器失敗
back_to_all: 所有
back_to_limited: 受限制的
back_to_warning: 警告
@ -876,7 +876,7 @@ zh-TW:
publishers:
no_publisher_selected: 因未選取任何發行者,所以什麼事都沒發生
shared_by_over_week:
other: 上週被 %{count} 使用者分享
other: 上週被 %{count} 使用者分享
title: 熱門連結
usage_comparison: 於今日被 %{today} 人分享,相較於昨日 %{yesterday} 人
not_allowed_to_trend: 不允許登上熱門
@ -1273,7 +1273,7 @@ zh-TW:
other: 選取 %{count} 個符合您搜尋的項目。
today: 今天
validation_errors:
other: 恩...似乎不太對勁耶?請檢查以下 %{count} 項錯誤
other: 恩...似乎發生了點錯誤?請檢查以下 %{count} 項錯誤
imports:
errors:
empty: 空的 CSV 檔案
@ -1796,7 +1796,7 @@ zh-TW:
welcome:
edit_profile_action: 設定個人檔案
edit_profile_step: 您可以設定您的個人檔案,包括上傳大頭貼、變更顯示名稱等等。您也可以選擇於新的跟隨者跟隨前,先對他們進行審核。
explanation: 下面是幾個小幫助,希望它們能幫到您
explanation: 以下是幾個小技巧,希望它們能幫到您
final_action: 開始嘟嘟
final_step: '開始嘟嘟吧!即使您現在沒有跟隨者,其他人仍然能於本站時間軸、主題標籤等地方,看到您的公開嘟文。試著用 #introductions 這個主題標籤介紹一下自己吧。'
full_handle: 您的完整帳號名稱
@ -1805,7 +1805,7 @@ zh-TW:
title: "%{name} 誠摯歡迎您的加入!"
users:
follow_limit_reached: 您無法跟隨多於 %{limit} 個人
go_to_sso_account_settings: 前往您的身分提供商 (identity provider) 之帳號設定
go_to_sso_account_settings: 前往您的身分識別提供者IdP之帳號設定
invalid_otp_token: 兩階段認證碼不正確
otp_lost_help_html: 如果您無法存取這兩者,您可以透過 %{email} 與我們聯繫
seamless_external_login: 由於您是由外部系統登入,所以不能設定密碼與電子郵件。

@ -97,6 +97,8 @@ module Mastodon::CLI
say("Removed #{custom_emojis_count} custom emojis#{dry_run_mode_suffix}", :green)
end
CRAWL_SLEEP_TIME = 20
option :concurrency, type: :numeric, default: 50, aliases: [:c]
option :format, type: :string, default: 'summary', aliases: [:f]
option :exclude_suspended, type: :boolean, default: false, aliases: [:x]
@ -168,8 +170,8 @@ module Mastodon::CLI
pool.post(domain, &work_unit)
end
sleep 20
sleep 20 until pool.queue_length.zero?
sleep CRAWL_SLEEP_TIME
sleep CRAWL_SLEEP_TIME until pool.queue_length.zero?
pool.shutdown
pool.wait_for_termination(20)

@ -185,15 +185,15 @@ module Mastodon::CLI
end
def schema_has_instances_view?
ActiveRecord::Migrator.current_version >= 2020_12_06_004238
migrator_version >= 2020_12_06_004238
end
def verify_schema_version!
if ActiveRecord::Migrator.current_version < MIN_SUPPORTED_VERSION
if migrator_version < MIN_SUPPORTED_VERSION
say 'Your version of the database schema is too old and is not supported by this script.', :red
say 'Please update to at least Mastodon 3.0.0 before running this script.', :red
exit(1)
elsif ActiveRecord::Migrator.current_version > MAX_SUPPORTED_VERSION
elsif migrator_version > MAX_SUPPORTED_VERSION
say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow
exit(1) unless yes?('Continue anyway? (Yes/No)')
end
@ -228,7 +228,7 @@ module Mastodon::CLI
end
say 'Restoring index_accounts_on_username_and_domain_lower…'
if ActiveRecord::Migrator.current_version < 2020_06_20_164023
if migrator_version < 2020_06_20_164023
ActiveRecord::Base.connection.add_index :accounts, 'lower (username), lower(domain)', name: 'index_accounts_on_username_and_domain_lower', unique: true
else
ActiveRecord::Base.connection.add_index :accounts, "lower (username), COALESCE(lower(domain), '')", name: 'index_accounts_on_username_and_domain_lower', unique: true
@ -238,7 +238,7 @@ module Mastodon::CLI
ActiveRecord::Base.connection.execute('REINDEX INDEX search_index;')
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_uri;')
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_url;')
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_domain_and_id;') if ActiveRecord::Migrator.current_version >= 2023_05_24_190515
ActiveRecord::Base.connection.execute('REINDEX INDEX index_accounts_on_domain_and_id;') if migrator_version >= 2023_05_24_190515
end
def deduplicate_users!
@ -269,15 +269,15 @@ module Mastodon::CLI
say 'Restoring users indexes…'
ActiveRecord::Base.connection.add_index :users, ['confirmation_token'], name: 'index_users_on_confirmation_token', unique: true
ActiveRecord::Base.connection.add_index :users, ['email'], name: 'index_users_on_email', unique: true
ActiveRecord::Base.connection.add_index :users, ['remember_token'], name: 'index_users_on_remember_token', unique: true if ActiveRecord::Migrator.current_version < 2022_01_18_183010
ActiveRecord::Base.connection.add_index :users, ['remember_token'], name: 'index_users_on_remember_token', unique: true if migrator_version < 2022_01_18_183010
if ActiveRecord::Migrator.current_version < 2022_03_10_060641
if migrator_version < 2022_03_10_060641
ActiveRecord::Base.connection.add_index :users, ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true
else
ActiveRecord::Base.connection.add_index :users, ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true, where: 'reset_password_token IS NOT NULL', opclass: :text_pattern_ops
end
ActiveRecord::Base.connection.execute('REINDEX INDEX index_users_on_unconfirmed_email;') if ActiveRecord::Migrator.current_version >= 2023_07_02_151753
ActiveRecord::Base.connection.execute('REINDEX INDEX index_users_on_unconfirmed_email;') if migrator_version >= 2023_07_02_151753
end
def deduplicate_users_process_confirmation_token
@ -292,7 +292,7 @@ module Mastodon::CLI
end
def deduplicate_users_process_remember_token
if ActiveRecord::Migrator.current_version < 2022_01_18_183010
if migrator_version < 2022_01_18_183010
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM users WHERE remember_token IS NOT NULL GROUP BY remember_token HAVING count(*) > 1").each do |row|
users = User.where(id: row['ids'].split(',')).sort_by(&:updated_at).reverse.drop(1)
say "Unsetting remember token for those accounts: #{users.map { |user| user.account.acct }.join(', ')}", :yellow
@ -346,7 +346,7 @@ module Mastodon::CLI
remove_index_if_exists!(:announcement_reactions, 'index_announcement_reactions_on_account_id_and_announcement_id')
say 'Removing duplicate account identity proofs…'
say 'Removing duplicate announcement reactions…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM announcement_reactions GROUP BY account_id, announcement_id, name HAVING count(*) > 1").each do |row|
AnnouncementReaction.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end
@ -371,7 +371,7 @@ module Mastodon::CLI
end
say 'Restoring conversations indexes…'
if ActiveRecord::Migrator.current_version < 2022_03_07_083603
if migrator_version < 2022_03_07_083603
ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true
else
ActiveRecord::Base.connection.add_index :conversations, ['uri'], name: 'index_conversations_on_uri', unique: true, where: 'uri IS NOT NULL', opclass: :text_pattern_ops
@ -431,7 +431,7 @@ module Mastodon::CLI
def deduplicate_domain_blocks!
remove_index_if_exists!(:domain_blocks, 'index_domain_blocks_on_domain')
say 'Deduplicating domain_allows…'
say 'Deduplicating domain_blocks…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM domain_blocks GROUP BY domain HAVING count(*) > 1").each do |row|
domain_blocks = DomainBlock.where(id: row['ids'].split(',')).by_severity.reverse.to_a
@ -462,7 +462,7 @@ module Mastodon::CLI
UnavailableDomain.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end
say 'Restoring domain_allows indexes…'
say 'Restoring unavailable_domains indexes…'
ActiveRecord::Base.connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true
end
@ -488,7 +488,7 @@ module Mastodon::CLI
end
say 'Restoring media_attachments indexes…'
if ActiveRecord::Migrator.current_version < 2022_03_10_060626
if migrator_version < 2022_03_10_060626
ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true
else
ActiveRecord::Base.connection.add_index :media_attachments, ['shortcode'], name: 'index_media_attachments_on_shortcode', unique: true, where: 'shortcode IS NOT NULL', opclass: :text_pattern_ops
@ -521,7 +521,7 @@ module Mastodon::CLI
end
say 'Restoring statuses indexes…'
if ActiveRecord::Migrator.current_version < 2022_03_10_060706
if migrator_version < 2022_03_10_060706
ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true
else
ActiveRecord::Base.connection.add_index :statuses, ['uri'], name: 'index_statuses_on_uri', unique: true, where: 'uri IS NOT NULL', opclass: :text_pattern_ops
@ -543,7 +543,7 @@ module Mastodon::CLI
end
say 'Restoring tags indexes…'
if ActiveRecord::Migrator.current_version < 2021_04_21_121431
if migrator_version < 2021_04_21_121431
ActiveRecord::Base.connection.add_index :tags, 'lower((name)::text)', name: 'index_tags_on_name_lower', unique: true
else
ActiveRecord::Base.connection.execute 'CREATE UNIQUE INDEX CONCURRENTLY index_tags_on_name_lower_btree ON tags (lower(name) text_pattern_ops)'
@ -707,12 +707,16 @@ module Mastodon::CLI
end
end
def migrator_version
ActiveRecord::Migrator.current_version
end
def find_duplicate_accounts
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM accounts GROUP BY lower(username), COALESCE(lower(domain), '') HAVING count(*) > 1")
end
def remove_index_if_exists!(table, name)
ActiveRecord::Base.connection.remove_index(table, name: name)
ActiveRecord::Base.connection.remove_index(table, name: name) if ActiveRecord::Base.connection.index_name_exists?(table, name)
rescue ArgumentError, ActiveRecord::StatementInvalid
nil
end

@ -205,7 +205,7 @@
"prettier": "^3.0.0",
"react-test-renderer": "^18.2.0",
"stylelint": "^15.10.1",
"stylelint-config-standard-scss": "^11.0.0",
"stylelint-config-standard-scss": "^12.0.0",
"typescript": "^5.0.4",
"webpack-dev-server": "^3.11.3",
"yargs": "^17.7.2"

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::IdentityProofsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
end
end

@ -1,25 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::ListsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') }
let(:account) { Fabricate(:account) }
let(:list) { Fabricate(:list, account: user.account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
user.account.follow!(account)
list.accounts << account
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id }
expect(response).to have_http_status(200)
end
end
end

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::LookupController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #show' do
it 'returns http success' do
get :show, params: { account_id: account.id, acct: account.acct }
expect(response).to have_http_status(200)
end
end
end

@ -1,40 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Accounts::PinsController do
let(:john) { Fabricate(:user) }
let(:kevin) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: john.id, scopes: 'write:accounts') }
before do
kevin.account.followers << john.account
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'POST #create' do
subject { post :create, params: { account_id: kevin.account.id } }
it 'creates account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(1)
expect(response).to have_http_status(200)
end
end
describe 'DELETE #destroy' do
subject { delete :destroy, params: { account_id: kevin.account.id } }
before do
Fabricate(:account_pin, account: john.account, target_account: kevin.account)
end
it 'destroys account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(-1)
expect(response).to have_http_status(200)
end
end
end

@ -1,22 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Accounts::SearchController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #show' do
it 'returns http success' do
get :show, params: { q: 'query' }
expect(response).to have_http_status(200)
end
end
end

@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::FeaturedTags::SuggestionsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
end
end

@ -1,22 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V2::SuggestionsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
end
end
end

File diff suppressed because it is too large Load Diff

@ -4,22 +4,29 @@ require 'rails_helper'
require 'mastodon/cli/cache'
describe Mastodon::CLI::Cache do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#clear' do
let(:action) { :clear }
before { allow(Rails.cache).to receive(:clear) }
it 'clears the Rails cache' do
expect { cli.invoke(:clear) }.to output(
a_string_including('OK')
).to_stdout
expect { subject }
.to output_results('OK')
expect(Rails.cache).to have_received(:clear)
end
end
describe '#recount' do
let(:action) { :recount }
context 'with the `accounts` argument' do
let(:arguments) { ['accounts'] }
let(:account_stat) { Fabricate(:account_stat) }
@ -29,9 +36,8 @@ describe Mastodon::CLI::Cache do
end
it 're-calculates account records in the cache' do
expect { cli.invoke(:recount, arguments) }.to output(
a_string_including('OK')
).to_stdout
expect { subject }
.to output_results('OK')
expect(account_stat.reload.statuses_count).to be_zero
end
@ -46,9 +52,8 @@ describe Mastodon::CLI::Cache do
end
it 're-calculates account records in the cache' do
expect { cli.invoke(:recount, arguments) }.to output(
a_string_including('OK')
).to_stdout
expect { subject }
.to output_results('OK')
expect(status_stat.reload.replies_count).to be_zero
end
@ -58,9 +63,9 @@ describe Mastodon::CLI::Cache do
let(:arguments) { ['other-type'] }
it 'Exits with an error message' do
expect { cli.invoke(:recount, arguments) }.to output(
a_string_including('Unknown')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('Unknown')
.and raise_error(SystemExit)
end
end
end

@ -4,42 +4,45 @@ require 'rails_helper'
require 'mastodon/cli/canonical_email_blocks'
describe Mastodon::CLI::CanonicalEmailBlocks do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#find' do
let(:action) { :find }
let(:arguments) { ['user@example.com'] }
context 'when a block is present' do
before { Fabricate(:canonical_email_block, email: 'user@example.com') }
it 'announces the presence of the block' do
expect { cli.invoke(:find, arguments) }.to output(
a_string_including('user@example.com is blocked')
).to_stdout
expect { subject }
.to output_results('user@example.com is blocked')
end
end
context 'when a block is not present' do
it 'announces the absence of the block' do
expect { cli.invoke(:find, arguments) }.to output(
a_string_including('user@example.com is not blocked')
).to_stdout
expect { subject }
.to output_results('user@example.com is not blocked')
end
end
end
describe '#remove' do
let(:action) { :remove }
let(:arguments) { ['user@example.com'] }
context 'when a block is present' do
before { Fabricate(:canonical_email_block, email: 'user@example.com') }
it 'removes the block' do
expect { cli.invoke(:remove, arguments) }.to output(
a_string_including('Unblocked user@example.com')
).to_stdout
expect { subject }
.to output_results('Unblocked user@example.com')
expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty
end
@ -47,9 +50,8 @@ describe Mastodon::CLI::CanonicalEmailBlocks do
context 'when a block is not present' do
it 'announces the absence of the block' do
expect { cli.invoke(:remove, arguments) }.to output(
a_string_including('user@example.com is not blocked')
).to_stdout
expect { subject }
.to output_results('user@example.com is not blocked')
end
end
end

@ -4,22 +4,75 @@ require 'rails_helper'
require 'mastodon/cli/domains'
describe Mastodon::CLI::Domains do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#purge' do
let(:action) { :purge }
context 'with accounts from the domain' do
let(:options) { {} }
let(:domain) { 'host.example' }
let!(:account) { Fabricate(:account, domain: domain) }
let(:arguments) { [domain] }
it 'removes the account' do
expect { cli.invoke(:purge, [domain], options) }.to output(
a_string_including('Removed 1 accounts')
).to_stdout
expect { subject }
.to output_results('Removed 1 accounts')
expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe '#crawl' do
let(:action) { :crawl }
context 'with accounts from the domain' do
let(:domain) { 'host.example' }
before do
Fabricate(:account, domain: domain)
stub_request(:get, 'https://host.example/api/v1/instance').to_return(status: 200, body: {}.to_json)
stub_request(:get, 'https://host.example/api/v1/instance/peers').to_return(status: 200, body: {}.to_json)
stub_request(:get, 'https://host.example/api/v1/instance/activity').to_return(status: 200, body: {}.to_json)
stub_const('Mastodon::CLI::Domains::CRAWL_SLEEP_TIME', 0)
end
context 'with --format of summary' do
let(:options) { { format: 'summary' } }
it 'crawls the domains and summarizes results' do
expect { subject }
.to output_results('Visited 1 domains, 0 failed')
end
end
context 'with --format of domains' do
let(:options) { { format: 'domains' } }
it 'crawls the domains and summarizes results' do
expect { subject }
.to output_results(domain)
end
end
context 'with --format of json' do
let(:options) { { format: 'json' } }
it 'crawls the domains and summarizes results' do
expect { subject }
.to output_results(json_summary)
end
def json_summary
Oj.dump('host.example': { activity: {} })
end
end
end
end
end

@ -4,96 +4,99 @@ require 'rails_helper'
require 'mastodon/cli/email_domain_blocks'
describe Mastodon::CLI::EmailDomainBlocks do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#list' do
let(:action) { :list }
context 'with email domain block records' do
let!(:parent_block) { Fabricate(:email_domain_block) }
let!(:child_block) { Fabricate(:email_domain_block, parent: parent_block) }
let(:options) { {} }
it 'lists the blocks' do
expect { cli.invoke(:list, [], options) }.to output(
a_string_including(parent_block.domain)
.and(a_string_including(child_block.domain))
).to_stdout
expect { subject }
.to output_results(
parent_block.domain,
child_block.domain
)
end
end
end
describe '#add' do
context 'without any options' do
let(:options) { {} }
let(:action) { :add }
context 'without any options' do
it 'warns about usage and exits' do
expect { cli.invoke(:add, [], options) }.to output(
a_string_including('No domain(s) given')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('No domain(s) given')
.and raise_error(SystemExit)
end
end
context 'when blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' }
let(:arguments) { [domain] }
before { Fabricate(:email_domain_block, domain: domain) }
it 'does not add a new block' do
expect { cli.invoke(:add, [domain], options) }.to output(
a_string_including('is already blocked')
).to_stdout
expect { subject }
.to output_results('is already blocked')
.and(not_change(EmailDomainBlock, :count))
end
end
context 'when no blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' }
let(:arguments) { [domain] }
it 'adds a new block' do
expect { cli.invoke(:add, [domain], options) }.to output(
a_string_including('Added 1')
).to_stdout
expect { subject }
.to output_results('Added 1')
.and(change(EmailDomainBlock, :count).by(1))
end
end
end
describe '#remove' do
context 'without any options' do
let(:options) { {} }
let(:action) { :remove }
context 'without any options' do
it 'warns about usage and exits' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('No domain(s) given')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('No domain(s) given')
.and raise_error(SystemExit)
end
end
context 'when blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' }
let(:arguments) { [domain] }
before { Fabricate(:email_domain_block, domain: domain) }
it 'removes the block' do
expect { cli.invoke(:remove, [domain], options) }.to output(
a_string_including('Removed 1')
).to_stdout
expect { subject }
.to output_results('Removed 1')
.and(change(EmailDomainBlock, :count).by(-1))
end
end
context 'when no blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' }
let(:arguments) { [domain] }
it 'does not remove a block' do
expect { cli.invoke(:remove, [domain], options) }.to output(
a_string_including('is not yet blocked')
).to_stdout
expect { subject }
.to output_results('is not yet blocked')
.and(not_change(EmailDomainBlock, :count))
end
end

@ -4,10 +4,10 @@ require 'rails_helper'
require 'mastodon/cli/emoji'
describe Mastodon::CLI::Emoji do
subject { cli.invoke(action, args, options) }
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:args) { [] }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
@ -29,7 +29,7 @@ describe Mastodon::CLI::Emoji do
context 'with existing custom emoji' do
let(:import_path) { Rails.root.join('spec', 'fixtures', 'files', 'elite-assets.tar.gz') }
let(:action) { :import }
let(:args) { [import_path] }
let(:arguments) { [import_path] }
it 'reports about imported emoji' do
expect { subject }
@ -51,7 +51,7 @@ describe Mastodon::CLI::Emoji do
after { FileUtils.rm_rf(export_path.dirname) }
let(:export_path) { Rails.root.join('tmp', 'cli-tests', 'export.tar.gz') }
let(:args) { [export_path.dirname.to_s] }
let(:arguments) { [export_path.dirname.to_s] }
let(:action) { :export }
it 'reports about exported emoji' do
@ -61,8 +61,4 @@ describe Mastodon::CLI::Emoji do
end
end
end
def output_results(string)
output(a_string_including(string)).to_stdout
end
end

@ -4,20 +4,25 @@ require 'rails_helper'
require 'mastodon/cli/feeds'
describe Mastodon::CLI::Feeds do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#build' do
let(:action) { :build }
before { Fabricate(:account) }
context 'with --all option' do
let(:options) { { all: true } }
it 'regenerates feeds for all accounts' do
expect { cli.invoke(:build, [], options) }.to output(
a_string_including('Regenerated feeds')
).to_stdout
expect { subject }
.to output_results('Regenerated feeds')
end
end
@ -27,9 +32,8 @@ describe Mastodon::CLI::Feeds do
let(:arguments) { ['alice'] }
it 'regenerates feeds for the account' do
expect { cli.invoke(:build, arguments) }.to output(
a_string_including('OK')
).to_stdout
expect { subject }
.to output_results('OK')
end
end
@ -37,22 +41,23 @@ describe Mastodon::CLI::Feeds do
let(:arguments) { ['invalid-username'] }
it 'displays an error and exits' do
expect { cli.invoke(:build, arguments) }.to output(
a_string_including('No such account')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('No such account')
.and raise_error(SystemExit)
end
end
end
describe '#clear' do
let(:action) { :clear }
before do
allow(redis).to receive(:del).with(key_namespace)
end
it 'clears the redis `feed:*` namespace' do
expect { cli.invoke(:clear) }.to output(
a_string_including('OK')
).to_stdout
expect { subject }
.to output_results('OK')
expect(redis).to have_received(:del).with(key_namespace).once
end

@ -4,11 +4,16 @@ require 'rails_helper'
require 'mastodon/cli/ip_blocks'
describe Mastodon::CLI::IpBlocks do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#add' do
let(:action) { :add }
let(:ip_list) do
[
'192.0.2.1',
@ -25,29 +30,28 @@ describe Mastodon::CLI::IpBlocks do
]
end
let(:options) { { severity: 'no_access' } }
let(:arguments) { ip_list }
shared_examples 'ip address blocking' do
it 'blocks all specified IP addresses' do
cli.invoke(:add, ip_list, options)
blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip)
expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) }
expect(blocked_ip_addresses).to match_array(expected_ip_addresses)
def blocked_ip_addresses
IpBlock.where(ip: ip_list).pluck(:ip)
end
it 'sets the severity for all blocked IP addresses' do
cli.invoke(:add, ip_list, options)
blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
def expected_ip_addresses
ip_list.map { |ip| IPAddr.new(ip) }
end
expect(blocked_ips_severity).to be(true)
def blocked_ips_severity
IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
end
it 'displays a success message with a summary' do
expect { cli.invoke(:add, ip_list, options) }.to output(
a_string_including("Added #{ip_list.size}, skipped 0, failed 0")
).to_stdout
it 'blocks and sets severity for ip address and displays summary' do
expect { subject }
.to output_results("Added #{ip_list.size}, skipped 0, failed 0")
expect(blocked_ip_addresses)
.to match_array(expected_ip_addresses)
expect(blocked_ips_severity)
.to be(true)
end
end
@ -57,27 +61,23 @@ describe Mastodon::CLI::IpBlocks do
context 'when a specified IP address is already blocked' do
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) }
let(:arguments) { ip_list }
it 'skips the already blocked IP address' do
allow(IpBlock).to receive(:new).and_call_original
before { allow(IpBlock).to receive(:new).and_call_original }
cli.invoke(:add, ip_list, options)
it 'skips already block ip and displays the correct summary' do
expect { subject }
.to output_results("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last)
end
it 'displays the correct summary' do
expect { cli.invoke(:add, ip_list, options) }.to output(
a_string_including("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
).to_stdout
end
context 'with --force option' do
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') }
let(:options) { { severity: 'sign_up_requires_approval', force: true } }
it 'overwrites the existing IP block record' do
expect { cli.invoke(:add, ip_list, options) }
expect { subject }
.to change { blocked_ip.reload.severity }
.from('no_access')
.to('sign_up_requires_approval')
@ -89,11 +89,11 @@ describe Mastodon::CLI::IpBlocks do
context 'when a specified IP address is invalid' do
let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] }
let(:arguments) { ip_list }
it 'displays the correct summary' do
expect { cli.invoke(:add, ip_list, options) }.to output(
a_string_including("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1")
).to_stdout
expect { subject }
.to output_results("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1")
end
end
@ -124,6 +124,7 @@ describe Mastodon::CLI::IpBlocks do
context 'when a specified IP address fails to be blocked' do
let(:ip_address) { '127.0.0.1' }
let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) }
let(:arguments) { [ip_address] }
before do
allow(IpBlock).to receive(:new).and_return(ip_block)
@ -132,24 +133,25 @@ describe Mastodon::CLI::IpBlocks do
end
it 'displays an error message' do
expect { cli.invoke(:add, [ip_address], options) }
.to output(
a_string_including("#{ip_address} could not be saved")
).to_stdout
expect { subject }
.to output_results("#{ip_address} could not be saved")
end
end
context 'when no IP address is provided' do
let(:arguments) { [] }
it 'exits with an error message' do
expect { cli.add }.to output(
a_string_including('No IP(s) given')
).to_stdout
expect { subject }
.to output_results('No IP(s) given')
.and raise_error(SystemExit)
end
end
end
describe '#remove' do
let(:action) { :remove }
context 'when removing exact matches' do
let(:ip_list) do
[
@ -166,22 +168,17 @@ describe Mastodon::CLI::IpBlocks do
'::/128',
]
end
let(:arguments) { ip_list }
before do
ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) }
end
it 'removes exact IP blocks' do
cli.invoke(:remove, ip_list)
it 'removes exact ip blocks and displays success message with a summary' do
expect { subject }
.to output_results("Removed #{ip_list.size}, skipped 0")
expect(IpBlock.where(ip: ip_list)).to_not exist
end
it 'displays success message with a summary' do
expect { cli.invoke(:remove, ip_list) }.to output(
a_string_including("Removed #{ip_list.size}, skipped 0")
).to_stdout
end
end
context 'with --force option' do
@ -191,62 +188,60 @@ describe Mastodon::CLI::IpBlocks do
let(:arguments) { ['192.168.0.5', '10.0.1.50'] }
let(:options) { { force: true } }
it 'removes blocks for IP ranges that cover given IP(s)' do
cli.invoke(:remove, arguments, options)
it 'removes blocks for IP ranges that cover given IP(s) and keeps other ranges' do
subject
expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist
expect(covered_ranges).to_not exist
expect(other_ranges).to exist
end
it 'does not remove other IP ranges' do
cli.invoke(:remove, arguments, options)
def covered_ranges
IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])
end
expect(IpBlock.where(id: third_ip_range_block.id)).to exist
def other_ranges
IpBlock.where(id: third_ip_range_block.id)
end
end
context 'when a specified IP address is not blocked' do
let(:unblocked_ip) { '192.0.2.1' }
let(:arguments) { [unblocked_ip] }
it 'skips the IP address' do
expect { cli.invoke(:remove, [unblocked_ip]) }.to output(
a_string_including("#{unblocked_ip} is not yet blocked")
).to_stdout
end
it 'displays the summary correctly' do
expect { cli.invoke(:remove, [unblocked_ip]) }.to output(
a_string_including('Removed 0, skipped 1')
).to_stdout
it 'skips the IP address and displays summary' do
expect { subject }
.to output_results(
"#{unblocked_ip} is not yet blocked",
'Removed 0, skipped 1'
)
end
end
context 'when a specified IP address is invalid' do
let(:invalid_ip) { '320.15.175.0' }
let(:arguments) { [invalid_ip] }
it 'skips the invalid IP address' do
expect { cli.invoke(:remove, [invalid_ip]) }.to output(
a_string_including("#{invalid_ip} is invalid")
).to_stdout
end
it 'displays the summary correctly' do
expect { cli.invoke(:remove, [invalid_ip]) }.to output(
a_string_including('Removed 0, skipped 1')
).to_stdout
it 'skips the invalid IP address and displays summary' do
expect { subject }
.to output_results(
"#{invalid_ip} is invalid",
'Removed 0, skipped 1'
)
end
end
context 'when no IP address is provided' do
it 'exits with an error message' do
expect { cli.remove }.to output(
a_string_including('No IP(s) given')
).to_stdout
expect { subject }
.to output_results('No IP(s) given')
.and raise_error(SystemExit)
end
end
end
describe '#export' do
let(:action) { :export }
let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) }
@ -255,15 +250,13 @@ describe Mastodon::CLI::IpBlocks do
let(:options) { { format: 'plain' } }
it 'exports blocked IPs with "no_access" severity in plain format' do
expect { cli.invoke(:export, nil, options) }.to output(
a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
).to_stdout
expect { subject }
.to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
end
it 'does not export bloked IPs with different severities' do
expect { cli.invoke(:export, nil, options) }.to_not output(
a_string_including("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
).to_stdout
it 'does not export blocked IPs with different severities' do
expect { subject }
.to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
end
end
@ -271,23 +264,20 @@ describe Mastodon::CLI::IpBlocks do
let(:options) { { format: 'nginx' } }
it 'exports blocked IPs with "no_access" severity in plain format' do
expect { cli.invoke(:export, nil, options) }.to output(
a_string_including("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
).to_stdout
expect { subject }
.to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
end
it 'does not export bloked IPs with different severities' do
expect { cli.invoke(:export, nil, options) }.to_not output(
a_string_including("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
).to_stdout
it 'does not export blocked IPs with different severities' do
expect { subject }
.to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
end
end
context 'when --format option is not provided' do
it 'exports blocked IPs in plain format by default' do
expect { cli.export }.to output(
a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
).to_stdout
expect { subject }
.to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
end
end
end

@ -4,13 +4,20 @@ require 'rails_helper'
require 'mastodon/cli/main'
describe Mastodon::CLI::Main do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe 'version' do
describe '#version' do
let(:action) { :version }
it 'returns the Mastodon version' do
expect { described_class.new.invoke(:version) }.to output(
a_string_including(Mastodon::Version.to_s)
).to_stdout
expect { subject }
.to output_results(Mastodon::Version.to_s)
end
end
end

@ -4,20 +4,26 @@ require 'rails_helper'
require 'mastodon/cli/maintenance'
describe Mastodon::CLI::Maintenance do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#fix_duplicates' do
let(:action) { :fix_duplicates }
context 'when the database version is too old' do
before do
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2000_01_01_000000) # Earlier than minimum
end
it 'Exits with error message' do
expect { cli.invoke :fix_duplicates }.to output(
a_string_including('is too old')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('is too old')
.and raise_error(SystemExit)
end
end
@ -28,9 +34,9 @@ describe Mastodon::CLI::Maintenance do
end
it 'Exits with error message' do
expect { cli.invoke :fix_duplicates }.to output(
a_string_including('more recent')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('more recent')
.and raise_error(SystemExit)
end
end
@ -41,9 +47,9 @@ describe Mastodon::CLI::Maintenance do
end
it 'Exits with error message' do
expect { cli.invoke :fix_duplicates }.to output(
a_string_including('Sidekiq is running')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('Sidekiq is running')
.and raise_error(SystemExit)
end
end
end

@ -4,18 +4,24 @@ require 'rails_helper'
require 'mastodon/cli/media'
describe Mastodon::CLI::Media do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#remove' do
let(:action) { :remove }
context 'with --prune-profiles and --remove-headers' do
let(:options) { { prune_profiles: true, remove_headers: true } }
it 'warns about usage and exits' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('--prune-profiles and --remove-headers should not be specified simultaneously')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('--prune-profiles and --remove-headers should not be specified simultaneously')
.and raise_error(SystemExit)
end
end
@ -23,9 +29,9 @@ describe Mastodon::CLI::Media do
let(:options) { { include_follows: true } }
it 'warns about usage and exits' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('--include-follows can only be used with --prune-profiles or --remove-headers')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('--include-follows can only be used with --prune-profiles or --remove-headers')
.and raise_error(SystemExit)
end
end
@ -38,9 +44,8 @@ describe Mastodon::CLI::Media do
let(:options) { { prune_profiles: true } }
it 'removes account avatars' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('Visited 1')
).to_stdout
expect { subject }
.to output_results('Visited 1')
expect(account.reload.avatar).to be_blank
end
@ -50,9 +55,8 @@ describe Mastodon::CLI::Media do
let(:options) { { remove_headers: true } }
it 'removes account header' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('Visited 1')
).to_stdout
expect { subject }
.to output_results('Visited 1')
expect(account.reload.header).to be_blank
end
@ -64,9 +68,8 @@ describe Mastodon::CLI::Media do
context 'without options' do
it 'removes account avatars' do
expect { cli.invoke(:remove) }.to output(
a_string_including('Removed 1')
).to_stdout
expect { subject }
.to output_results('Removed 1')
expect(media_attachment.reload.file).to be_blank
expect(media_attachment.reload.thumbnail).to be_blank
@ -76,25 +79,50 @@ describe Mastodon::CLI::Media do
end
describe '#usage' do
context 'without options' do
let(:options) { {} }
let(:action) { :usage }
context 'without options' do
it 'reports about storage size' do
expect { cli.invoke(:usage, [], options) }.to output(
a_string_including('0 Bytes')
).to_stdout
expect { subject }
.to output_results('0 Bytes')
end
end
end
describe '#lookup' do
let(:action) { :lookup }
let(:arguments) { [url] }
context 'with valid url not connected to a record' do
let(:url) { 'https://example.host/assets/1' }
it 'warns about url and exits' do
expect { subject }
.to output_results('Not a media URL')
.and raise_error(SystemExit)
end
end
context 'with a valid media url' do
let(:status) { Fabricate(:status) }
let(:media_attachment) { Fabricate(:media_attachment, status: status) }
let(:url) { media_attachment.file.url(:original) }
it 'displays the url of a connected status' do
expect { subject }
.to output_results(status.id.to_s)
end
end
end
describe '#refresh' do
context 'without any options' do
let(:options) { {} }
let(:action) { :refresh }
context 'without any options' do
it 'warns about usage and exits' do
expect { cli.invoke(:refresh, [], options) }.to output(
a_string_including('Specify the source')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('Specify the source')
.and raise_error(SystemExit)
end
end
@ -108,9 +136,8 @@ describe Mastodon::CLI::Media do
let(:status) { Fabricate(:status) }
it 'redownloads the attachment file' do
expect { cli.invoke(:refresh, [], options) }.to output(
a_string_including('Downloaded 1 media')
).to_stdout
expect { subject }
.to output_results('Downloaded 1 media')
end
end
@ -119,9 +146,9 @@ describe Mastodon::CLI::Media do
let(:options) { { account: 'not-real-user@example.host' } }
it 'warns about usage and exits' do
expect { cli.invoke(:refresh, [], options) }.to output(
a_string_including('No such account')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('No such account')
.and raise_error(SystemExit)
end
end
@ -135,9 +162,8 @@ describe Mastodon::CLI::Media do
let(:account) { Fabricate(:account) }
it 'redownloads the attachment file' do
expect { cli.invoke(:refresh, [], options) }.to output(
a_string_including('Downloaded 1 media')
).to_stdout
expect { subject }
.to output_results('Downloaded 1 media')
end
end
end
@ -153,9 +179,8 @@ describe Mastodon::CLI::Media do
let(:account) { Fabricate(:account, domain: domain) }
it 'redownloads the attachment file' do
expect { cli.invoke(:refresh, [], options) }.to output(
a_string_including('Downloaded 1 media')
).to_stdout
expect { subject }
.to output_results('Downloaded 1 media')
end
end
end

@ -4,11 +4,17 @@ require 'rails_helper'
require 'mastodon/cli/preview_cards'
describe Mastodon::CLI::PreviewCards do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#remove' do
let(:action) { :remove }
context 'with relevant preview cards' do
before do
Fabricate(:preview_card, updated_at: 10.years.ago, type: :link)
@ -18,10 +24,11 @@ describe Mastodon::CLI::PreviewCards do
context 'with no arguments' do
it 'deletes thumbnails for local preview cards' do
expect { cli.invoke(:remove) }.to output(
a_string_including('Removed 2 preview cards')
.and(a_string_including('approx. 119 KB'))
).to_stdout
expect { subject }
.to output_results(
'Removed 2 preview cards',
'approx. 119 KB'
)
end
end
@ -29,10 +36,11 @@ describe Mastodon::CLI::PreviewCards do
let(:options) { { link: true } }
it 'deletes thumbnails for local preview cards' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('Removed 1 link-type preview cards')
.and(a_string_including('approx. 59.6 KB'))
).to_stdout
expect { subject }
.to output_results(
'Removed 1 link-type preview cards',
'approx. 59.6 KB'
)
end
end
@ -40,10 +48,11 @@ describe Mastodon::CLI::PreviewCards do
let(:options) { { days: 365 } }
it 'deletes thumbnails for local preview cards' do
expect { cli.invoke(:remove, [], options) }.to output(
a_string_including('Removed 1 preview cards')
.and(a_string_including('approx. 59.6 KB'))
).to_stdout
expect { subject }
.to output_results(
'Removed 1 preview cards',
'approx. 59.6 KB'
)
end
end
end

@ -4,5 +4,79 @@ require 'rails_helper'
require 'mastodon/cli/search'
describe Mastodon::CLI::Search do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#deploy' do
let(:action) { :deploy }
context 'with concurrency out of range' do
let(:options) { { concurrency: -100 } }
it 'Exits with error message' do
expect { subject }
.to output_results('this concurrency setting')
.and raise_error(SystemExit)
end
end
context 'with batch size out of range' do
let(:options) { { batch_size: -100_000 } }
it 'Exits with error message' do
expect { subject }
.to output_results('this batch_size setting')
.and raise_error(SystemExit)
end
end
context 'without options' do
before { stub_search_indexes }
let(:indexed_count) { 1 }
let(:deleted_count) { 2 }
it 'reports about storage size' do
expect { subject }
.to output_results(
"Indexed #{described_class::INDICES.size * indexed_count} records",
"de-indexed #{described_class::INDICES.size * deleted_count}"
)
end
end
def stub_search_indexes
described_class::INDICES.each do |index|
allow(index)
.to receive_messages(
specification: instance_double(Chewy::Index::Specification, changed?: true, lock!: nil),
purge: nil
)
importer_double = importer_double_for(index)
allow(importer_double).to receive(:on_progress).and_yield([indexed_count, deleted_count])
allow("Importer::#{index}Importer".constantize)
.to receive(:new)
.and_return(importer_double)
end
end
def importer_double_for(index)
instance_double(
"Importer::#{index}Importer".constantize,
clean_up!: nil,
estimate!: 100,
import!: nil,
on_failure: nil,
# on_progress: nil,
optimize_for_import!: nil,
optimize_for_search!: nil
)
end
end
end

@ -7,59 +7,53 @@ describe Mastodon::CLI::Settings do
it_behaves_like 'CLI Command'
describe 'subcommand "registrations"' do
subject { cli.invoke(action, arguments, options) }
let(:cli) { Mastodon::CLI::Registrations.new }
let(:arguments) { [] }
let(:options) { {} }
before do
Setting.registrations_mode = nil
end
describe '#open' do
it 'changes "registrations_mode" to "open"' do
expect { cli.open }.to change(Setting, :registrations_mode).from(nil).to('open')
end
let(:action) { :open }
it 'displays success message' do
expect { cli.open }.to output(
a_string_including('OK')
).to_stdout
it 'changes "registrations_mode" to "open" and displays success' do
expect { subject }
.to change(Setting, :registrations_mode).from(nil).to('open')
.and output_results('OK')
end
end
describe '#approved' do
it 'changes "registrations_mode" to "approved"' do
expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
end
let(:action) { :approved }
it 'displays success message' do
expect { cli.approved }.to output(
a_string_including('OK')
).to_stdout
it 'changes "registrations_mode" to "approved" and displays success' do
expect { subject }
.to change(Setting, :registrations_mode).from(nil).to('approved')
.and output_results('OK')
end
context 'with --require-reason' do
before do
cli.options = { require_reason: true }
end
it 'changes "registrations_mode" to "approved"' do
expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
end
let(:options) { { require_reason: true } }
it 'sets "require_invite_text" to "true"' do
expect { cli.approved }.to change(Setting, :require_invite_text).from(false).to(true)
it 'changes registrations_mode and require_invite_text' do
expect { subject }
.to change(Setting, :registrations_mode).from(nil).to('approved')
.and change(Setting, :require_invite_text).from(false).to(true)
end
end
end
describe '#close' do
it 'changes "registrations_mode" to "none"' do
expect { cli.close }.to change(Setting, :registrations_mode).from(nil).to('none')
end
let(:action) { :close }
it 'displays success message' do
expect { cli.close }.to output(
a_string_including('OK')
).to_stdout
it 'changes "registrations_mode" to "none" and displays success' do
expect { subject }
.to change(Setting, :registrations_mode).from(nil).to('none')
.and output_results('OK')
end
end
end

@ -4,26 +4,31 @@ require 'rails_helper'
require 'mastodon/cli/statuses'
describe Mastodon::CLI::Statuses do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#remove', use_transactional_tests: false do
let(:action) { :remove }
context 'with small batch size' do
let(:options) { { batch_size: 0 } }
it 'exits with error message' do
expect { cli.invoke :remove, [], options }.to output(
a_string_including('Cannot run')
).to_stdout.and raise_error(SystemExit)
expect { subject }
.to output_results('Cannot run')
.and raise_error(SystemExit)
end
end
context 'with default batch size' do
it 'removes unreferenced statuses' do
expect { cli.invoke :remove }.to output(
a_string_including('Done after')
).to_stdout
expect { subject }
.to output_results('Done after')
end
end
end

@ -4,23 +4,26 @@ require 'rails_helper'
require 'mastodon/cli/upgrade'
describe Mastodon::CLI::Upgrade do
subject { cli.invoke(action, arguments, options) }
let(:cli) { described_class.new }
let(:arguments) { [] }
let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#storage_schema' do
context 'with records that dont need upgrading' do
let(:options) { {} }
let(:action) { :storage_schema }
context 'with records that dont need upgrading' do
before do
Fabricate(:account)
Fabricate(:media_attachment)
end
it 'does not upgrade storage for the attachments' do
expect { cli.invoke(:storage_schema, [], options) }.to output(
a_string_including('Upgraded storage schema of 0 records')
).to_stdout
expect { subject }
.to output_results('Upgraded storage schema of 0 records')
end
end
end

@ -88,6 +88,7 @@ RSpec.configure do |config|
config.include Chewy::Rspec::Helpers
config.include Redisable
config.include SignedRequestHelpers, type: :request
config.include CommandLineHelpers, type: :cli
config.around(:each, use_transactional_tests: false) do |example|
self.use_transactional_tests = false

@ -2,20 +2,16 @@
require 'rails_helper'
describe Api::V1::Accounts::FamiliarFollowersController do
render_views
describe 'Accounts Familiar Followers API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:follows') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:follows' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
describe 'GET /api/v1/accounts/familiar_followers' do
it 'returns http success' do
get :index, params: { account_id: account.id, limit: 2 }
get '/api/v1/accounts/familiar_followers', params: { account_id: account.id, limit: 2 }, headers: headers
expect(response).to have_http_status(200)
end
@ -26,7 +22,7 @@ describe Api::V1::Accounts::FamiliarFollowersController do
it 'removes duplicate account IDs from params' do
account_ids = [account_a, account_b, account_b, account_a, account_a].map { |a| a.id.to_s }
get :index, params: { id: account_ids }
get '/api/v1/accounts/familiar_followers', params: { id: account_ids }, headers: headers
expect(body_as_json.pluck(:id)).to contain_exactly(account_a.id.to_s, account_b.id.to_s)
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Identity Proofs API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/accounts/identity_proofs' do
it 'returns http success' do
get "/api/v1/accounts/#{account.id}/identity_proofs", params: { limit: 2 }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Lists API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:lists' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
let(:list) { Fabricate(:list, account: user.account) }
before do
user.account.follow!(account)
list.accounts << account
end
describe 'GET /api/v1/accounts/lists' do
it 'returns http success' do
get "/api/v1/accounts/#{account.id}/lists", headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Lookup API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/accounts/lookup' do
it 'returns http success' do
get '/api/v1/accounts/lookup', params: { account_id: account.id, acct: account.acct }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -2,21 +2,17 @@
require 'rails_helper'
describe Api::V1::Accounts::NotesController do
render_views
describe 'Accounts Notes API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:accounts') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
let(:comment) { 'foo' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'POST #create' do
describe 'POST /api/v1/accounts/:account_id/note' do
subject do
post :create, params: { account_id: account.id, comment: comment }
post "/api/v1/accounts/#{account.id}/note", params: { comment: comment }, headers: headers
end
context 'when account note has reasonable length', :aggregate_failures do

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Pins API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:kevin) { Fabricate(:user) }
before do
kevin.account.followers << user.account
end
describe 'POST /api/v1/accounts/:account_id/pin' do
subject { post "/api/v1/accounts/#{kevin.account.id}/pin", headers: headers }
it 'creates account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(1)
expect(response).to have_http_status(200)
end
end
describe 'POST /api/v1/accounts/:account_id/unpin' do
subject { post "/api/v1/accounts/#{kevin.account.id}/unpin", headers: headers }
before do
Fabricate(:account_pin, account: user.account, target_account: kevin.account)
end
it 'destroys account_pin', :aggregate_failures do
expect do
subject
end.to change { AccountPin.where(account: user.account, target_account: kevin.account).count }.by(-1)
expect(response).to have_http_status(200)
end
end
end

@ -31,8 +31,8 @@ describe 'GET /api/v1/accounts/relationships' do
.to have_http_status(200)
expect(body_as_json)
.to be_an(Enumerable)
.and have_attributes(
first: include(
.and contain_exactly(
include(
following: true,
followed_by: false
)
@ -53,9 +53,11 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json)
.to be_an(Enumerable)
.and have_attributes(
size: 2,
first: include(simon_item),
second: include(lewis_item)
size: 2
)
.and contain_exactly(
include(simon_item),
include(lewis_item)
)
end
end
@ -71,10 +73,12 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json)
.to be_an(Enumerable)
.and have_attributes(
size: 3,
first: include(simon_item),
second: include(lewis_item),
third: include(bob_item)
size: 3
)
.and contain_exactly(
include(simon_item),
include(lewis_item),
include(bob_item)
)
end
end
@ -88,9 +92,11 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json)
.to be_an(Enumerable)
.and have_attributes(
size: 2,
first: include(simon_item),
second: include(lewis_item)
size: 2
)
.and contain_exactly(
include(simon_item),
include(lewis_item)
)
end
end
@ -116,7 +122,6 @@ describe 'GET /api/v1/accounts/relationships' do
muting: false,
requested: false,
domain_blocking: false,
}
end
@ -129,7 +134,6 @@ describe 'GET /api/v1/accounts/relationships' do
muting: false,
requested: false,
domain_blocking: false,
}
end
end
@ -149,8 +153,10 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json)
.to be_an(Enumerable)
.and have_attributes(
size: 1,
first: include(
size: 1
)
.and contain_exactly(
include(
following: true,
showing_reblogs: true
)
@ -168,8 +174,8 @@ describe 'GET /api/v1/accounts/relationships' do
expect(body_as_json)
.to be_an(Enumerable)
.and have_attributes(
first: include(
.and contain_exactly(
include(
following: false,
showing_reblogs: false
)

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Accounts Search API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/accounts/search' do
it 'returns http success' do
get '/api/v1/accounts/search', params: { q: 'query' }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Featured Tags Suggestions API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
describe 'GET /api/v1/featured_tags/suggestions' do
it 'returns http success' do
get '/api/v1/featured_tags/suggestions', params: { account_id: account.id, limit: 2 }, headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -2,25 +2,21 @@
require 'rails_helper'
RSpec.describe Api::V2::SearchController do
render_views
describe 'Search API' do
context 'with token' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:search' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET #index' do
describe 'GET /api/v2/search' do
let!(:bob) { Fabricate(:account, username: 'bob_test') }
let!(:ana) { Fabricate(:account, username: 'ana_test') }
let!(:tom) { Fabricate(:account, username: 'tom_test') }
let(:params) { { q: 'test' } }
it 'returns http success' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200)
end
@ -29,7 +25,7 @@ RSpec.describe Api::V2::SearchController do
let(:params) { { q: 'test', type: 'accounts' } }
it 'returns all matching accounts' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(bob.id.to_s, ana.id.to_s, tom.id.to_s)
end
@ -38,7 +34,7 @@ RSpec.describe Api::V2::SearchController do
let(:params) { { q: 'test1', resolve: '1' } }
it 'returns http unauthorized' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200)
end
@ -48,7 +44,7 @@ RSpec.describe Api::V2::SearchController do
let(:params) { { q: 'test1', offset: 1 } }
it 'returns http unauthorized' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200)
end
@ -62,7 +58,7 @@ RSpec.describe Api::V2::SearchController do
end
it 'returns only the followed accounts' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(ana.id.to_s)
end
@ -73,7 +69,7 @@ RSpec.describe Api::V2::SearchController do
before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) }
it 'returns http unprocessable_entity' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(422)
end
@ -83,7 +79,7 @@ RSpec.describe Api::V2::SearchController do
before { allow(Search).to receive(:new).and_raise(ActiveRecord::RecordNotFound) }
it 'returns http not_found' do
get :index, params: params
get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(404)
end
@ -92,11 +88,11 @@ RSpec.describe Api::V2::SearchController do
end
context 'without token' do
describe 'GET #index' do
describe 'GET /api/v2/search' do
let(:search_params) { nil }
before do
get :index, params: search_params
get '/api/v2/search', params: search_params
end
context 'without a `q` param' do

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Suggestions API' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v2/suggestions' do
it 'returns http success' do
get '/api/v2/suggestions', headers: headers
expect(response).to have_http_status(200)
end
end
end

@ -6,11 +6,12 @@ RSpec.describe FanOutOnWriteService, type: :service do
subject { described_class.new }
let(:last_active_at) { Time.now.utc }
let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') }
let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob @eve #hoge') }
let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account }
let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account }
let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account }
let!(:eve) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'eve' }).account }
before do
bob.follow!(alice)
@ -109,5 +110,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
expect(redis).to_not have_received(:publish).with('timeline:public', anything)
end
context 'when handling status updates', :sidekiq_fake do
before do
subject.call(status)
status.snapshot!(at_time: status.created_at, rate_limit: false)
status.update!(text: 'Hello @bob @eve #hoge (edited)')
status.snapshot!(account_id: status.account_id)
redis.set("subscribed:timeline:#{eve.id}:notifications", '1')
Sidekiq::Worker.clear_all
end
it 'pushes the update to mentioned users through the notifications streaming channel' do
subject.call(status, update: true)
expect(PushUpdateWorker).to have_enqueued_sidekiq_job(anything, status.id, "timeline:#{eve.id}:notifications", { 'update' => true })
end
end
end
end

@ -0,0 +1,9 @@
# frozen_string_literal: true
module CommandLineHelpers
def output_results(*args)
output(
include(*args)
).to_stdout
end
end

@ -52,7 +52,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9, @babel/compat-data@npm:^7.23.3, @babel/compat-data@npm:^7.23.5":
"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.3, @babel/compat-data@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/compat-data@npm:7.23.5"
checksum: 081278ed46131a890ad566a59c61600a5f9557bd8ee5e535890c8548192532ea92590742fd74bd9db83d74c669ef8a04a7e1c85cdea27f960233e3b83c3a957c
@ -60,37 +60,37 @@ __metadata:
linkType: hard
"@babel/core@npm:^7.10.4, @babel/core@npm:^7.11.1, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.1":
version: 7.23.5
resolution: "@babel/core@npm:7.23.5"
version: 7.23.6
resolution: "@babel/core@npm:7.23.6"
dependencies:
"@ampproject/remapping": "npm:^2.2.0"
"@babel/code-frame": "npm:^7.23.5"
"@babel/generator": "npm:^7.23.5"
"@babel/helper-compilation-targets": "npm:^7.22.15"
"@babel/generator": "npm:^7.23.6"
"@babel/helper-compilation-targets": "npm:^7.23.6"
"@babel/helper-module-transforms": "npm:^7.23.3"
"@babel/helpers": "npm:^7.23.5"
"@babel/parser": "npm:^7.23.5"
"@babel/helpers": "npm:^7.23.6"
"@babel/parser": "npm:^7.23.6"
"@babel/template": "npm:^7.22.15"
"@babel/traverse": "npm:^7.23.5"
"@babel/types": "npm:^7.23.5"
"@babel/traverse": "npm:^7.23.6"
"@babel/types": "npm:^7.23.6"
convert-source-map: "npm:^2.0.0"
debug: "npm:^4.1.0"
gensync: "npm:^1.0.0-beta.2"
json5: "npm:^2.2.3"
semver: "npm:^6.3.1"
checksum: 311a512a870ee330a3f9a7ea89e5df790b2b5af0b1bd98b10b4edc0de2ac440f0df4d69ea2c0ee38a4b89041b9a495802741d93603be7d4fd834ec8bb6970bd2
checksum: a02bae7d916029b70706dc301535e1b31e5d216f55d4ee6f64a15825c6b69ee2c14c52a213d1497ec414e925ed4e9d897d41fb0d75df9fea28ed2c0008790e31
languageName: node
linkType: hard
"@babel/generator@npm:^7.23.5, @babel/generator@npm:^7.7.2":
version: 7.23.5
resolution: "@babel/generator@npm:7.23.5"
"@babel/generator@npm:^7.23.6, @babel/generator@npm:^7.7.2":
version: 7.23.6
resolution: "@babel/generator@npm:7.23.6"
dependencies:
"@babel/types": "npm:^7.23.5"
"@babel/types": "npm:^7.23.6"
"@jridgewell/gen-mapping": "npm:^0.3.2"
"@jridgewell/trace-mapping": "npm:^0.3.17"
jsesc: "npm:^2.5.1"
checksum: 14c6e874f796c4368e919bed6003bb0adc3ce837760b08f9e646d20aeb5ae7d309723ce6e4f06bcb4a2b5753145446c8e4425851380f695e40e71e1760f49e7b
checksum: 53540e905cd10db05d9aee0a5304e36927f455ce66f95d1253bb8a179f286b88fa7062ea0db354c566fe27f8bb96567566084ffd259f8feaae1de5eccc8afbda
languageName: node
linkType: hard
@ -122,16 +122,16 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-compilation-targets@npm:^7.22.15, @babel/helper-compilation-targets@npm:^7.22.6":
version: 7.22.15
resolution: "@babel/helper-compilation-targets@npm:7.22.15"
"@babel/helper-compilation-targets@npm:^7.22.15, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.23.6":
version: 7.23.6
resolution: "@babel/helper-compilation-targets@npm:7.23.6"
dependencies:
"@babel/compat-data": "npm:^7.22.9"
"@babel/helper-validator-option": "npm:^7.22.15"
browserslist: "npm:^4.21.9"
"@babel/compat-data": "npm:^7.23.5"
"@babel/helper-validator-option": "npm:^7.23.5"
browserslist: "npm:^4.22.2"
lru-cache: "npm:^5.1.1"
semver: "npm:^6.3.1"
checksum: 45b9286861296e890f674a3abb199efea14a962a27d9b8adeb44970a9fd5c54e73a9e342e8414d2851cf4f98d5994537352fbce7b05ade32e9849bbd327f9ff1
checksum: ba38506d11185f48b79abf439462ece271d3eead1673dd8814519c8c903c708523428806f05f2ec5efd0c56e4e278698fac967e5a4b5ee842c32415da54bc6fa
languageName: node
linkType: hard
@ -342,14 +342,14 @@ __metadata:
languageName: node
linkType: hard
"@babel/helpers@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/helpers@npm:7.23.5"
"@babel/helpers@npm:^7.23.6":
version: 7.23.6
resolution: "@babel/helpers@npm:7.23.6"
dependencies:
"@babel/template": "npm:^7.22.15"
"@babel/traverse": "npm:^7.23.5"
"@babel/types": "npm:^7.23.5"
checksum: a37e2728eb4378a4888e5d614e28de7dd79b55ac8acbecd0e5c761273e2a02a8f33b34b1932d9069db55417ace2937cbf8ec37c42f1030ce6d228857d7ccaa4f
"@babel/traverse": "npm:^7.23.6"
"@babel/types": "npm:^7.23.6"
checksum: df1cf6607676ad36f52f652ec03536f2732d70aef5e76dba5c964e34d49f3c2d3dcf9fb3740db359f53071d74b64606a833d5ba156f79f437f71bfe06e2e7e19
languageName: node
linkType: hard
@ -364,12 +364,12 @@ __metadata:
languageName: node
linkType: hard
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/parser@npm:7.23.5"
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.6":
version: 7.23.6
resolution: "@babel/parser@npm:7.23.6"
bin:
parser: ./bin/babel-parser.js
checksum: 3356aa90d7bafb4e2c7310e7c2c3d443c4be4db74913f088d3d577a1eb914ea4188e05fd50a47ce907a27b755c4400c4e3cbeee73dbeb37761f6ca85954f5a20
checksum: 6f76cd5ccae1fa9bcab3525b0865c6222e9c1d22f87abc69f28c5c7b2c8816a13361f5bd06bddbd5faf903f7320a8feba02545c981468acec45d12a03db7755e
languageName: node
linkType: hard
@ -836,14 +836,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/plugin-transform-for-of@npm:^7.23.3":
version: 7.23.3
resolution: "@babel/plugin-transform-for-of@npm:7.23.3"
"@babel/plugin-transform-for-of@npm:^7.23.6":
version: 7.23.6
resolution: "@babel/plugin-transform-for-of@npm:7.23.6"
dependencies:
"@babel/helper-plugin-utils": "npm:^7.22.5"
"@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5"
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 8a36202cfee312ba80e509c7c2131e6773524e572b4dc64a8ee95bd912634fdeb5ea91c6c7747ee30e03562d0f0d333f88ed7dbb929b36b60b8d74189189e12f
checksum: 46681b6ab10f3ca2d961f50d4096b62ab5d551e1adad84e64be1ee23e72eb2f26a1e30e617e853c74f1349fffe4af68d33921a128543b6f24b6d46c09a3e2aec
languageName: node
linkType: hard
@ -1200,8 +1201,8 @@ __metadata:
linkType: hard
"@babel/plugin-transform-runtime@npm:^7.22.4":
version: 7.23.4
resolution: "@babel/plugin-transform-runtime@npm:7.23.4"
version: 7.23.6
resolution: "@babel/plugin-transform-runtime@npm:7.23.6"
dependencies:
"@babel/helper-module-imports": "npm:^7.22.15"
"@babel/helper-plugin-utils": "npm:^7.22.5"
@ -1211,7 +1212,7 @@ __metadata:
semver: "npm:^6.3.1"
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 6ac29012550cdd10b65ec43fef0c7f43904ec458c43d597f627d8f52807413e57ea94e3986dbace576d734e67c2d09be5e43e77c72567d18f8c4ac5e19844625
checksum: 94a7ee92f073df53fd8bebf9ed391a95553716077da1c6c3a57f10f042358c938495d55e6b09b4b50544c01f03560c4770c17698e1c24817a15d3668e8231249
languageName: node
linkType: hard
@ -1333,11 +1334,11 @@ __metadata:
linkType: hard
"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.22.4":
version: 7.23.5
resolution: "@babel/preset-env@npm:7.23.5"
version: 7.23.6
resolution: "@babel/preset-env@npm:7.23.6"
dependencies:
"@babel/compat-data": "npm:^7.23.5"
"@babel/helper-compilation-targets": "npm:^7.22.15"
"@babel/helper-compilation-targets": "npm:^7.23.6"
"@babel/helper-plugin-utils": "npm:^7.22.5"
"@babel/helper-validator-option": "npm:^7.23.5"
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.23.3"
@ -1377,7 +1378,7 @@ __metadata:
"@babel/plugin-transform-dynamic-import": "npm:^7.23.4"
"@babel/plugin-transform-exponentiation-operator": "npm:^7.23.3"
"@babel/plugin-transform-export-namespace-from": "npm:^7.23.4"
"@babel/plugin-transform-for-of": "npm:^7.23.3"
"@babel/plugin-transform-for-of": "npm:^7.23.6"
"@babel/plugin-transform-function-name": "npm:^7.23.3"
"@babel/plugin-transform-json-strings": "npm:^7.23.4"
"@babel/plugin-transform-literals": "npm:^7.23.3"
@ -1418,7 +1419,7 @@ __metadata:
semver: "npm:^6.3.1"
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 2a0e1274dec045186e131c6433659b75492583290e8d41633c616f6bff829cb2e4b2f9a57f556283a54db3bd6aa697911e56a36f607911a29b731c445a5b5a06
checksum: 5b24d179af52f082d04b9b98cc4777e37bf31a97cef5a91d8917e996dbd75f2f743c88c40f80744cb8529355bb674619d150c0260c32d834aa4067e21d0c8962
languageName: node
linkType: hard
@ -1483,11 +1484,11 @@ __metadata:
linkType: hard
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
version: 7.23.5
resolution: "@babel/runtime@npm:7.23.5"
version: 7.23.6
resolution: "@babel/runtime@npm:7.23.6"
dependencies:
regenerator-runtime: "npm:^0.14.0"
checksum: ca679cc91bb7e424bc2db87bb58cc3b06ade916b9adb21fbbdc43e54cdaacb3eea201ceba2a0464b11d2eb65b9fe6a6ffcf4d7521fa52994f19be96f1af14788
checksum: d886954e985ef8e421222f7a2848884d96a752e0020d3078b920dd104e672fdf23bcc6f51a44313a048796319f1ac9d09c2c88ec8cbb4e1f09174bcd3335b9ff
languageName: node
linkType: hard
@ -1502,32 +1503,32 @@ __metadata:
languageName: node
linkType: hard
"@babel/traverse@npm:7, @babel/traverse@npm:^7.23.5":
version: 7.23.5
resolution: "@babel/traverse@npm:7.23.5"
"@babel/traverse@npm:7, @babel/traverse@npm:^7.23.6":
version: 7.23.6
resolution: "@babel/traverse@npm:7.23.6"
dependencies:
"@babel/code-frame": "npm:^7.23.5"
"@babel/generator": "npm:^7.23.5"
"@babel/generator": "npm:^7.23.6"
"@babel/helper-environment-visitor": "npm:^7.22.20"
"@babel/helper-function-name": "npm:^7.23.0"
"@babel/helper-hoist-variables": "npm:^7.22.5"
"@babel/helper-split-export-declaration": "npm:^7.22.6"
"@babel/parser": "npm:^7.23.5"
"@babel/types": "npm:^7.23.5"
debug: "npm:^4.1.0"
"@babel/parser": "npm:^7.23.6"
"@babel/types": "npm:^7.23.6"
debug: "npm:^4.3.1"
globals: "npm:^11.1.0"
checksum: c5ea793080ca6719b0a1612198fd25e361cee1f3c14142d7a518d2a1eeb5c1d21f7eec1b26c20ea6e1ddd8ed12ab50b960ff95ffd25be353b6b46e1b54d6f825
checksum: 5b4ebb94a00a7e1daf111e4b0b45a7998d5b7598637a14e75e855e88cc1b702789e09a958726b5d599a003be1e9032dbdfde4b88ea6061332228738950d5582d
languageName: node
linkType: hard
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
version: 7.23.5
resolution: "@babel/types@npm:7.23.5"
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3":
version: 7.23.6
resolution: "@babel/types@npm:7.23.6"
dependencies:
"@babel/helper-string-parser": "npm:^7.23.4"
"@babel/helper-validator-identifier": "npm:^7.22.20"
to-fast-properties: "npm:^2.0.0"
checksum: 7dd5e2f59828ed046ad0b06b039df2524a8b728d204affb4fc08da2502b9dd3140b1356b5166515d229dc811539a8b70dcd4bc507e06d62a89f4091a38d0b0fb
checksum: 42cefce8a68bd09bb5828b4764aa5586c53c60128ac2ac012e23858e1c179347a4aac9c66fc577994fbf57595227611c5ec8270bf0cfc94ff033bbfac0550b70
languageName: node
linkType: hard
@ -1726,9 +1727,9 @@ __metadata:
languageName: node
linkType: hard
"@eslint/eslintrc@npm:^2.1.3":
version: 2.1.3
resolution: "@eslint/eslintrc@npm:2.1.3"
"@eslint/eslintrc@npm:^2.1.4":
version: 2.1.4
resolution: "@eslint/eslintrc@npm:2.1.4"
dependencies:
ajv: "npm:^6.12.4"
debug: "npm:^4.3.2"
@ -1739,14 +1740,14 @@ __metadata:
js-yaml: "npm:^4.1.0"
minimatch: "npm:^3.1.2"
strip-json-comments: "npm:^3.1.1"
checksum: f4103f4346126292eb15581c5a1d12bef03410fd3719dedbdb92e1f7031d46a5a2d60de8566790445d5d4b70b75ba050876799a11f5fff8265a91ee3fa77dab0
checksum: 32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573
languageName: node
linkType: hard
"@eslint/js@npm:8.54.0":
version: 8.54.0
resolution: "@eslint/js@npm:8.54.0"
checksum: d61fb4a0be6af2d8cb290121c329697664a75d6255a29926d5454fb02aeb02b87112f67fdf218d10abac42f90c570ac366126751baefc5405d0e017ed0c946c5
"@eslint/js@npm:8.55.0":
version: 8.55.0
resolution: "@eslint/js@npm:8.55.0"
checksum: 88ab9fc57a651becd2b32ec40a3958db27fae133b1ae77bebd733aa5bbd00a92f325bb02f20ad680d31c731fa49b22f060a4777dd52eb3e27da013d940bd978d
languageName: node
linkType: hard
@ -2435,7 +2436,7 @@ __metadata:
stacktrace-js: "npm:^2.0.2"
stringz: "npm:^2.1.0"
stylelint: "npm:^15.10.1"
stylelint-config-standard-scss: "npm:^11.0.0"
stylelint-config-standard-scss: "npm:^12.0.0"
substring-trie: "npm:^1.0.2"
terser-webpack-plugin: "npm:^4.2.3"
tesseract.js: "npm:^2.1.5"
@ -3030,11 +3031,11 @@ __metadata:
linkType: hard
"@types/emoji-mart@npm:^3.0.9":
version: 3.0.13
resolution: "@types/emoji-mart@npm:3.0.13"
version: 3.0.14
resolution: "@types/emoji-mart@npm:3.0.14"
dependencies:
"@types/react": "npm:*"
checksum: 840f920c3242e1d274f0102e67cb2d00434e1fd370e0bcc8983b43b6b62322b01e3ddcd5fb078c60883e613530a7c70b8c40060624897543cd4da9441ca81486
checksum: 23ded65fce9b3355fbe903d3971cb67cc827a5d587464bb7e3f349615527ef4a9197b3bb59fa84c4391d1b901e7f200f686a7fc83f649ae2a51a0fb948cbadfb
languageName: node
linkType: hard
@ -3178,12 +3179,12 @@ __metadata:
linkType: hard
"@types/jest@npm:^29.5.2":
version: 29.5.10
resolution: "@types/jest@npm:29.5.10"
version: 29.5.11
resolution: "@types/jest@npm:29.5.11"
dependencies:
expect: "npm:^29.0.0"
pretty-format: "npm:^29.0.0"
checksum: b46171d59d12a5f69bbe710f65eaf59a8073337c6b4a67dff8158575caec53f1c61f8a7d645b34d6ac3c4ea398acd30f0c5d1c4a131c0c918798019264a3397d
checksum: 524a3394845214581278bf4d75055927261fbeac7e1a89cd621bd0636da37d265fe0a85eac58b5778758faad1cbd7c7c361dfc190c78ebde03a91cce33463261
languageName: node
linkType: hard
@ -3377,11 +3378,11 @@ __metadata:
linkType: hard
"@types/react-helmet@npm:^6.1.6":
version: 6.1.9
resolution: "@types/react-helmet@npm:6.1.9"
version: 6.1.11
resolution: "@types/react-helmet@npm:6.1.11"
dependencies:
"@types/react": "npm:*"
checksum: d1823582903d6e70f1f447c7bec9e844b6f85f5de84cbcde5c8bbeecc064db1394c786ed9b9ded30544afe5c91e57c7e8105171df1643998f64c0aeab9f7f2aa
checksum: f7b3bb2151d992a108ae46fed876fb9c8119108397d9a01d150c5642782997542c8b3c52e742b56e8689b7dbfa62ca9cfc76aa7e05dec4e60c652f7ef53fa783
languageName: node
linkType: hard
@ -3498,13 +3499,13 @@ __metadata:
linkType: hard
"@types/react@npm:*, @types/react@npm:16 || 17 || 18, @types/react@npm:>=16.9.11, @types/react@npm:^18.2.7":
version: 18.2.41
resolution: "@types/react@npm:18.2.41"
version: 18.2.43
resolution: "@types/react@npm:18.2.43"
dependencies:
"@types/prop-types": "npm:*"
"@types/scheduler": "npm:*"
csstype: "npm:^3.0.2"
checksum: 5cc72491ce8be95e7bbedd8bf039ca971772ecd22d989feb045af7e73247c7e6cff25a2f1c2200be461fb2f6b5aacef739e1ba9fd83c744209dfd3ce8aa75afe
checksum: 10477a50fbd3c0cc5b8a2ade679f442717f68fb27c8460b2aa1d3256cd18c48f742bbe5b9ee37a8c4c5f832ffa37b3a23c09fd96dd880a8e3182d8929c05e803
languageName: node
linkType: hard
@ -3685,14 +3686,14 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^6.0.0":
version: 6.11.0
resolution: "@typescript-eslint/eslint-plugin@npm:6.11.0"
version: 6.13.2
resolution: "@typescript-eslint/eslint-plugin@npm:6.13.2"
dependencies:
"@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:6.11.0"
"@typescript-eslint/type-utils": "npm:6.11.0"
"@typescript-eslint/utils": "npm:6.11.0"
"@typescript-eslint/visitor-keys": "npm:6.11.0"
"@typescript-eslint/scope-manager": "npm:6.13.2"
"@typescript-eslint/type-utils": "npm:6.13.2"
"@typescript-eslint/utils": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.13.2"
debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4"
@ -3705,44 +3706,44 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 6645aa09b9d51c5e3ea781eaf74da75b94f83f3e2d7b3dd988d5ce7eb82dd87e3509471cf2ee8c6b2428d907df5f1b02f29dbd04f54c2653f9566c8c4ce98009
checksum: 531a4406d872738d165c6a66cb26e976523c94053b022a8210dc9fd10e91b79b705bc0fcc77145e9744e4108b53bdba55e02a10dc17757b22be92aff57849384
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^6.0.0":
version: 6.11.0
resolution: "@typescript-eslint/parser@npm:6.11.0"
version: 6.13.2
resolution: "@typescript-eslint/parser@npm:6.13.2"
dependencies:
"@typescript-eslint/scope-manager": "npm:6.11.0"
"@typescript-eslint/types": "npm:6.11.0"
"@typescript-eslint/typescript-estree": "npm:6.11.0"
"@typescript-eslint/visitor-keys": "npm:6.11.0"
"@typescript-eslint/scope-manager": "npm:6.13.2"
"@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/typescript-estree": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.13.2"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: e7caeb20069102e21f468fc0dbe7ff6fb6b1efa9e72f4c9f39d4a865ed0633f39130b593ef9ae8f394ca1d70563e15410faf30a482a97809951eaac6ed3a67da
checksum: 2c62b8cd8a37eb2ea59cd00e559f51a9f57af746e2040e872af3c58ddd3f4071ad7b7009789bdeb0e0d4ee0343bfe96ee77288020f3ae22d08e1674203f5e156
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:6.11.0":
version: 6.11.0
resolution: "@typescript-eslint/scope-manager@npm:6.11.0"
"@typescript-eslint/scope-manager@npm:6.13.2":
version: 6.13.2
resolution: "@typescript-eslint/scope-manager@npm:6.13.2"
dependencies:
"@typescript-eslint/types": "npm:6.11.0"
"@typescript-eslint/visitor-keys": "npm:6.11.0"
checksum: d8999e2d1a4cbde8a79df5e3ec416f0e3db9532d39f2f4bb5a0ebdf954ae75c183d3277579ba05268fe2c88e88ef87f0fa12f02bb8d95d9e67d92e411241f3a3
"@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.13.2"
checksum: 9b159e5bb10dfb5953e71488200b4126378fc7e987ce7d90946aea9ec40cd66c7ada92399657c5d9794189b764ca6f4eb38a8dcb9e4c5aa50ab6000a39636b9c
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:6.11.0":
version: 6.11.0
resolution: "@typescript-eslint/type-utils@npm:6.11.0"
"@typescript-eslint/type-utils@npm:6.13.2":
version: 6.13.2
resolution: "@typescript-eslint/type-utils@npm:6.13.2"
dependencies:
"@typescript-eslint/typescript-estree": "npm:6.11.0"
"@typescript-eslint/utils": "npm:6.11.0"
"@typescript-eslint/typescript-estree": "npm:6.13.2"
"@typescript-eslint/utils": "npm:6.13.2"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1"
peerDependencies:
@ -3750,23 +3751,23 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: ff68f2e052b8d688f1dc1a0050746704c8e0ab6263b47f1f52da73a7d251678e4950af23a95e1cd8e3fcea2457e6e5294ddbe01d29dafa2fdfb5b11ed9452a3f
checksum: 1ca97c78abdf479aea0c54e869fda2ae2f69de1974cc063062ce7b5b16c7fdf497ea15c50a29dd5941ea1b6b77e8f1213a5c272a747e334ac69ede083f327468
languageName: node
linkType: hard
"@typescript-eslint/types@npm:6.11.0":
version: 6.11.0
resolution: "@typescript-eslint/types@npm:6.11.0"
checksum: 23182813db39a5e9b9bcc1e85306c953f7b8b22d3885e41fcac0bd725c170fbcb70f4ce55633678cc5921dcf062fa0e55635eb39480c118a4411a00354820223
"@typescript-eslint/types@npm:6.13.2":
version: 6.13.2
resolution: "@typescript-eslint/types@npm:6.13.2"
checksum: 029918ca5b1442bb4bc435773504ce32191e2c3e2fde8d4176bb6513f03e3dfa2aa9724b2d22b1640656d666b97f7a7ebfeaf67b881d5e07250828fa83e3ebe8
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:6.11.0":
version: 6.11.0
resolution: "@typescript-eslint/typescript-estree@npm:6.11.0"
"@typescript-eslint/typescript-estree@npm:6.13.2":
version: 6.13.2
resolution: "@typescript-eslint/typescript-estree@npm:6.13.2"
dependencies:
"@typescript-eslint/types": "npm:6.11.0"
"@typescript-eslint/visitor-keys": "npm:6.11.0"
"@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/visitor-keys": "npm:6.13.2"
debug: "npm:^4.3.4"
globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3"
@ -3775,34 +3776,34 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 3e183e554e1bc74f065da3015f7137eb40c262f989c547701b1e3f4f20134e574e56b749288cd00d77b9d1ddb705546613c2457661ffc63b6060ffa97ba3aac8
checksum: 1c4c59dce0c51fdfee34d9f418e64fe28e3ec1a97661efc8a3d2780bdff36aff38de9090d356a968f394fa6d4e9c058936ce9cd260d4c44a52761ecd74915bce
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:6.11.0, @typescript-eslint/utils@npm:^6.5.0":
version: 6.11.0
resolution: "@typescript-eslint/utils@npm:6.11.0"
"@typescript-eslint/utils@npm:6.13.2, @typescript-eslint/utils@npm:^6.5.0":
version: 6.13.2
resolution: "@typescript-eslint/utils@npm:6.13.2"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:6.11.0"
"@typescript-eslint/types": "npm:6.11.0"
"@typescript-eslint/typescript-estree": "npm:6.11.0"
"@typescript-eslint/scope-manager": "npm:6.13.2"
"@typescript-eslint/types": "npm:6.13.2"
"@typescript-eslint/typescript-estree": "npm:6.13.2"
semver: "npm:^7.5.4"
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
checksum: c91eb4578607959acc2b43ddc791571682e45601a19b25d5d120786ed4af607656f83c5c1fa71972e549ddfb5542acf2f7d443ae93b32ee28192c22c106b8883
checksum: 84969be91e7949868eaaa289288c9d71927f0e427b572501b0991d8d62b40a4234f7287c35b35d276ccbb53e9ea5457b8250fcf4941e60e6b9ba4065fbfba416
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:6.11.0":
version: 6.11.0
resolution: "@typescript-eslint/visitor-keys@npm:6.11.0"
"@typescript-eslint/visitor-keys@npm:6.13.2":
version: 6.13.2
resolution: "@typescript-eslint/visitor-keys@npm:6.13.2"
dependencies:
"@typescript-eslint/types": "npm:6.11.0"
"@typescript-eslint/types": "npm:6.13.2"
eslint-visitor-keys: "npm:^3.4.1"
checksum: 5f48329422b7f286196661d39e93e9defd7c5cf80e6c84c8d03459853f5d9f86a5e91c5e80ea572dcdb907ebbe503bbcc77aeb8b468c294b2aa7b3ccfc81cb88
checksum: c173bc1fcc42c3075a5ee094e7f3bf0279d98315c25ff49e20d02d79022b1d0402accfa113b070afb4d52a6f6d180594b67baa8b6a784eabdf82b54dd1ff454c
languageName: node
linkType: hard
@ -5180,17 +5181,17 @@ __metadata:
languageName: node
linkType: hard
"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.21.9, browserslist@npm:^4.22.1":
version: 4.22.1
resolution: "browserslist@npm:4.22.1"
"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.1, browserslist@npm:^4.22.2":
version: 4.22.2
resolution: "browserslist@npm:4.22.2"
dependencies:
caniuse-lite: "npm:^1.0.30001541"
electron-to-chromium: "npm:^1.4.535"
node-releases: "npm:^2.0.13"
caniuse-lite: "npm:^1.0.30001565"
electron-to-chromium: "npm:^1.4.601"
node-releases: "npm:^2.0.14"
update-browserslist-db: "npm:^1.0.13"
bin:
browserslist: cli.js
checksum: 6810f2d63f171d0b7b8d38cf091708e00cb31525501810a507839607839320d66e657293b0aa3d7f051ecbc025cb07390a90c037682c1d05d12604991e41050b
checksum: 2a331aab90503130043ca41dd5d281fa1e89d5e076d07a2d75e76bf4d693bd56e73d5abcd8c4f39119da6328d450578c216cf1cd5c99b82d8a90a2ae6271b465
languageName: node
linkType: hard
@ -5418,10 +5419,10 @@ __metadata:
languageName: node
linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001541":
version: 1.0.30001561
resolution: "caniuse-lite@npm:1.0.30001561"
checksum: 6e84c84026fee53edbdbb5aded7a04a036aae4c2e367cf6bdc90c6783a591e2fdcfcdebcc4e774aca61092e542a61200c8c16b06659396492426033c4dbcc618
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001565":
version: 1.0.30001568
resolution: "caniuse-lite@npm:1.0.30001568"
checksum: 13f01e5a2481134bd61cf565ce9fecbd8e107902927a0dcf534230a92191a81f1715792170f5f39719c767c3a96aa6df9917a8d5601f15bbd5e4041a8cfecc99
languageName: node
linkType: hard
@ -6423,7 +6424,7 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.4":
"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4":
version: 4.3.4
resolution: "debug@npm:4.3.4"
dependencies:
@ -6965,10 +6966,10 @@ __metadata:
languageName: node
linkType: hard
"electron-to-chromium@npm:^1.4.535":
version: 1.4.576
resolution: "electron-to-chromium@npm:1.4.576"
checksum: b0b9e7ba803bf93ffac9cb830ed2b0e0eb07f20066127065f9ab9e08e4e6a5812040e03d76f6ee9bc59e03fb938fd414e83d4883b29111303e9e88633cf2dce4
"electron-to-chromium@npm:^1.4.601":
version: 1.4.609
resolution: "electron-to-chromium@npm:1.4.609"
checksum: 9675a79388acbaff5953a4c61589af7da93e0d1f9d6a3b284c7630f10126eb0998557b07448514214d5a3d19025310039b55f405ab701b1253130fc94907f743
languageName: node
linkType: hard
@ -7323,13 +7324,13 @@ __metadata:
linkType: hard
"eslint-config-prettier@npm:^9.0.0":
version: 9.0.0
resolution: "eslint-config-prettier@npm:9.0.0"
version: 9.1.0
resolution: "eslint-config-prettier@npm:9.1.0"
peerDependencies:
eslint: ">=7.0.0"
bin:
eslint-config-prettier: bin/cli.js
checksum: bc1f661915845c631824178942e5d02f858fe6d0ea796f0050d63e0f681927b92696e81139dd04714c08c3e7de580fd079c66162e40070155ba79eaee78ab5d0
checksum: 6d332694b36bc9ac6fdb18d3ca2f6ac42afa2ad61f0493e89226950a7091e38981b66bac2b47ba39d15b73fff2cd32c78b850a9cf9eed9ca9a96bfb2f3a2f10d
languageName: node
linkType: hard
@ -7565,13 +7566,13 @@ __metadata:
linkType: hard
"eslint@npm:^8.41.0":
version: 8.54.0
resolution: "eslint@npm:8.54.0"
version: 8.55.0
resolution: "eslint@npm:8.55.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/regexpp": "npm:^4.6.1"
"@eslint/eslintrc": "npm:^2.1.3"
"@eslint/js": "npm:8.54.0"
"@eslint/eslintrc": "npm:^2.1.4"
"@eslint/js": "npm:8.55.0"
"@humanwhocodes/config-array": "npm:^0.11.13"
"@humanwhocodes/module-importer": "npm:^1.0.1"
"@nodelib/fs.walk": "npm:^1.2.8"
@ -7608,7 +7609,7 @@ __metadata:
text-table: "npm:^0.2.0"
bin:
eslint: bin/eslint.js
checksum: 4f205f832bdbd0218cde374b067791f4f76d7abe8de86b2dc849c273899051126d912ebf71531ee49b8eeaa22cad77febdc8f2876698dc2a76e84a8cb976af22
checksum: d28c0b60f19bb7d355cb8393e77b018c8f548dba3f820b799c89bb2e0c436ee26084e700c5e57e1e97e7972ec93065277849141b82e7b0c0d02c2dc1e553a2a1
languageName: node
linkType: hard
@ -11954,10 +11955,10 @@ __metadata:
languageName: node
linkType: hard
"node-releases@npm:^2.0.13":
version: 2.0.13
resolution: "node-releases@npm:2.0.13"
checksum: 2fb44bf70fc949d27f3a48a7fd1a9d1d603ddad4ccd091f26b3fb8b1da976605d919330d7388ccd55ca2ade0dc8b2e12841ba19ef249c8bb29bf82532d401af7
"node-releases@npm:^2.0.14":
version: 2.0.14
resolution: "node-releases@npm:2.0.14"
checksum: 199fc93773ae70ec9969bc6d5ac5b2bbd6eb986ed1907d751f411fef3ede0e4bfdb45ceb43711f8078bea237b6036db8b1bf208f6ff2b70c7d615afd157f3ab9
languageName: node
linkType: hard
@ -13336,11 +13337,11 @@ __metadata:
linkType: hard
"prettier@npm:^3.0.0":
version: 3.1.0
resolution: "prettier@npm:3.1.0"
version: 3.1.1
resolution: "prettier@npm:3.1.1"
bin:
prettier: bin/prettier.cjs
checksum: a45ea70aa97fde162ea4c4aba3dfc7859aa6a732a1db34458d9535dc3c2c16d3bc3fb5689e6cd76aa835562555303b02d9449fd2e15af3b73c8053557e25c5b6
checksum: facc944ba20e194ff4db765e830ffbcb642803381f0d2033ed397e79904fa4ccc877dc25ad68f42d36985c01d051c990ca1b905fb83d2d7d65fe69e4386fa1a3
languageName: node
linkType: hard
@ -15781,62 +15782,62 @@ __metadata:
languageName: node
linkType: hard
"stylelint-config-recommended-scss@npm:^13.1.0":
version: 13.1.0
resolution: "stylelint-config-recommended-scss@npm:13.1.0"
"stylelint-config-recommended-scss@npm:^14.0.0":
version: 14.0.0
resolution: "stylelint-config-recommended-scss@npm:14.0.0"
dependencies:
postcss-scss: "npm:^4.0.9"
stylelint-config-recommended: "npm:^13.0.0"
stylelint-scss: "npm:^5.3.0"
stylelint-config-recommended: "npm:^14.0.0"
stylelint-scss: "npm:^6.0.0"
peerDependencies:
postcss: ^8.3.3
stylelint: ^15.10.0
stylelint: ^16.0.2
peerDependenciesMeta:
postcss:
optional: true
checksum: e07d0172c7936b4f644138e4129df2f187d297f1f96ce5865ab21ccd1c22caf94220f7caf9d6985e93e515de4c0356f6cb9c924d00df2eee5b3bc237f7e5bb48
checksum: 9ddc92e7a5fa131b41cee1ab1f69251934ca35c0e2803dc613329cdead7b8b27d8457048a63db29f61a1442e7cdef14207f88a3abce00ec53fdefe0d604f7de3
languageName: node
linkType: hard
"stylelint-config-recommended@npm:^13.0.0":
version: 13.0.0
resolution: "stylelint-config-recommended@npm:13.0.0"
"stylelint-config-recommended@npm:^14.0.0":
version: 14.0.0
resolution: "stylelint-config-recommended@npm:14.0.0"
peerDependencies:
stylelint: ^15.10.0
checksum: 80420a1ab616e8637b66223f88c597388990d9991cd6a28b8372049b83329d893412f83029bb253a82b52387e497b62e042bc898064a2f22574b0d8921f01dd2
stylelint: ^16.0.0
checksum: 4ad15c36e8c03291aa7bbe4b672ebfb0f46ab698e7580a0da8d29644046d102d7f31dbf00a2a6eab94b565c390c6fb0d5d528737b83ac3acf6dc2ef085a90b11
languageName: node
linkType: hard
"stylelint-config-standard-scss@npm:^11.0.0":
version: 11.1.0
resolution: "stylelint-config-standard-scss@npm:11.1.0"
"stylelint-config-standard-scss@npm:^12.0.0":
version: 12.0.0
resolution: "stylelint-config-standard-scss@npm:12.0.0"
dependencies:
stylelint-config-recommended-scss: "npm:^13.1.0"
stylelint-config-standard: "npm:^34.0.0"
stylelint-config-recommended-scss: "npm:^14.0.0"
stylelint-config-standard: "npm:^35.0.0"
peerDependencies:
postcss: ^8.3.3
stylelint: ^15.10.0
stylelint: ^16.0.2
peerDependenciesMeta:
postcss:
optional: true
checksum: 22d00e75c1eacce9883fd48c3d67b1107b0e39d7d86e9f73deaa332b11c39a9678c947ae2c34cd5159a452ec9a857694ed58b5a851087480d3c9a66dab629415
checksum: 7f3ccfb4175f9c50b69d30ca35a97887008c5ba493dbe7d5bce0b57b1eafd21b268177b82404368e7780600077cba784f98e1046671724be3b29a00c6a7913a4
languageName: node
linkType: hard
"stylelint-config-standard@npm:^34.0.0":
version: 34.0.0
resolution: "stylelint-config-standard@npm:34.0.0"
"stylelint-config-standard@npm:^35.0.0":
version: 35.0.0
resolution: "stylelint-config-standard@npm:35.0.0"
dependencies:
stylelint-config-recommended: "npm:^13.0.0"
stylelint-config-recommended: "npm:^14.0.0"
peerDependencies:
stylelint: ^15.10.0
checksum: 2494468af2359490b6ebb9723d9653f9e31db3a0772b8d9f0e081018b0079ef84ae6f90dcf94c879a3c374f299e334941e3dcff1afb603c2284d3103085b71fb
stylelint: ^16.0.0
checksum: 791fbc26cc3029ce3c2423a643e903545b5e4cd605251b18f0ce790bac6fbaaf380469845c1ff45f4e320126af9f8a9dc1ca85d0df9274277ae60da91e81895b
languageName: node
linkType: hard
"stylelint-scss@npm:^5.3.0":
version: 5.3.1
resolution: "stylelint-scss@npm:5.3.1"
"stylelint-scss@npm:^6.0.0":
version: 6.0.0
resolution: "stylelint-scss@npm:6.0.0"
dependencies:
known-css-properties: "npm:^0.29.0"
postcss-media-query-parser: "npm:^0.2.3"
@ -15844,8 +15845,8 @@ __metadata:
postcss-selector-parser: "npm:^6.0.13"
postcss-value-parser: "npm:^4.2.0"
peerDependencies:
stylelint: ^14.5.1 || ^15.0.0
checksum: 5dfed5f9ac9812cd2ac6ef0272c720dee0326aaaee2998315a23bdcd71b8f04427f29cad634793eea2b45984182e20f03e90d43501e8e4d55bc956f80e2de477
stylelint: ^16.0.2
checksum: f5e971d19ef6879ae5c18cb8fba8033fe7928f241178e6afd80357cc080d2feddfd6f7fe564aaa696008aa10345df5885d9a4471c926b3e266088e015927782e
languageName: node
linkType: hard
@ -16519,22 +16520,22 @@ __metadata:
linkType: hard
"typescript@npm:5, typescript@npm:^5.0.4":
version: 5.3.2
resolution: "typescript@npm:5.3.2"
version: 5.3.3
resolution: "typescript@npm:5.3.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: d7dbe1fbe19039e36a65468ea64b5d338c976550394ba576b7af9c68ed40c0bc5d12ecce390e4b94b287a09a71bd3229f19c2d5680611f35b7c53a3898791159
checksum: e33cef99d82573624fc0f854a2980322714986bc35b9cb4d1ce736ed182aeab78e2cb32b385efa493b2a976ef52c53e20d6c6918312353a91850e2b76f1ea44f
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>":
version: 5.3.2
resolution: "typescript@patch:typescript@npm%3A5.3.2#optional!builtin<compat/typescript>::version=5.3.2&hash=e012d7"
version: 5.3.3
resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin<compat/typescript>::version=5.3.3&hash=e012d7"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 73c8bad74e732d93211c9d77f28b03307e2f5fc6a0afc73f4b783261ab567686a16d6ae958bdaef383a00be1b0b8c8b6741dd6ca3d13af4963fa7e47456d49c7
checksum: 1d0a5f4ce496c42caa9a30e659c467c5686eae15d54b027ee7866744952547f1be1262f2d40de911618c242b510029d51d43ff605dba8fb740ec85ca2d3f9500
languageName: node
linkType: hard
@ -17762,8 +17763,8 @@ __metadata:
linkType: hard
"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.14.2":
version: 8.14.2
resolution: "ws@npm:8.14.2"
version: 8.15.0
resolution: "ws@npm:8.15.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
@ -17772,7 +17773,7 @@ __metadata:
optional: true
utf-8-validate:
optional: true
checksum: 35b4c2da048b8015c797fd14bcb5a5766216ce65c8a5965616a5440ca7b6c3681ee3cbd0ea0c184a59975556e9d58f2002abf8485a14d11d3371770811050a16
checksum: b778a405b2589ffbf549323e2f404f1f72e372a049d332d2f0b1f33057e9fbb14a05aa474cb156e4584b418cd95edf4297c0ca5263d6519e8009064bf8e0b80d
languageName: node
linkType: hard

Loading…
Cancel
Save