Instantly share code, notes, and snippets.
Created
April 16, 2024 15:39
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save ryanfb/84502d3f9fde8d91fa2339124354b785 to your computer and use it in GitHub Desktop.
Diff of current Truth Social code release against previous 2022 code release
This file has been truncated, but you can view the full file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -ru truth-old/opensource/.buildpacks truth-new/opensource/.buildpacks | |
--- truth-old/opensource/.buildpacks 2022-06-08 09:15:38 | |
+++ truth-new/opensource/.buildpacks 2024-04-01 14:59:13 | |
@@ -1,3 +1,3 @@ | |
https://github.com/heroku/heroku-buildpack-apt | |
https://github.com/Scalingo/ffmpeg-buildpack | |
-https://github.com/Scalingo/ruby-buildpack | |
+https://github.com/heroku/heroku-buildpack-ruby | |
Only in truth-new/opensource: .bundle | |
Only in truth-new/opensource: .circleci | |
Only in truth-old/opensource: .env.production.sample | |
Only in truth-new/opensource: .envrc | |
diff -ru truth-old/opensource/.eslintrc.js truth-new/opensource/.eslintrc.js | |
--- truth-old/opensource/.eslintrc.js 2022-06-08 09:15:38 | |
+++ truth-new/opensource/.eslintrc.js 2024-04-01 14:59:13 | |
@@ -1,6 +1,10 @@ | |
module.exports = { | |
root: true, | |
+ extends: [ | |
+ 'plugin:import/typescript', | |
+ ], | |
+ | |
env: { | |
browser: true, | |
node: true, | |
@@ -36,6 +40,7 @@ | |
}, | |
'import/extensions': [ | |
'.js', | |
+ '.ts', | |
], | |
'import/ignore': [ | |
'node_modules', | |
@@ -183,6 +188,7 @@ | |
'always', | |
{ | |
js: 'never', | |
+ ts: 'never', | |
}, | |
], | |
'import/newline-after-import': 'error', | |
Only in truth-new/opensource: .github | |
diff -ru truth-old/opensource/.gitignore truth-new/opensource/.gitignore | |
--- truth-old/opensource/.gitignore 2022-06-08 09:15:38 | |
+++ truth-new/opensource/.gitignore 2024-04-01 14:59:13 | |
@@ -14,23 +14,22 @@ | |
/db/*.sqlite3-journal | |
# Ignore all logfiles and tempfiles. | |
+.byebug_history | |
.eslintcache | |
/log/* | |
!/log/.keep | |
/tmp | |
/coverage | |
/public/system | |
-/public/assets | |
-/public/packs | |
-/public/packs-test | |
.env | |
.env.production | |
.env.development | |
.env.test | |
+.byebug_history | |
/node_modules/ | |
/build/ | |
/scripts/ | |
-# Ignore Capistrano customizations | |
+ | |
/config/deploy/* | |
# Ignore Vagrant files | |
@@ -81,4 +80,4 @@ | |
/opensource/ | |
# Ignore generated test data file | |
-spec/support/examples/lib/csvs/accounts_for_suspension.csv | |
\ No newline at end of file | |
+spec/support/examples/lib/csvs/accounts_for_suspension.csv | |
diff -ru truth-old/opensource/.rubocop.yml truth-new/opensource/.rubocop.yml | |
--- truth-old/opensource/.rubocop.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/.rubocop.yml 2024-04-12 09:09:08 | |
@@ -16,7 +16,10 @@ | |
- 'vendor/**/*' | |
- 'lib/json_ld/*' | |
- 'lib/templates/**/*' | |
+ - 'lib/proto/*' | |
+ SuggestExtensions: false | |
+ | |
Bundler/OrderedGems: | |
Enabled: false | |
@@ -49,6 +52,12 @@ | |
Lint/DuplicateElsifCondition: | |
Enabled: true | |
+Lint/MissingSuper: | |
+ Exclude: | |
+ - 'app/services/chat_service.rb' | |
+ - 'app/services/chat_message_service.rb' | |
+ - 'app/services/geo_service.rb' | |
+ | |
Lint/MixedRegexpCaptureTypes: | |
Enabled: true | |
@@ -72,6 +81,7 @@ | |
Exclude: | |
- 'lib/tasks/**/*' | |
- 'lib/mastodon/*_cli.rb' | |
+ - '**/*_spec.rb' | |
Metrics/BlockNesting: | |
Max: 3 | |
@@ -291,6 +301,9 @@ | |
Style/SymbolArray: | |
Enabled: false | |
+ | |
+Style/TrailingCommaInArguments: | |
+ EnforcedStyleForMultiline: 'comma' | |
Style/TrailingCommaInArrayLiteral: | |
EnforcedStyleForMultiline: 'comma' | |
Only in truth-new/opensource: .vagrant | |
diff -ru truth-old/opensource/Brewfile truth-new/opensource/Brewfile | |
--- truth-old/opensource/Brewfile 2022-06-08 09:15:38 | |
+++ truth-new/opensource/Brewfile 2023-05-05 13:42:02 | |
@@ -9,3 +9,5 @@ | |
brew "postgresql" | |
brew "redis" | |
brew "direnv" | |
+brew "imagemagick" | |
+brew "ffmpeg" | |
diff -ru truth-old/opensource/Brewfile.lock.json truth-new/opensource/Brewfile.lock.json | |
--- truth-old/opensource/Brewfile.lock.json 2022-06-08 09:15:38 | |
+++ truth-new/opensource/Brewfile.lock.json 2023-05-05 13:42:02 | |
@@ -2,87 +2,87 @@ | |
"entries": { | |
"tap": { | |
"homebrew/bundle": { | |
- "revision": "4b703e446be1d848521b0bdf3e85f36bb3aae1f9" | |
+ "revision": "8ec900f210c925f4b4731b0f359b0808ca132285" | |
}, | |
"homebrew/core": { | |
- "revision": "18c5a8c7d1ed4d58a80c1b3d5485c26f290eaa01" | |
+ "revision": "b0fa68871ce4b23a8ea225529e5bc4aea15bac95" | |
} | |
}, | |
"brew": { | |
"protobuf-c": { | |
- "version": "1.4.0_1", | |
+ "version": "1.4.1_1", | |
"bottle": { | |
"rebuild": 0, | |
"root_url": "https://ghcr.io/v2/homebrew/core", | |
"files": { | |
"arm64_monterey": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:4a3986d128583d41b29e369bfddeff1e369267441797b71776b8567b4eac5702", | |
- "sha256": "4a3986d128583d41b29e369bfddeff1e369267441797b71776b8567b4eac5702" | |
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:201a08aabe9bc83897b908019d7dd8aba6dcddf46224eb15bbccdd5f70f6a21b", | |
+ "sha256": "201a08aabe9bc83897b908019d7dd8aba6dcddf46224eb15bbccdd5f70f6a21b" | |
}, | |
"arm64_big_sur": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:8e855e301d3e6f20acb9b79f8e86ed46cba43790d03a2a82b2de7024abb721ec", | |
- "sha256": "8e855e301d3e6f20acb9b79f8e86ed46cba43790d03a2a82b2de7024abb721ec" | |
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:48ea3989f31b6f44c8170479f5115064ed32ccd4ccf6784ea4ad254697d4f53e", | |
+ "sha256": "48ea3989f31b6f44c8170479f5115064ed32ccd4ccf6784ea4ad254697d4f53e" | |
}, | |
"monterey": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:1d380b543cfaed179de2a482212975c9bc7219da96aa939148f9c6a6a30e170c", | |
- "sha256": "1d380b543cfaed179de2a482212975c9bc7219da96aa939148f9c6a6a30e170c" | |
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:eeb51fce7f9a32e9c64ed31ffaa0c9e1fe747b0e047065fcd7e69cc6361b039c", | |
+ "sha256": "eeb51fce7f9a32e9c64ed31ffaa0c9e1fe747b0e047065fcd7e69cc6361b039c" | |
}, | |
"big_sur": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:c89d06a0c0b555379f137f448cd8d25dd0a476d417ab277c572fd07c6faf0275", | |
- "sha256": "c89d06a0c0b555379f137f448cd8d25dd0a476d417ab277c572fd07c6faf0275" | |
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:06b3fc06f5fe8b09353ac6aa106373833d897a960bb607a6caf84ba0043634ac", | |
+ "sha256": "06b3fc06f5fe8b09353ac6aa106373833d897a960bb607a6caf84ba0043634ac" | |
}, | |
"catalina": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:55732600c0f049e6b40bee2751dfacaadd79d62a72f6f843897e25d129cbd47f", | |
- "sha256": "55732600c0f049e6b40bee2751dfacaadd79d62a72f6f843897e25d129cbd47f" | |
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:5c3d841771a3527b3c118abb738b2ab04345de884588cf313d8ed14fe8514288", | |
+ "sha256": "5c3d841771a3527b3c118abb738b2ab04345de884588cf313d8ed14fe8514288" | |
}, | |
"x86_64_linux": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:a52bfbd47abd15484c307ae0e9d11d93bf9c98606dabaa893b75952e9db80a28", | |
- "sha256": "a52bfbd47abd15484c307ae0e9d11d93bf9c98606dabaa893b75952e9db80a28" | |
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:4fd6aa2c3972f3b24248fb0a75638c61dc658cd0c2bc3005b088715e68f6a106", | |
+ "sha256": "4fd6aa2c3972f3b24248fb0a75638c61dc658cd0c2bc3005b088715e68f6a106" | |
} | |
} | |
} | |
}, | |
"gcc": { | |
- "version": "11.2.0_3", | |
+ "version": "11.3.0_2", | |
"bottle": { | |
"rebuild": 1, | |
"root_url": "https://ghcr.io/v2/homebrew/core", | |
"files": { | |
"arm64_monterey": { | |
"cellar": "/opt/homebrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:2d179246426328ee69b94a25b8bd4c25caeff0699b5ecb4b3d258fe4efd3673e", | |
- "sha256": "2d179246426328ee69b94a25b8bd4c25caeff0699b5ecb4b3d258fe4efd3673e" | |
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:330f9db4ca60cf49809b8bb6ed0307b991330ff0184d8989e1e9fcf31c9b557d", | |
+ "sha256": "330f9db4ca60cf49809b8bb6ed0307b991330ff0184d8989e1e9fcf31c9b557d" | |
}, | |
"arm64_big_sur": { | |
"cellar": "/opt/homebrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:9dbb002aa1aab75071fe1a5432fd3ee61378d711aebe0d35d0ca7226a4225451", | |
- "sha256": "9dbb002aa1aab75071fe1a5432fd3ee61378d711aebe0d35d0ca7226a4225451" | |
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:ec9f983bdd7c9d8a9f383c54388d5121a51824710b78b571de892c4d773dfa06", | |
+ "sha256": "ec9f983bdd7c9d8a9f383c54388d5121a51824710b78b571de892c4d773dfa06" | |
}, | |
"monterey": { | |
"cellar": "/usr/local/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:198f5312ecfe6fc6437b55e2fb3bb380e8c597ae6fa255f8f7d0be90306e7601", | |
- "sha256": "198f5312ecfe6fc6437b55e2fb3bb380e8c597ae6fa255f8f7d0be90306e7601" | |
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:5624a8566fb118edbcacd5dba3b2bdd612ea6bc8fa24b3994b62becb0b2429fe", | |
+ "sha256": "5624a8566fb118edbcacd5dba3b2bdd612ea6bc8fa24b3994b62becb0b2429fe" | |
}, | |
"big_sur": { | |
"cellar": "/usr/local/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:d2d4543675948c7adf3f1d4934dc651b864f66d5dad6fb3c8bdcfc6f5eef42e6", | |
- "sha256": "d2d4543675948c7adf3f1d4934dc651b864f66d5dad6fb3c8bdcfc6f5eef42e6" | |
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:b8d94950b02fc77ef7e018dc7b0b69ec00b826a0bd33da32deb4214817a632d3", | |
+ "sha256": "b8d94950b02fc77ef7e018dc7b0b69ec00b826a0bd33da32deb4214817a632d3" | |
}, | |
"catalina": { | |
"cellar": "/usr/local/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:e721b6a3195d2a1e73e4c12d34d0138bc5ebe6a37fb1a8d63ad733316e944c59", | |
- "sha256": "e721b6a3195d2a1e73e4c12d34d0138bc5ebe6a37fb1a8d63ad733316e944c59" | |
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:efd0048f48f5bde84a6b23fbddb29b4d9f05a0c9753799a9d02c65bfddb6c7c6", | |
+ "sha256": "efd0048f48f5bde84a6b23fbddb29b4d9f05a0c9753799a9d02c65bfddb6c7c6" | |
}, | |
"x86_64_linux": { | |
- "cellar": "/home/linuxbrew/.linuxbrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:3717134ab0f56e7eeb167c4f4a993c81329d6c1248dae5ee6e39f59cfdfa0eee", | |
- "sha256": "3717134ab0f56e7eeb167c4f4a993c81329d6c1248dae5ee6e39f59cfdfa0eee" | |
+ "cellar": ":any_skip_relocation", | |
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:e826c10b577ca561cdcef55042c426bc7aabb4a937e5e2aab66c0f21d87c79f5", | |
+ "sha256": "e826c10b577ca561cdcef55042c426bc7aabb4a937e5e2aab66c0f21d87c79f5" | |
} | |
} | |
} | |
@@ -137,182 +137,250 @@ | |
} | |
}, | |
"shared-mime-info": { | |
- "version": "2.1", | |
+ "version": "2.2", | |
"bottle": { | |
"rebuild": 0, | |
"root_url": "https://ghcr.io/v2/homebrew/core", | |
"files": { | |
"arm64_monterey": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:49fd4c8b0f7cb6b3d45be48968613f8f26b0bded7f7c55b9e978c11d94efb513", | |
- "sha256": "49fd4c8b0f7cb6b3d45be48968613f8f26b0bded7f7c55b9e978c11d94efb513" | |
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:6a9e8f01389c00cf8e3d3b7fbd9dc0b95d33744fcdb8bf3dca2c3db87c0d7cd1", | |
+ "sha256": "6a9e8f01389c00cf8e3d3b7fbd9dc0b95d33744fcdb8bf3dca2c3db87c0d7cd1" | |
}, | |
"arm64_big_sur": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:c2c98a7a02e1b23f5c7f7baafe0e4b04f22a7b1a6df73912a7450ea73c162819", | |
- "sha256": "c2c98a7a02e1b23f5c7f7baafe0e4b04f22a7b1a6df73912a7450ea73c162819" | |
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:c00d8c439285648cb14490b6f4bbb2111d16bd1cf7bbd386cc16ff9b1825a04a", | |
+ "sha256": "c00d8c439285648cb14490b6f4bbb2111d16bd1cf7bbd386cc16ff9b1825a04a" | |
}, | |
"monterey": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:eb8c22370434b81375766139e32b3d8f823a1569a8f3bccc3eaf1fe9f39f250a", | |
- "sha256": "eb8c22370434b81375766139e32b3d8f823a1569a8f3bccc3eaf1fe9f39f250a" | |
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:d279f9a9dfe8d9eb3aa22388b0ae41bdd284f44b35ef40b654f8d1c04929c488", | |
+ "sha256": "d279f9a9dfe8d9eb3aa22388b0ae41bdd284f44b35ef40b654f8d1c04929c488" | |
}, | |
"big_sur": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:4857d9f38c0f3cbf23984d60c4ec6280d84b457123d34b9c01e96f3deb8b0bb2", | |
- "sha256": "4857d9f38c0f3cbf23984d60c4ec6280d84b457123d34b9c01e96f3deb8b0bb2" | |
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:3287f34793705e039a140e2614d3aafad8de654e5829515ffd3b77d024de6551", | |
+ "sha256": "3287f34793705e039a140e2614d3aafad8de654e5829515ffd3b77d024de6551" | |
}, | |
"catalina": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:8cb87ae2f3014998ecebab2d8c37ac9ff364f1164417420c4d8778a38ca17d29", | |
- "sha256": "8cb87ae2f3014998ecebab2d8c37ac9ff364f1164417420c4d8778a38ca17d29" | |
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:406f54a852d1f7ea4e0e2d065495d825cd55a6e32132e0e1572c06010bfc89b6", | |
+ "sha256": "406f54a852d1f7ea4e0e2d065495d825cd55a6e32132e0e1572c06010bfc89b6" | |
}, | |
- "mojave": { | |
- "cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:786d1c053d03676c985de3a7c15d764b69626f5d12e7e36e4048055bdc36413c", | |
- "sha256": "786d1c053d03676c985de3a7c15d764b69626f5d12e7e36e4048055bdc36413c" | |
- }, | |
"x86_64_linux": { | |
"cellar": "/home/linuxbrew/.linuxbrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:6099cf602b42eb8f23022b02c292b0bbdce2e22f4ff5b5e8f4d8a3c4575b298f", | |
- "sha256": "6099cf602b42eb8f23022b02c292b0bbdce2e22f4ff5b5e8f4d8a3c4575b298f" | |
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:42873f1d296084b1afe36a085f96b9b4074b1337b290cc0daf9a81859cf6766a", | |
+ "sha256": "42873f1d296084b1afe36a085f96b9b4074b1337b290cc0daf9a81859cf6766a" | |
} | |
} | |
} | |
}, | |
"postgresql": { | |
- "version": "14.1_1", | |
+ "version": "14.4", | |
"bottle": { | |
"rebuild": 0, | |
"root_url": "https://ghcr.io/v2/homebrew/core", | |
"files": { | |
"arm64_monterey": { | |
"cellar": "/opt/homebrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:6e6f3099ad1e64fbdc9dff2152c33a2f01743d2010330bcb34cefe13052fa228", | |
- "sha256": "6e6f3099ad1e64fbdc9dff2152c33a2f01743d2010330bcb34cefe13052fa228" | |
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:148b28ec301378520e83e53869452afdb82cce0b29eae4c7966dce23a110d546", | |
+ "sha256": "148b28ec301378520e83e53869452afdb82cce0b29eae4c7966dce23a110d546" | |
}, | |
"arm64_big_sur": { | |
"cellar": "/opt/homebrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:d75aee6c8beaabf4add33f0f77f150b13523ffc21f6b72fd2a3c1ea0b7095362", | |
- "sha256": "d75aee6c8beaabf4add33f0f77f150b13523ffc21f6b72fd2a3c1ea0b7095362" | |
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:441b6519f16ff4b6d9bdce9c116b804d27d3c0b3537cade961ee28eec2ec89f8", | |
+ "sha256": "441b6519f16ff4b6d9bdce9c116b804d27d3c0b3537cade961ee28eec2ec89f8" | |
}, | |
"monterey": { | |
"cellar": "/usr/local/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:027c8b48406c3d732241426e0f5d2caf9f48cb4d2d38610b8f5d46f0adf7a89f", | |
- "sha256": "027c8b48406c3d732241426e0f5d2caf9f48cb4d2d38610b8f5d46f0adf7a89f" | |
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:1e258c37f55737787151ee3a5276e805e0aa4e30cf5d166bdc2208d0d7f812c2", | |
+ "sha256": "1e258c37f55737787151ee3a5276e805e0aa4e30cf5d166bdc2208d0d7f812c2" | |
}, | |
"big_sur": { | |
"cellar": "/usr/local/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:b207e5d55b0696b3b1dd649b4496d8213c933304b2b85e1913270f0834167b7f", | |
- "sha256": "b207e5d55b0696b3b1dd649b4496d8213c933304b2b85e1913270f0834167b7f" | |
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:04247388a3fcade374189d6777ff6685f4b3450cf14f90bb6859eb5e2eec4b8c", | |
+ "sha256": "04247388a3fcade374189d6777ff6685f4b3450cf14f90bb6859eb5e2eec4b8c" | |
}, | |
"catalina": { | |
"cellar": "/usr/local/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:7708c5cd803ce6bc1481527ecff0ad387d7489e71a3da47140768995bed3e145", | |
- "sha256": "7708c5cd803ce6bc1481527ecff0ad387d7489e71a3da47140768995bed3e145" | |
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:3fa8b21ec3952be003c0803a4d7e58d478c219e1a75a0948b93e2ffebd250e7f", | |
+ "sha256": "3fa8b21ec3952be003c0803a4d7e58d478c219e1a75a0948b93e2ffebd250e7f" | |
}, | |
"x86_64_linux": { | |
"cellar": "/home/linuxbrew/.linuxbrew/Cellar", | |
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:37477f8fd9f0fed2956558ad9f7169123200e6761a65936e8732a3a88d95c3ca", | |
- "sha256": "37477f8fd9f0fed2956558ad9f7169123200e6761a65936e8732a3a88d95c3ca" | |
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:431a89f854eb55b6eeed149515ea1876d0a425bede3c512cff40e49491223062", | |
+ "sha256": "431a89f854eb55b6eeed149515ea1876d0a425bede3c512cff40e49491223062" | |
} | |
} | |
} | |
}, | |
"redis": { | |
- "version": "6.2.6", | |
+ "version": "7.0.3", | |
"bottle": { | |
"rebuild": 0, | |
"root_url": "https://ghcr.io/v2/homebrew/core", | |
"files": { | |
"arm64_monterey": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:a656500c3b5762c7cfe03d587a4fa08c5df4568783d167555962d850e8cab3c3", | |
- "sha256": "a656500c3b5762c7cfe03d587a4fa08c5df4568783d167555962d850e8cab3c3" | |
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:4cc1d45e351961e95e782e66c00661316f22d580826f1703c2c59907c83e1dba", | |
+ "sha256": "4cc1d45e351961e95e782e66c00661316f22d580826f1703c2c59907c83e1dba" | |
}, | |
"arm64_big_sur": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:846aada68ca07b36d58fd620ed5d52ae67a759526c5da27042748363bfdb6271", | |
- "sha256": "846aada68ca07b36d58fd620ed5d52ae67a759526c5da27042748363bfdb6271" | |
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:69d4b633d35eac570aba6c16aacb3e597c16c1f02a5c730c1785ae4dbad3b0a2", | |
+ "sha256": "69d4b633d35eac570aba6c16aacb3e597c16c1f02a5c730c1785ae4dbad3b0a2" | |
}, | |
"monterey": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:ac30519a604ff014e3903893ddca6c563c134002fec58df3613632e42c4d117c", | |
- "sha256": "ac30519a604ff014e3903893ddca6c563c134002fec58df3613632e42c4d117c" | |
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:b0ceaa7592468ee103656390217caa61259a0542c3289d413e442b6237d25fe5", | |
+ "sha256": "b0ceaa7592468ee103656390217caa61259a0542c3289d413e442b6237d25fe5" | |
}, | |
"big_sur": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:246f73498993a2a0c6c4326a298d2fcc3da6d61904ad09a631aa9c63a6800f76", | |
- "sha256": "246f73498993a2a0c6c4326a298d2fcc3da6d61904ad09a631aa9c63a6800f76" | |
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:28bc30760d01dac125aea13c2c3814995728d63025be4df8e92aefe2bdd6fe71", | |
+ "sha256": "28bc30760d01dac125aea13c2c3814995728d63025be4df8e92aefe2bdd6fe71" | |
}, | |
"catalina": { | |
"cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:ff93a763d622cc9130c09fa9ce2ec7236f91562667eaa5c304fcf175c1253746", | |
- "sha256": "ff93a763d622cc9130c09fa9ce2ec7236f91562667eaa5c304fcf175c1253746" | |
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:3387ab3983d8c93d0f57d8796dcc00db3750184c9bdbb1374e468be172de07f4", | |
+ "sha256": "3387ab3983d8c93d0f57d8796dcc00db3750184c9bdbb1374e468be172de07f4" | |
}, | |
- "mojave": { | |
- "cellar": ":any", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:57842762aad1434f8b511f603364b4528f0545f7d768c9387b362011351cda2b", | |
- "sha256": "57842762aad1434f8b511f603364b4528f0545f7d768c9387b362011351cda2b" | |
- }, | |
"x86_64_linux": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:8398fc05ef8eb1ea3d7b26844e3a314a948b3d0d4fb937a00c6c62f0abbe340a", | |
- "sha256": "8398fc05ef8eb1ea3d7b26844e3a314a948b3d0d4fb937a00c6c62f0abbe340a" | |
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:823b2c71e5656342095f1af7bcb4d43eef9014717b3687a2e6248a7516872970", | |
+ "sha256": "823b2c71e5656342095f1af7bcb4d43eef9014717b3687a2e6248a7516872970" | |
} | |
} | |
} | |
}, | |
"direnv": { | |
- "version": "2.30.3", | |
+ "version": "2.32.1", | |
"bottle": { | |
"rebuild": 0, | |
"root_url": "https://ghcr.io/v2/homebrew/core", | |
"files": { | |
"arm64_monterey": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:0a2bf97696f0e57e713db8f39dcff719fa17e0512b6ad14a7657a1c946943a85", | |
- "sha256": "0a2bf97696f0e57e713db8f39dcff719fa17e0512b6ad14a7657a1c946943a85" | |
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:567a8c1ca45ffae17d4901b0aa729be8866b6cb93ee8224da98cacf36f73ed69", | |
+ "sha256": "567a8c1ca45ffae17d4901b0aa729be8866b6cb93ee8224da98cacf36f73ed69" | |
}, | |
"arm64_big_sur": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:41d4f105cdef28417dae6c248a5819709967897071c34ed63fa5432644d944f2", | |
- "sha256": "41d4f105cdef28417dae6c248a5819709967897071c34ed63fa5432644d944f2" | |
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:b21230f43123e6b1a832f87d30f040d9f4684bb19f62f69f651bb24ae1cfaaab", | |
+ "sha256": "b21230f43123e6b1a832f87d30f040d9f4684bb19f62f69f651bb24ae1cfaaab" | |
}, | |
"monterey": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:761499a99dc029d5cafe075105827c4897d5e45dd53cfa7bf86ea51fc4f1afaf", | |
- "sha256": "761499a99dc029d5cafe075105827c4897d5e45dd53cfa7bf86ea51fc4f1afaf" | |
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:68f7b9093d44fdef4210ffeaa8f88e8fa27bef356b4c8b2d4fc7749aab1d2614", | |
+ "sha256": "68f7b9093d44fdef4210ffeaa8f88e8fa27bef356b4c8b2d4fc7749aab1d2614" | |
}, | |
"big_sur": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:73fc3e19b391c97806c44d2f2b38b5ddc28742d656ab6ca013371acc6cabd5bc", | |
- "sha256": "73fc3e19b391c97806c44d2f2b38b5ddc28742d656ab6ca013371acc6cabd5bc" | |
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:f5dc03f040b2638a14e30c9fdeaaed616084539c0360fc916b53c3c8206259c4", | |
+ "sha256": "f5dc03f040b2638a14e30c9fdeaaed616084539c0360fc916b53c3c8206259c4" | |
}, | |
"catalina": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:77c87b8f6ee51b65514b5688babcd83117b75feb2b40b0489bd0649cdeb3f3cb", | |
- "sha256": "77c87b8f6ee51b65514b5688babcd83117b75feb2b40b0489bd0649cdeb3f3cb" | |
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:2edc8e221d28db3da039490d4728d3c5ac7ce38d5274418b4d5f0ca31dfc0b28", | |
+ "sha256": "2edc8e221d28db3da039490d4728d3c5ac7ce38d5274418b4d5f0ca31dfc0b28" | |
}, | |
"x86_64_linux": { | |
"cellar": ":any_skip_relocation", | |
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:b18ff46bf3e0b18eace8b2a0754829555a991d58d6fe3f76681a8a057c48a04c", | |
- "sha256": "b18ff46bf3e0b18eace8b2a0754829555a991d58d6fe3f76681a8a057c48a04c" | |
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:c0356bf7cc43d0a1eb7777e7ed390f47f9dd8fb51cc480c8fb87fcde5fba1b4a", | |
+ "sha256": "c0356bf7cc43d0a1eb7777e7ed390f47f9dd8fb51cc480c8fb87fcde5fba1b4a" | |
} | |
} | |
} | |
+ }, | |
+ "imagemagick": { | |
+ "version": "7.1.0-43", | |
+ "bottle": { | |
+ "rebuild": 0, | |
+ "root_url": "https://ghcr.io/v2/homebrew/core", | |
+ "files": { | |
+ "arm64_monterey": { | |
+ "cellar": "/opt/homebrew/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:0400965c5e3292014220eccc4c86c781322de2656a73521223dad47a5995d9d2", | |
+ "sha256": "0400965c5e3292014220eccc4c86c781322de2656a73521223dad47a5995d9d2" | |
+ }, | |
+ "arm64_big_sur": { | |
+ "cellar": "/opt/homebrew/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:c8b12081678eddd29bdd48e64d9920159f0ddc4c35e9b5dabc4f32b954b4d111", | |
+ "sha256": "c8b12081678eddd29bdd48e64d9920159f0ddc4c35e9b5dabc4f32b954b4d111" | |
+ }, | |
+ "monterey": { | |
+ "cellar": "/usr/local/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:7ba74e818a3c320d0246d9d01f063ae1acb1bc7b9682afdb8477a3f9760cf003", | |
+ "sha256": "7ba74e818a3c320d0246d9d01f063ae1acb1bc7b9682afdb8477a3f9760cf003" | |
+ }, | |
+ "big_sur": { | |
+ "cellar": "/usr/local/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:52a890f084a4dbe6da65b9a93d626b53d970ebccab4b5e7ba72a6f025be07f49", | |
+ "sha256": "52a890f084a4dbe6da65b9a93d626b53d970ebccab4b5e7ba72a6f025be07f49" | |
+ }, | |
+ "catalina": { | |
+ "cellar": "/usr/local/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:f1f5c94945b0da6263fd55f206eebf04abfb24745f395f7574b81e2a03e782c5", | |
+ "sha256": "f1f5c94945b0da6263fd55f206eebf04abfb24745f395f7574b81e2a03e782c5" | |
+ }, | |
+ "x86_64_linux": { | |
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:2b7e46bd4f6282f30ae1bd0bbe700460a057df23ea1697f02fd111cca5188f39", | |
+ "sha256": "2b7e46bd4f6282f30ae1bd0bbe700460a057df23ea1697f02fd111cca5188f39" | |
+ } | |
+ } | |
+ } | |
+ }, | |
+ "ffmpeg": { | |
+ "version": "5.0.1_3", | |
+ "bottle": { | |
+ "rebuild": 0, | |
+ "root_url": "https://ghcr.io/v2/homebrew/core", | |
+ "files": { | |
+ "arm64_monterey": { | |
+ "cellar": "/opt/homebrew/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:6aa7fa8dbc5fb950f1ef81c31e5c3af52d92c616ea9a4b46e58b42c51a0ba7d7", | |
+ "sha256": "6aa7fa8dbc5fb950f1ef81c31e5c3af52d92c616ea9a4b46e58b42c51a0ba7d7" | |
+ }, | |
+ "arm64_big_sur": { | |
+ "cellar": "/opt/homebrew/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:a494fef2d5a93ecdadfce8530964af6ddcdb8662795bb7aa35ef8f8d8f659a01", | |
+ "sha256": "a494fef2d5a93ecdadfce8530964af6ddcdb8662795bb7aa35ef8f8d8f659a01" | |
+ }, | |
+ "monterey": { | |
+ "cellar": "/usr/local/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:b835b65ef6d4b85e36b7a315133fd9310a4ab6184caef6e8c99174d4aeec7bbb", | |
+ "sha256": "b835b65ef6d4b85e36b7a315133fd9310a4ab6184caef6e8c99174d4aeec7bbb" | |
+ }, | |
+ "big_sur": { | |
+ "cellar": "/usr/local/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:a65289290fb40e981887568f2711357402f2e9e7e42f57e2c4d3984f11b36f7a", | |
+ "sha256": "a65289290fb40e981887568f2711357402f2e9e7e42f57e2c4d3984f11b36f7a" | |
+ }, | |
+ "catalina": { | |
+ "cellar": "/usr/local/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:d35f1a769b57ff7180076d53af7c1602ff7e3d3f29f81d6e5a6cb1a90cbc6a3a", | |
+ "sha256": "d35f1a769b57ff7180076d53af7c1602ff7e3d3f29f81d6e5a6cb1a90cbc6a3a" | |
+ }, | |
+ "x86_64_linux": { | |
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar", | |
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:d020ef50ab876425fa9d9555473b3925da14fe80365a7dc39f53e98e5b7960de", | |
+ "sha256": "d020ef50ab876425fa9d9555473b3925da14fe80365a7dc39f53e98e5b7960de" | |
+ } | |
+ } | |
+ } | |
} | |
} | |
}, | |
"system": { | |
"macos": { | |
"monterey": { | |
- "HOMEBREW_VERSION": "3.3.13", | |
- "HOMEBREW_PREFIX": "/usr/local", | |
- "Homebrew/homebrew-core": "18c5a8c7d1ed4d58a80c1b3d5485c26f290eaa01", | |
- "CLT": "12.5.0.22.11", | |
- "Xcode": "12.5.1", | |
- "macOS": "12.2" | |
+ "HOMEBREW_VERSION": "3.5.5", | |
+ "HOMEBREW_PREFIX": "/opt/homebrew", | |
+ "Homebrew/homebrew-core": "b0fa68871ce4b23a8ea225529e5bc4aea15bac95", | |
+ "CLT": "13.0.0.0.1.1627064638", | |
+ "Xcode": "13.2.1", | |
+ "macOS": "12.3" | |
} | |
} | |
} | |
Only in truth-new/opensource: CHANGELOG.md | |
Only in truth-new/opensource: CONTRIBUTING.md | |
Only in truth-old/opensource: Capfile | |
diff -ru truth-old/opensource/Gemfile truth-new/opensource/Gemfile | |
--- truth-old/opensource/Gemfile 2022-06-08 09:15:38 | |
+++ truth-new/opensource/Gemfile 2024-04-05 09:07:34 | |
@@ -15,24 +15,25 @@ | |
gem 'hamlit-rails', '~> 0.2' | |
gem 'pg', '~> 1.2' | |
gem 'makara', '~> 0.5' | |
-gem 'pghero', '~> 2.8' | |
gem 'dotenv-rails', '~> 2.7' | |
+gem 'acts_as_list', '~> 1.1' | |
gem 'aws-sdk-s3', '~> 1.96', require: false | |
gem 'activerecord-import' | |
gem 'fog-core', '<= 2.1.0' | |
gem 'fog-openstack', '~> 0.3', require: false | |
-gem 'paperclip', '~> 6.0' | |
+gem 'paperclip', '~> 6.0.0' | |
gem 'blurhash', '~> 0.1' | |
+gem 'composite_primary_keys', '~> 13.0' | |
gem 'active_model_serializers', '~> 0.10' | |
-gem 'addressable', '~> 2.7' | |
+gem 'addressable', '~> 2.8' | |
gem 'bootsnap', '~> 1.6.0', require: false | |
gem 'browser' | |
gem 'charlock_holmes', '~> 0.7.7' | |
gem 'iso-639' | |
-gem 'chewy', '~> 5.2' | |
-gem 'cld3', '~> 3.4.2' | |
+gem 'chewy', '~> 7.2.4' | |
+gem 'cld3', '~> 3.5.0' | |
gem 'devise', '~> 4.8' | |
gem 'devise-two-factor', '~> 4.0' | |
@@ -46,10 +47,14 @@ | |
gem 'omniauth', '~> 1.9' | |
gem 'omniauth-rails_csrf_protection', '~> 0.1' | |
+gem 'amqp', '~> 1.8.0' | |
+gem 'bunny', '~> 2.19.0' | |
gem 'color_diff', '~> 0.1' | |
gem 'discard', '~> 1.2' | |
gem 'doorkeeper', '~> 5.5' | |
gem 'ed25519', '~> 1.2' | |
+gem 'fabrication', '~> 2.22' | |
+gem 'faker', '~> 2.18' | |
gem 'fast_blank', '~> 1.0' | |
gem 'fastimage' | |
gem 'hiredis', '~> 0.6' | |
@@ -59,19 +64,20 @@ | |
gem 'http_accept_language', '~> 2.1' | |
gem 'httplog', '~> 1.5.0' | |
gem 'idn-ruby', require: 'idn' | |
+gem 'jwe', '~> 0.4.0' | |
gem 'jwt', '~> 2.2' | |
gem 'kaminari', '~> 1.2' | |
gem 'link_header', '~> 0.0' | |
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar' | |
gem 'nokogiri', '~> 1.11' | |
-gem 'nsa', '~> 0.2' | |
gem 'oj', '~> 3.11' | |
gem 'ox', '~> 2.14' | |
+gem 'panko_serializer', '~> 0.7.7' | |
gem 'parslet' | |
-gem 'parallel', '~> 1.20' | |
+gem 'phonelib', '~> 0.8.7' | |
gem 'posix-spawn' | |
gem 'prometheus_exporter', '~> 1.0' | |
-gem 'google-protobuf', '~> 3.19' | |
+gem 'ruby_proto_schemas', git: "https://gitlab.com/tmediatech/ruby-proto-schemas.git", tag: "v4" | |
gem 'premailer-rails' | |
gem 'pundit', '~> 2.1' | |
gem 'rack-attack', '~> 6.5' | |
@@ -87,28 +93,31 @@ | |
gem 'scenic', '~> 1.5' | |
gem 'sidekiq', '~> 6.2' | |
gem 'sidekiq-scheduler', '~> 3.1' | |
-gem 'sidekiq-unique-jobs', '~> 7.0' | |
+gem 'sidekiq-unique-jobs', '~> 7.1' | |
gem 'sidekiq-bulk', '~>0.2.0' | |
gem 'simple-navigation', '~> 4.3' | |
gem 'simple_form', '~> 5.1' | |
+gem 'sneakers', '~> 2.3', '>= 2.3.5' | |
+gem 'sneakers_handlers', '~> 0.0.8' | |
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' | |
gem 'stoplight', '~> 2.2.1' | |
gem 'strong_migrations', '~> 0.7' | |
+gem 'swagger-blocks' | |
gem 'tty-prompt', '~> 0.23', require: false | |
gem 'twitter-text', '~> 3.1.0' | |
gem 'tzinfo-data', '~> 1.2021' | |
gem 'webpacker', '~> 5.4.2' | |
gem 'webpush', '~> 0.3' | |
-gem 'webauthn', '~> 3.0.0.alpha1' | |
+gem 'webauthn', '~> 2.5' | |
gem 'json-ld' | |
gem 'json-ld-preloaded', '~> 3.1' | |
gem 'rdf-normalize', '~> 0.4' | |
group :development, :test do | |
- gem 'fabrication', '~> 2.22' | |
gem 'fuubar', '~> 2.5' | |
gem 'i18n-tasks', '~> 0.9', require: false | |
+ gem 'oauth2' | |
gem 'pry-byebug', '~> 3.9' | |
gem 'pry-rails', '~> 0.3' | |
gem 'rspec-rails', '~> 5.0' | |
@@ -121,7 +130,6 @@ | |
group :test do | |
gem 'capybara', '~> 3.35' | |
gem 'climate_control', '~> 0.2' | |
- gem 'faker', '~> 2.18' | |
gem 'microformats', '~> 4.2' | |
gem 'rails-controller-testing', '~> 1.0' | |
gem 'rspec-sidekiq', '~> 3.1' | |
@@ -140,16 +148,11 @@ | |
gem 'letter_opener', '~> 1.7' | |
gem 'letter_opener_web', '~> 1.4' | |
gem 'memory_profiler' | |
- gem 'rubocop', '~> 1.16', require: false | |
+ gem 'rubocop', '~> 1.60', require: false | |
gem 'rubocop-rails', '~> 2.10', require: false | |
+ gem 'rubocop-rspec', '~> 2.26', require: false | |
gem 'brakeman', '~> 5.0', require: false | |
gem 'bundler-audit', '~> 0.8', require: false | |
- | |
- gem 'capistrano', '~> 3.16' | |
- gem 'capistrano-rails', '~> 1.6' | |
- gem 'capistrano-rbenv', '~> 2.2' | |
- gem 'capistrano-yarn', '~> 2.0' | |
- | |
gem 'stackprof' | |
end | |
@@ -159,9 +162,7 @@ | |
gem 'concurrent-ruby', require: false | |
gem 'connection_pool', require: false | |
- | |
-gem 'xorcist', '~> 1.1' | |
gem 'resolv', '~> 0.1.0' | |
-gem 'newrelic_rpm', '~> 7.2' | |
\ No newline at end of file | |
+gem 'newrelic_rpm', '~> 7.2' | |
diff -ru truth-old/opensource/Gemfile.lock truth-new/opensource/Gemfile.lock | |
--- truth-old/opensource/Gemfile.lock 2022-06-08 09:15:38 | |
+++ truth-new/opensource/Gemfile.lock 2024-04-05 09:07:34 | |
@@ -1,3 +1,11 @@ | |
+GIT | |
+ remote: https://gitlab.com/tmediatech/ruby-proto-schemas.git | |
+ revision: c1416cc1a1716b2d46d6a0dcd4470630182e5a86 | |
+ tag: v4 | |
+ specs: | |
+ ruby_proto_schemas (0.1.0) | |
+ google-protobuf (~> 3.19) | |
+ | |
GEM | |
remote: https://rubygems.org/ | |
specs: | |
@@ -68,10 +76,14 @@ | |
minitest (>= 5.1) | |
tzinfo (~> 2.0) | |
zeitwerk (~> 2.3) | |
- addressable (2.7.0) | |
- public_suffix (>= 2.0.2, < 5.0) | |
- airbrussh (1.4.0) | |
- sshkit (>= 1.6.1, != 1.7.0) | |
+ acts_as_list (1.1.0) | |
+ activerecord (>= 4.2) | |
+ addressable (2.8.6) | |
+ public_suffix (>= 2.0.2, < 6.0) | |
+ amq-protocol (2.3.2) | |
+ amqp (1.8.0) | |
+ amq-protocol (>= 2.2.0) | |
+ eventmachine | |
android_key_attestation (0.3.0) | |
annotate (3.1.1) | |
activerecord (>= 3.2, < 7.0) | |
@@ -79,7 +91,7 @@ | |
ast (2.4.2) | |
attr_encrypted (3.1.0) | |
encryptor (~> 3.0.0) | |
- awrence (1.1.1) | |
+ awrence (1.2.1) | |
aws-eventstream (1.1.1) | |
aws-partitions (1.465.0) | |
aws-sdk-core (3.114.1) | |
@@ -101,7 +113,7 @@ | |
coderay (>= 1.0.0) | |
erubi (>= 1.0.0) | |
rack (>= 0.9.0) | |
- bindata (2.4.8) | |
+ bindata (2.4.10) | |
binding_of_caller (1.0.0) | |
debug_inspector (>= 0.0.1) | |
blurhash (0.1.5) | |
@@ -120,22 +132,10 @@ | |
bundler-audit (0.8.0) | |
bundler (>= 1.2.0, < 3) | |
thor (~> 1.0) | |
+ bunny (2.19.0) | |
+ amq-protocol (~> 2.3, >= 2.3.1) | |
+ sorted_set (~> 1, >= 1.0.2) | |
byebug (11.1.3) | |
- capistrano (3.16.0) | |
- airbrussh (>= 1.0.0) | |
- i18n | |
- rake (>= 10.0.0) | |
- sshkit (>= 1.9.0) | |
- capistrano-bundler (2.0.1) | |
- capistrano (~> 3.1) | |
- capistrano-rails (1.6.1) | |
- capistrano (~> 3.1) | |
- capistrano-bundler (>= 1.1, < 3) | |
- capistrano-rbenv (2.2.0) | |
- capistrano (~> 3.1) | |
- sshkit (~> 1.3) | |
- capistrano-yarn (2.0.2) | |
- capistrano (~> 3.0) | |
capybara (3.35.3) | |
addressable | |
mini_mime (>= 0.1.3) | |
@@ -148,21 +148,23 @@ | |
activesupport | |
cbor (0.5.9.6) | |
charlock_holmes (0.7.7) | |
- chewy (5.2.0) | |
+ chewy (7.2.6) | |
activesupport (>= 5.2) | |
- elasticsearch (>= 2.0.0) | |
+ elasticsearch (>= 7.12.0, < 7.14.0) | |
elasticsearch-dsl | |
chunky_png (1.4.0) | |
- cld3 (3.4.2) | |
+ cld3 (3.5.0) | |
ffi (>= 1.1.0, < 1.16.0) | |
climate_control (0.2.0) | |
coderay (1.1.3) | |
color_diff (0.1) | |
- concurrent-ruby (1.1.9) | |
+ composite_primary_keys (13.0.3) | |
+ activerecord (~> 6.1.0) | |
+ concurrent-ruby (1.1.10) | |
connection_pool (2.2.5) | |
- cose (1.0.0) | |
+ cose (1.2.0) | |
cbor (~> 0.5.9) | |
- openssl-signature_algorithm (~> 0.4.0) | |
+ openssl-signature_algorithm (~> 1.0) | |
crack (0.4.5) | |
rexml | |
crass (1.0.6) | |
@@ -198,28 +200,47 @@ | |
railties (>= 3.2) | |
e2mmap (0.1.0) | |
ed25519 (1.2.4) | |
- elasticsearch (7.10.1) | |
- elasticsearch-api (= 7.10.1) | |
- elasticsearch-transport (= 7.10.1) | |
- elasticsearch-api (7.10.1) | |
+ elasticsearch (7.13.3) | |
+ elasticsearch-api (= 7.13.3) | |
+ elasticsearch-transport (= 7.13.3) | |
+ elasticsearch-api (7.13.3) | |
multi_json | |
- elasticsearch-dsl (0.1.9) | |
- elasticsearch-transport (7.10.1) | |
+ elasticsearch-dsl (0.1.10) | |
+ elasticsearch-transport (7.13.3) | |
faraday (~> 1) | |
multi_json | |
encryptor (3.0.0) | |
erubi (1.10.0) | |
et-orbi (1.2.4) | |
tzinfo | |
+ eventmachine (1.2.7) | |
excon (0.76.0) | |
fabrication (2.22.0) | |
faker (2.18.0) | |
i18n (>= 1.6, < 2) | |
- faraday (1.3.0) | |
+ faraday (1.10.0) | |
+ faraday-em_http (~> 1.0) | |
+ faraday-em_synchrony (~> 1.0) | |
+ faraday-excon (~> 1.1) | |
+ faraday-httpclient (~> 1.0) | |
+ faraday-multipart (~> 1.0) | |
faraday-net_http (~> 1.0) | |
- multipart-post (>= 1.2, < 3) | |
- ruby2_keywords | |
+ faraday-net_http_persistent (~> 1.0) | |
+ faraday-patron (~> 1.0) | |
+ faraday-rack (~> 1.0) | |
+ faraday-retry (~> 1.0) | |
+ ruby2_keywords (>= 0.0.4) | |
+ faraday-em_http (1.0.0) | |
+ faraday-em_synchrony (1.0.0) | |
+ faraday-excon (1.1.0) | |
+ faraday-httpclient (1.0.1) | |
+ faraday-multipart (1.0.4) | |
+ multipart-post (~> 2) | |
faraday-net_http (1.0.1) | |
+ faraday-net_http_persistent (1.2.0) | |
+ faraday-patron (1.0.0) | |
+ faraday-rack (1.0.0) | |
+ faraday-retry (1.0.3) | |
fast_blank (1.0.0) | |
fastimage (2.2.4) | |
ffi (1.15.5) | |
@@ -247,7 +268,7 @@ | |
ruby-progressbar (~> 1.4) | |
globalid (0.4.2) | |
activesupport (>= 4.2.0) | |
- google-protobuf (3.19.3) | |
+ google-protobuf (3.25.3) | |
hamlit (2.13.0) | |
temple (>= 0.8.2) | |
thor | |
@@ -261,7 +282,7 @@ | |
concurrent-ruby (~> 1.0) | |
hashdiff (1.0.1) | |
hashie (4.1.0) | |
- highline (2.0.3) | |
+ highline (2.1.0) | |
hiredis (0.6.3) | |
hkdf (0.3.0) | |
htmlentities (4.3.4) | |
@@ -279,9 +300,9 @@ | |
httplog (1.5.0) | |
rack (>= 1.0) | |
rainbow (>= 2.0.0) | |
- i18n (1.8.10) | |
+ i18n (1.12.0) | |
concurrent-ruby (~> 1.0) | |
- i18n-tasks (0.9.34) | |
+ i18n-tasks (0.9.37) | |
activesupport (>= 4.0.2) | |
ast (>= 2.1.0) | |
erubi | |
@@ -308,6 +329,7 @@ | |
json-ld (~> 3.1) | |
rdf (~> 3.1) | |
jsonapi-renderer (0.2.2) | |
+ jwe (0.4.0) | |
jwt (2.2.2) | |
kaminari (1.2.1) | |
activesupport (>= 4.1.0) | |
@@ -321,6 +343,7 @@ | |
activerecord | |
kaminari-core (= 1.2.1) | |
kaminari-core (1.2.1) | |
+ language_server-protocol (3.17.0.3) | |
launchy (2.5.0) | |
addressable (~> 2.7) | |
letter_opener (1.7.0) | |
@@ -358,14 +381,12 @@ | |
rake | |
mini_mime (1.0.3) | |
mini_portile2 (2.5.3) | |
- minitest (5.14.4) | |
+ minitest (5.16.2) | |
msgpack (1.4.2) | |
multi_json (1.15.0) | |
- multipart-post (2.1.1) | |
+ multi_xml (0.6.0) | |
+ multipart-post (2.2.3) | |
net-ldap (0.17.0) | |
- net-scp (3.0.0) | |
- net-ssh (>= 2.6.5, < 7.0.0) | |
- net-ssh (6.1.0) | |
newrelic_rpm (7.2.0) | |
nio4r (2.5.7) | |
nokogiri (1.11.7) | |
@@ -373,11 +394,13 @@ | |
racc (~> 1.4) | |
nokogumbo (2.0.4) | |
nokogiri (~> 1.8, >= 1.8.4) | |
- nsa (0.2.8) | |
- activesupport (>= 4.2, < 7) | |
- concurrent-ruby (~> 1.0, >= 1.0.2) | |
- sidekiq (>= 3.5) | |
- statsd-ruby (~> 1.4, >= 1.4.0) | |
+ oauth2 (2.0.6) | |
+ faraday (>= 0.17.3, < 3.0) | |
+ jwt (>= 1.0, < 3.0) | |
+ multi_xml (~> 0.5) | |
+ rack (>= 1.2, < 3) | |
+ rash_alt (>= 0.4, < 1) | |
+ version_gem (~> 1.1) | |
oj (3.11.5) | |
omniauth (1.9.1) | |
hashie (>= 3.4.6) | |
@@ -393,9 +416,13 @@ | |
omniauth (~> 1.3, >= 1.3.2) | |
ruby-saml (~> 1.9) | |
openssl (2.2.0) | |
- openssl-signature_algorithm (0.4.0) | |
+ openssl-signature_algorithm (1.1.1) | |
+ openssl (~> 2.0) | |
orm_adapter (0.5.0) | |
ox (2.14.5) | |
+ panko_serializer (0.7.7) | |
+ activesupport | |
+ oj (> 3.11.0, < 4.0.0) | |
paperclip (6.0.0) | |
activemodel (>= 4.2.0) | |
activesupport (>= 4.2.0) | |
@@ -405,14 +432,14 @@ | |
parallel (1.20.1) | |
parallel_tests (3.7.0) | |
parallel | |
- parser (3.0.1.1) | |
+ parser (3.3.0.5) | |
ast (~> 2.4.1) | |
+ racc | |
parslet (2.0.0) | |
pastel (0.8.0) | |
tty-color (~> 0.5) | |
pg (1.2.3) | |
- pghero (2.8.1) | |
- activerecord (>= 5) | |
+ phonelib (0.8.7) | |
pkg-config (1.4.6) | |
posix-spawn (0.3.15) | |
premailer (1.14.2) | |
@@ -433,7 +460,7 @@ | |
pry (~> 0.13.0) | |
pry-rails (0.3.9) | |
pry (>= 0.10.4) | |
- public_suffix (4.0.6) | |
+ public_suffix (5.0.4) | |
puma (5.4.0) | |
nio4r (~> 2.0) | |
pundit (2.1.0) | |
@@ -490,6 +517,9 @@ | |
activerecord (>= 6.0.4) | |
activesupport (>= 6.0.4) | |
i18n | |
+ rash_alt (0.4.12) | |
+ hashie (>= 3.4) | |
+ rbtree (0.4.5) | |
rdf (3.1.13) | |
hamster (~> 3.0) | |
link_header (~> 0.0, >= 0.0.8) | |
@@ -534,25 +564,35 @@ | |
rspec-support (3.10.2) | |
rspec_junit_formatter (0.4.1) | |
rspec-core (>= 2, < 4, != 2.12.0) | |
- rubocop (1.16.0) | |
+ rubocop (1.60.2) | |
+ json (~> 2.3) | |
+ language_server-protocol (>= 3.17.0) | |
parallel (~> 1.10) | |
- parser (>= 3.0.0.0) | |
+ parser (>= 3.3.0.2) | |
rainbow (>= 2.2.2, < 4.0) | |
regexp_parser (>= 1.8, < 3.0) | |
- rexml | |
- rubocop-ast (>= 1.7.0, < 2.0) | |
+ rexml (>= 3.2.5, < 4.0) | |
+ rubocop-ast (>= 1.30.0, < 2.0) | |
ruby-progressbar (~> 1.7) | |
- unicode-display_width (>= 1.4.0, < 3.0) | |
- rubocop-ast (1.7.0) | |
- parser (>= 3.0.1.1) | |
+ unicode-display_width (>= 2.4.0, < 3.0) | |
+ rubocop-ast (1.30.0) | |
+ parser (>= 3.2.1.0) | |
+ rubocop-capybara (2.20.0) | |
+ rubocop (~> 1.41) | |
+ rubocop-factory_bot (2.25.1) | |
+ rubocop (~> 1.41) | |
rubocop-rails (2.10.1) | |
activesupport (>= 4.2.0) | |
rack (>= 1.1) | |
rubocop (>= 1.7.0, < 2.0) | |
+ rubocop-rspec (2.26.1) | |
+ rubocop (~> 1.40) | |
+ rubocop-capybara (~> 2.17) | |
+ rubocop-factory_bot (~> 2.22) | |
ruby-progressbar (1.11.0) | |
ruby-saml (1.11.0) | |
nokogiri (>= 1.5.10) | |
- ruby2_keywords (0.0.4) | |
+ ruby2_keywords (0.0.5) | |
rufus-scheduler (3.7.0) | |
fugit (~> 1.1, >= 1.1.6) | |
safety_net_attestation (0.4.0) | |
@@ -564,8 +604,10 @@ | |
scenic (1.5.4) | |
activerecord (>= 4.0.0) | |
railties (>= 4.0.0) | |
- securecompare (1.0.0) | |
semantic_range (3.0.0) | |
+ serverengine (2.0.7) | |
+ sigdump (~> 0.2.2) | |
+ set (1.0.2) | |
sidekiq (6.2.1) | |
connection_pool (>= 2.2.2) | |
rack (~> 2.0) | |
@@ -579,11 +621,12 @@ | |
sidekiq (>= 3) | |
thwait | |
tilt (>= 1.4.0) | |
- sidekiq-unique-jobs (7.0.12) | |
+ sidekiq-unique-jobs (7.1.25) | |
brpoplpush-redis_script (> 0.1.1, <= 2.0.0) | |
concurrent-ruby (~> 1.0, >= 1.0.5) | |
- sidekiq (>= 5.0, < 7.0) | |
- thor (>= 0.20, < 2.0) | |
+ sidekiq (>= 5.0, < 8.0) | |
+ thor (>= 0.20, < 3.0) | |
+ sigdump (0.2.5) | |
simple-navigation (4.3.0) | |
activesupport (>= 2.3.2) | |
simple_form (5.1.0) | |
@@ -595,6 +638,17 @@ | |
simplecov_json_formatter (~> 0.1) | |
simplecov-html (0.12.3) | |
simplecov_json_formatter (0.1.2) | |
+ sneakers (2.11.0) | |
+ bunny (~> 2.12) | |
+ concurrent-ruby (~> 1.0) | |
+ rake | |
+ serverengine (~> 2.0.5) | |
+ thor | |
+ sneakers_handlers (0.0.8) | |
+ sneakers (~> 2.0) | |
+ sorted_set (1.0.3) | |
+ rbtree | |
+ set (~> 1.0) | |
sprockets (3.7.2) | |
concurrent-ruby (~> 1.0) | |
rack (> 1, < 3) | |
@@ -602,26 +656,23 @@ | |
actionpack (>= 4.0) | |
activesupport (>= 4.0) | |
sprockets (>= 3.0.0) | |
- sshkit (1.21.2) | |
- net-scp (>= 1.1.2) | |
- net-ssh (>= 2.8.0) | |
stackprof (0.2.17) | |
- statsd-ruby (1.5.0) | |
stoplight (2.2.1) | |
strong_migrations (0.7.6) | |
activerecord (>= 5) | |
+ swagger-blocks (3.0.0) | |
temple (0.8.2) | |
- terminal-table (3.0.0) | |
- unicode-display_width (~> 1.1, >= 1.1.1) | |
+ terminal-table (3.0.2) | |
+ unicode-display_width (>= 1.1.1, < 3) | |
terrapin (0.6.0) | |
climate_control (>= 0.0.3, < 1.0) | |
thor (1.1.0) | |
thwait (0.2.0) | |
e2mmap | |
tilt (2.0.10) | |
- tpm-key_attestation (0.9.0) | |
+ tpm-key_attestation (0.10.0) | |
bindata (~> 2.4) | |
- openssl-signature_algorithm (~> 0.4.0) | |
+ openssl-signature_algorithm (~> 1.0) | |
tty-color (0.6.0) | |
tty-cursor (0.7.1) | |
tty-prompt (0.23.1) | |
@@ -635,27 +686,27 @@ | |
twitter-text (3.1.0) | |
idn-ruby | |
unf (~> 0.1.0) | |
- tzinfo (2.0.4) | |
+ tzinfo (2.0.5) | |
concurrent-ruby (~> 1.0) | |
tzinfo-data (1.2021.1) | |
tzinfo (>= 1.0.0) | |
unf (0.1.4) | |
unf_ext | |
unf_ext (0.0.7.7) | |
- unicode-display_width (1.7.0) | |
+ unicode-display_width (2.5.0) | |
uniform_notifier (1.14.1) | |
+ version_gem (1.1.0) | |
warden (1.2.9) | |
rack (>= 2.0.9) | |
- webauthn (3.0.0.alpha1) | |
+ webauthn (2.5.1) | |
android_key_attestation (~> 0.3.0) | |
awrence (~> 1.1) | |
bindata (~> 2.4) | |
cbor (~> 0.5.9) | |
- cose (~> 1.0) | |
- openssl (~> 2.0) | |
+ cose (~> 1.1) | |
+ openssl (~> 2.2) | |
safety_net_attestation (~> 0.4.0) | |
- securecompare (~> 1.0) | |
- tpm-key_attestation (~> 0.9.0) | |
+ tpm-key_attestation (~> 0.10.0) | |
webmock (3.13.0) | |
addressable (>= 2.3.6) | |
crack (>= 0.3.2) | |
@@ -673,10 +724,9 @@ | |
websocket-extensions (>= 0.1.0) | |
websocket-extensions (0.1.5) | |
wisper (2.0.1) | |
- xorcist (1.1.2) | |
xpath (3.2.0) | |
nokogiri (~> 1.8) | |
- zeitwerk (2.4.2) | |
+ zeitwerk (2.6.0) | |
PLATFORMS | |
ruby | |
@@ -685,7 +735,9 @@ | |
active_model_serializers (~> 0.10) | |
active_record_query_trace (~> 1.8) | |
activerecord-import | |
- addressable (~> 2.7) | |
+ acts_as_list (~> 1.1) | |
+ addressable (~> 2.8) | |
+ amqp (~> 1.8.0) | |
annotate (~> 3.1) | |
aws-sdk-s3 (~> 1.96) | |
better_errors (~> 2.9) | |
@@ -696,16 +748,14 @@ | |
browser | |
bullet (~> 6.1) | |
bundler-audit (~> 0.8) | |
- capistrano (~> 3.16) | |
- capistrano-rails (~> 1.6) | |
- capistrano-rbenv (~> 2.2) | |
- capistrano-yarn (~> 2.0) | |
+ bunny (~> 2.19.0) | |
capybara (~> 3.35) | |
charlock_holmes (~> 0.7.7) | |
- chewy (~> 5.2) | |
- cld3 (~> 3.4.2) | |
+ chewy (~> 7.2.4) | |
+ cld3 (~> 3.5.0) | |
climate_control (~> 0.2) | |
color_diff (~> 0.1) | |
+ composite_primary_keys (~> 13.0) | |
concurrent-ruby | |
connection_pool | |
devise (~> 4.8) | |
@@ -722,7 +772,6 @@ | |
fog-core (<= 2.1.0) | |
fog-openstack (~> 0.3) | |
fuubar (~> 2.5) | |
- google-protobuf (~> 3.19) | |
hamlit-rails (~> 0.2) | |
hiredis (~> 0.6) | |
htmlentities (~> 4.3) | |
@@ -734,6 +783,7 @@ | |
iso-639 | |
json-ld | |
json-ld-preloaded (~> 3.1) | |
+ jwe (~> 0.4.0) | |
jwt (~> 2.2) | |
kaminari (~> 1.2) | |
letter_opener (~> 1.7) | |
@@ -748,7 +798,7 @@ | |
net-ldap (~> 0.17) | |
newrelic_rpm (~> 7.2) | |
nokogiri (~> 1.11) | |
- nsa (~> 0.2) | |
+ oauth2 | |
oj (~> 3.11) | |
omniauth (~> 1.9) | |
omniauth-cas (~> 2.0) | |
@@ -756,12 +806,12 @@ | |
omniauth-saml (~> 1.10) | |
openssl (~> 2.2.0) | |
ox (~> 2.14) | |
- paperclip (~> 6.0) | |
- parallel (~> 1.20) | |
+ panko_serializer (~> 0.7.7) | |
+ paperclip (~> 6.0.0) | |
parallel_tests (~> 3.7) | |
parslet | |
pg (~> 1.2) | |
- pghero (~> 2.8) | |
+ phonelib (~> 0.8.7) | |
pkg-config (~> 1.4) | |
posix-spawn | |
premailer-rails | |
@@ -787,35 +837,39 @@ | |
rspec-rails (~> 5.0) | |
rspec-sidekiq (~> 3.1) | |
rspec_junit_formatter (~> 0.4) | |
- rubocop (~> 1.16) | |
+ rubocop (~> 1.60) | |
rubocop-rails (~> 2.10) | |
+ rubocop-rspec (~> 2.26) | |
ruby-progressbar (~> 1.11) | |
+ ruby_proto_schemas! | |
sanitize (~> 5.2) | |
scenic (~> 1.5) | |
sidekiq (~> 6.2) | |
sidekiq-bulk (~> 0.2.0) | |
sidekiq-scheduler (~> 3.1) | |
- sidekiq-unique-jobs (~> 7.0) | |
+ sidekiq-unique-jobs (~> 7.1) | |
simple-navigation (~> 4.3) | |
simple_form (~> 5.1) | |
simplecov (~> 0.21) | |
+ sneakers (~> 2.3, >= 2.3.5) | |
+ sneakers_handlers (~> 0.0.8) | |
sprockets (~> 3.7.2) | |
sprockets-rails (~> 3.2) | |
stackprof | |
stoplight (~> 2.2.1) | |
strong_migrations (~> 0.7) | |
+ swagger-blocks | |
thor (~> 1.1) | |
tty-prompt (~> 0.23) | |
twitter-text (~> 3.1.0) | |
tzinfo-data (~> 1.2021) | |
- webauthn (~> 3.0.0.alpha1) | |
+ webauthn (~> 2.5) | |
webmock (~> 3.13) | |
webpacker (~> 5.4.2) | |
webpush (~> 0.3) | |
- xorcist (~> 1.1) | |
RUBY VERSION | |
ruby 2.7.2p137 | |
BUNDLED WITH | |
- 2.2.29 | |
+ 2.3.18 | |
diff -ru truth-old/opensource/Rakefile truth-new/opensource/Rakefile | |
--- truth-old/opensource/Rakefile 2022-06-08 09:15:38 | |
+++ truth-new/opensource/Rakefile 2024-04-01 14:59:13 | |
@@ -1,5 +1,5 @@ | |
# Add your own tasks in files placed in lib/tasks ending in .rake, | |
-# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. | |
+# for example lib/tasks/groups.rake, and they will automatically be available to Rake. | |
require File.expand_path('../config/application', __FILE__) | |
diff -ru truth-old/opensource/app/chewy/accounts_index.rb truth-new/opensource/app/chewy/accounts_index.rb | |
--- truth-old/opensource/app/chewy/accounts_index.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/chewy/accounts_index.rb 2024-04-01 14:59:13 | |
@@ -1,7 +1,7 @@ | |
# frozen_string_literal: true | |
class AccountsIndex < Chewy::Index | |
- settings index: { refresh_interval: '5m' }, number_of_shards: '12', analysis: { | |
+ settings index: { refresh_interval: '1m' }, number_of_shards: '12', analysis: { | |
analyzer: { | |
content: { | |
tokenizer: 'whitespace', | |
@@ -12,8 +12,26 @@ | |
tokenizer: 'edge_ngram', | |
filter: %w(lowercase asciifolding cjk_width), | |
}, | |
+ | |
+ exact: { | |
+ tokenizer: 'keyword', | |
+ filter: %w(lowercase asciifolding cjk_width), | |
+ }, | |
+ | |
+ phone: { | |
+ tokenizer: 'pattern', | |
+ filter: %w(phone), | |
+ }, | |
}, | |
+ filter: { | |
+ phone: { | |
+ type: 'pattern_capture', | |
+ preserve_original: false, | |
+ patterns: ['(\d+(\d{10}))'], | |
+ }, | |
+ }, | |
+ | |
tokenizer: { | |
edge_ngram: { | |
type: 'edge_ngram', | |
@@ -23,23 +41,34 @@ | |
}, | |
} | |
- define_type ::Account.searchable.includes(:account_stat), delete_if: ->(account) { | |
- account.destroyed? || !account.searchable? | |
- } do | |
- root date_detection: false do | |
- field :id, type: 'long' | |
+ index_scope ::Account.includes(:account_follower, :account_following, :account_status), delete_if: ->(account) { account.destroyed? || !account.searchable? } | |
- field :display_name, type: 'text', analyzer: 'content' do | |
- field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' | |
- end | |
+ root date_detection: false do | |
+ field :id, type: 'long' | |
- field :acct, type: 'text', analyzer: 'content', value: ->(account) { account.username } do | |
- field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' | |
- end | |
+ field :display_name, type: 'text', analyzer: 'content' do | |
+ field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' | |
+ field :keyword, type: 'keyword' | |
+ end | |
- field :following_count, type: 'long', value: ->(account) { account.following_count.negative? ? 0 : account.following_count } | |
- field :followers_count, type: 'long', value: ->(account) { account.followers_count.negative? ? 0 : account.followers_count } | |
- field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at } | |
+ field :acct, type: 'text', analyzer: 'content', value: ->(account) { account.username } do | |
+ field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' | |
+ field :keyword, type: 'keyword' | |
end | |
+ | |
+ field :following_count, type: 'long', value: ->(account) { account.following_count.negative? ? 0 : account.following_count } | |
+ field :followers_count, type: 'long', value: ->(account) { account.followers_count.negative? ? 0 : account.followers_count } | |
+ field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at } | |
+ | |
+ field :suspended, type: 'boolean', value: ->(account) { account.suspended? } | |
+ field :disabled, type: 'boolean', value: ->(account) { account.user_disabled? } | |
+ field :hidden, type: 'boolean', value: -> { false } | |
+ | |
+ field :email, type: 'text', analyzer: 'exact', value: ->(account) { account.user_email }do | |
+ field :keyword, type: 'keyword' | |
+ end | |
+ field :created_at, type: 'date', value: ->(account) { account.created_at } | |
+ | |
+ field :sms, type: 'text', analyzer: 'phone', search_analyzer: 'exact', value: ->(account) { account.user_sms } | |
end | |
end | |
diff -ru truth-old/opensource/app/chewy/statuses_index.rb truth-new/opensource/app/chewy/statuses_index.rb | |
--- truth-old/opensource/app/chewy/statuses_index.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/chewy/statuses_index.rb 2024-04-01 14:59:13 | |
@@ -1,7 +1,7 @@ | |
# frozen_string_literal: true | |
class StatusesIndex < Chewy::Index | |
- settings index: { refresh_interval: '15m' }, number_of_shards: '12', analysis: { | |
+ settings index: { refresh_interval: '1m' }, number_of_shards: '12', analysis: { | |
filter: { | |
english_stop: { | |
type: 'stop', | |
@@ -31,22 +31,22 @@ | |
}, | |
} | |
- define_type ::Status.unscoped.kept.without_reblogs.includes(:status_stat) do | |
+ index_scope ::Status.unscoped.kept.without_reblogs.includes(:status_favourite, :status_reply, :status_reblog) | |
- root date_detection: false do | |
- field :id, type: 'long' | |
- field :account_id, type: 'long' | |
+ root date_detection: false do | |
+ field :id, type: 'long' | |
+ field :account_id, type: 'long' | |
- field :text, type: 'text', value: ->(status) { | |
- [status.spoiler_text, Formatter.instance.plaintext(status)].reject(&:blank?).join("\n\n") | |
- } do | |
- field :stemmed, type: 'text', analyzer: 'content' | |
- end | |
+ field :text, type: 'text', value: ->(status) { | |
+ [status.spoiler_text, Formatter.instance.plaintext(status)].reject(&:blank?).join("\n\n") | |
+ } do | |
+ field :stemmed, type: 'text', analyzer: 'content' | |
+ end | |
- field :activity, type: 'integer', value: ->(status) { (status.reblogs_count * 3) + status.favourites_count } | |
+ field :activity, type: 'integer', value: ->(status) { (status.reblogs_count * 3) + status.favourites_count } | |
- field :created_at, type: 'date' | |
- end | |
- end | |
+ field :created_at, type: 'date' | |
+ field :text_hash, type: 'keyword' | |
+ end | |
end | |
diff -ru truth-old/opensource/app/chewy/tags_index.rb truth-new/opensource/app/chewy/tags_index.rb | |
--- truth-old/opensource/app/chewy/tags_index.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/chewy/tags_index.rb 2024-04-01 14:59:13 | |
@@ -1,7 +1,7 @@ | |
# frozen_string_literal: true | |
class TagsIndex < Chewy::Index | |
- settings index: { refresh_interval: '15m' }, number_of_shards: '12', analysis: { | |
+ settings index: { refresh_interval: '1m' }, number_of_shards: '12', analysis: { | |
analyzer: { | |
content: { | |
tokenizer: 'keyword', | |
@@ -23,17 +23,15 @@ | |
}, | |
} | |
- define_type ::Tag.listable, delete_if: ->(tag) { | |
- tag.destroyed? || !tag.listable? | |
- } do | |
- root date_detection: false do | |
- field :name, type: 'text', analyzer: 'content' do | |
- field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' | |
- end | |
+ index_scope ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? } | |
- field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? } | |
- field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } } | |
- field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at } | |
+ root date_detection: false do | |
+ field :name, type: 'text', analyzer: 'content' do | |
+ field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' | |
end | |
+ | |
+ field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? } | |
+ field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } } | |
+ field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at } | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/accounts_controller.rb truth-new/opensource/app/controllers/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -28,7 +28,7 @@ | |
return | |
end | |
- @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? | |
+ @pinned_statuses = cache_collection(@account.pinned_statuses.merge!(StatusPin.profile_pins), Status) if show_pinned_statuses? | |
@statuses = cached_filtered_status_page | |
@rss_url = rss_url | |
diff -ru truth-old/opensource/app/controllers/activitypub/collections_controller.rb truth-new/opensource/app/controllers/activitypub/collections_controller.rb | |
--- truth-old/opensource/app/controllers/activitypub/collections_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/activitypub/collections_controller.rb 2024-04-01 14:59:13 | |
@@ -20,7 +20,7 @@ | |
def set_items | |
case params[:id] | |
when 'featured' | |
- @items = for_signed_account { cache_collection(@account.pinned_statuses, Status) } | |
+ @items = for_signed_account { cache_collection(@account.pinned_statuses.merge!(StatusPin.profile_pins), Status) } | |
when 'tags' | |
@items = for_signed_account { @account.featured_tags } | |
when 'devices' | |
Only in truth-old/opensource/app/controllers/activitypub: inboxes_controller.rb | |
Only in truth-old/opensource/app/controllers/activitypub: outboxes_controller.rb | |
diff -ru truth-old/opensource/app/controllers/admin/accounts_controller.rb truth-new/opensource/app/controllers/admin/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/admin/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/admin/accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -43,13 +43,20 @@ | |
def reject | |
authorize @account.user, :reject? | |
- DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false) | |
+ DeleteAccountService.new.call( | |
+ @account, | |
+ @current_account.id, | |
+ deletion_type: 'admin_reject', | |
+ reserve_email: false, | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ ) | |
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct) | |
end | |
def destroy | |
authorize @account, :destroy? | |
- Admin::AccountDeletionWorker.perform_async(@account.id) | |
+ Admin::AccountDeletionWorker.perform_async(@account.id, @current_account.id) | |
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct) | |
end | |
Only in truth-old/opensource/app/controllers/admin: trending_truths_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/base_controller.rb truth-new/opensource/app/controllers/api/base_controller.rb | |
--- truth-old/opensource/app/controllers/api/base_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/base_controller.rb 2024-04-12 09:09:08 | |
@@ -3,8 +3,13 @@ | |
class Api::BaseController < ApplicationController | |
DEFAULT_STATUSES_LIMIT = 20 | |
DEFAULT_ACCOUNTS_LIMIT = 40 | |
+ VERIFICATION_INTERVAL = 1.hour.ago.freeze | |
+ RATE_LIMIT_EXPIRE_AFTER = 7.days.seconds | |
include RateLimitHeaders | |
+ include Redisable | |
+ include AppAttestable | |
+ include Clientable | |
skip_before_action :store_current_location | |
skip_before_action :require_functional!, unless: :whitelist_mode? | |
@@ -17,76 +22,111 @@ | |
skip_around_action :set_locale | |
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| | |
- errror_message = e.to_s | |
+ error_message = e.to_s | |
if params[:controller] == 'api/v1/admin/accounts' && params[:action] == 'create' | |
filters = Rails.application.config.filter_parameters | |
f = ActiveSupport::ParameterFilter.new filters | |
filtered_params = f.filter params | |
- Rails.logger.info("Unsuccessful registration: #{errror_message}. params: #{filtered_params}") | |
+ Rails.logger.info("Unsuccessful registration: #{error_message}. params: #{filtered_params}") | |
end | |
- response = { error: errror_message } | |
+ error = error_message | |
+ additional_fields = {} | |
- # Output an errorCode key w/ value instead of shoving it into one giant string in the error key | |
- # Usage: object.errors.add(:errorCode, 'machine_readable_text') | |
- if (errror_message.include?('Errorcode')) | |
- message = errror_message.split(', Errorcode ') | |
- formatted_readable_message = message[0].sub('Validation failed: ', '') | |
- response = { error: formatted_readable_message, errorCode: message[1] } | |
+ # Output an error_code key w/ value instead of shoving it into one giant string in the error key | |
+ # Usage: object.errors.add(:error_code, 'machine_readable_text') | |
+ if error_message.include?('Error code') | |
+ message = error_message.split(', Error code ') | |
+ error = message[0].sub(I18n.t('activerecord.errors.messages.record_invalid').split(': ')[0], '').sub(': ', '') # there's not an easy way to remove the 'Validation failed' text in regards to localization | |
+ code = message[1] | |
+ # Need this for backwards compatibility | |
+ if message[1] == 'followLimitReached' | |
+ code = message[1].underscore | |
+ additional_fields[:errorCode] = message[1] | |
+ elsif request.path == validate_api_v1_groups_path | |
+ additional_fields[:message] = message[1] | |
+ else | |
+ additional_fields | |
+ end | |
end | |
- render json: response, status: 422 | |
+ render_error(422, error, code, error, additional_fields) | |
end | |
- rescue_from ActiveRecord::RecordNotUnique do | |
- render json: { error: 'Duplicate record' }, status: 422 | |
+ rescue_from ActiveRecord::RecordNotUnique do |e| | |
+ Rails.logger.error "RecordNotUnique: #{e}, user: #{current_user.id}, user_agent: #{request.user_agent}" | |
+ message = I18n.t('errors.api.duplicate') | |
+ render_error(422, message, message, message) | |
end | |
rescue_from ActiveRecord::RecordNotFound do | |
- render json: { error: 'Record not found' }, status: 404 | |
+ message = I18n.t('errors.api.404') | |
+ render_error(404, message, nil, message) | |
end | |
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do | |
- render json: { error: 'Remote data could not be fetched' }, status: 503 | |
+ render json: { error: I18n.t('errors.api.data_fetch') }, status: 503 | |
end | |
rescue_from OpenSSL::SSL::SSLError do | |
- render json: { error: 'Remote SSL certificate could not be verified' }, status: 503 | |
+ render json: { error: I18n.t('errors.api.ssl') }, status: 503 | |
end | |
rescue_from Mastodon::NotPermittedError do | |
- render json: { error: 'This action is not allowed' }, status: 403 | |
+ message = I18n.t('errors.api.403') | |
+ render_error(403, message, nil, message) | |
end | |
+ rescue_from Mastodon::UnprocessableAssertion do |e| | |
+ alert(e.message) unless e.message == e.class.to_s | |
+ render json: { error: 'Unable to verify assertion' }, status: 422 | |
+ end | |
+ | |
+ rescue_from Mastodon::AttestationError do | |
+ render json: { error: 'Unable to verify attestation' }, status: 400 | |
+ end | |
+ | |
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do |e| | |
- Rails.logger.info("Network error: #{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}") if e.class == Seahorse::Client::NetworkingError | |
- render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503 | |
+ Rails.logger.info("Network error: #{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}") if e.instance_of?(Seahorse::Client::NetworkingError) | |
+ render json: { error: I18n.t('errors.api.503') }, status: 503 | |
end | |
rescue_from Mastodon::RateLimitExceededError do |e| | |
Rails.logger.info("#{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}") | |
+ track_rate_limited_user | |
render json: { error: I18n.t('errors.429') }, status: 429 | |
end | |
+ rescue_from Mastodon::HostileRateLimitExceededError do |e| | |
+ Rails.logger.info("#{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}") | |
+ track_hostile_rate_limited_user | |
+ render json: {}, status: 200 | |
+ end | |
+ | |
rescue_from ActionController::ParameterMissing do |e| | |
render json: { error: e.to_s }, status: 400 | |
end | |
+ rescue_from WebAuthn::Error do |e| | |
+ render json: { error: e.to_s }, status: 400 | |
+ end | |
+ | |
def doorkeeper_unauthorized_render_options(error: nil) | |
- { json: { error: (error.try(:description) || 'Not authorized') } } | |
+ { json: { error: (error.try(:description) || I18n.t('errors.api.unauthorized')) } } | |
end | |
def doorkeeper_forbidden_render_options(*) | |
- { json: { error: 'This action is outside the authorized scopes' } } | |
+ { json: { error: I18n.t('errors.api.outside_scopes') } } | |
end | |
protected | |
- def set_pagination_headers(next_path = nil, prev_path = nil) | |
+ def set_pagination_headers(next_path = nil, prev_path = nil, offset = nil) | |
links = [] | |
links << [next_path, [%w(rel next)]] if next_path | |
links << [prev_path, [%w(rel prev)]] if prev_path | |
+ links << [offset, [%w(rel self)]] if offset | |
response.headers['Link'] = LinkHeader.new(links) unless links.empty? | |
end | |
@@ -105,7 +145,7 @@ | |
end | |
def current_resource_owner | |
- @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token | |
+ @current_user ||= User.with_reverification.find(doorkeeper_token.resource_owner_id) if doorkeeper_token | |
end | |
def current_user | |
@@ -115,18 +155,25 @@ | |
end | |
def require_authenticated_user! | |
- render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user | |
+ render json: { error: I18n.t('errors.api.401') }, status: 401 unless current_user | |
end | |
- def require_user!(requires_approval: true) | |
+ def require_user!(requires_approval: true, skip_sms_reverification: false) | |
if !current_user | |
- render json: { error: 'This method requires an authenticated user' }, status: 422 | |
+ alert('Current user is missing') if assert_request? | |
+ render json: { error: I18n.t('errors.api.401') }, status: 422 | |
elsif !current_user.confirmed? | |
- render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403 | |
+ alert("Current user: #{current_user.id} is not confirmed") if assert_request? | |
+ render json: { error: I18n.t('errors.api.missing_email') }, status: 403 | |
elsif requires_approval && !current_user.approved? | |
- render json: { error: 'Your login is currently pending approval' }, status: 403 | |
+ alert("Current user: #{current_user.id} is not approved") if assert_request? | |
+ render json: { error: I18n.t('errors.api.login_pending') }, status: 403 | |
elsif requires_approval && !current_user.functional? | |
- render json: { error: 'Your login is currently disabled' }, status: 403 | |
+ alert("Current user: #{current_user.id} is not functional") if assert_request? | |
+ render json: { error: I18n.t('errors.api.login_disabled') }, status: 403 | |
+ elsif !skip_sms_reverification && sms_reverification_required? | |
+ alert("Current user: #{current_user.id} is waiting sms verification") if assert_request? | |
+ render json: { error: I18n.t('errors.api.sms_reverification_pending') }, status: 403 | |
else | |
update_user_sign_in | |
end | |
@@ -136,6 +183,20 @@ | |
render json: {}, status: 200 | |
end | |
+ # rubocop:disable Metrics/ParameterLists | |
+ def render_error(status, error_message = nil, code = nil, error = nil, additional_fields = {}) | |
+ default_code = Rack::Utils::HTTP_STATUS_CODES[status] | |
+ response = { | |
+ error_message: error_message || default_code, | |
+ error_code: format_code(code || default_code), | |
+ error: error, | |
+ **additional_fields, | |
+ }.compact | |
+ | |
+ render json: response, status: status | |
+ end | |
+ # rubocop:enable Metrics/ParameterLists | |
+ | |
def authorize_if_got_token!(*scopes) | |
doorkeeper_authorize!(*scopes) if doorkeeper_token | |
end | |
@@ -146,5 +207,51 @@ | |
def disallow_unauthenticated_api_access? | |
authorized_fetch_mode? | |
+ end | |
+ | |
+ def track_rate_limited_user | |
+ redis_key = "rate_limit:#{DateTime.current.to_date}" | |
+ redis_element_key = "#{current_user.id}-#{request.remote_ip}" | |
+ Redis.current.zincrby(redis_key, 1, redis_element_key) | |
+ Redis.current.expire(redis_key, RATE_LIMIT_EXPIRE_AFTER) | |
+ end | |
+ | |
+ def track_hostile_rate_limited_user | |
+ redis_key = "hostile_rate_limit:#{DateTime.current.to_date}" | |
+ redis_element_key = "#{current_user.id}-#{request.remote_ip}" | |
+ Redis.current.zincrby(redis_key, 1, redis_element_key) | |
+ Redis.current.expire(redis_key, RATE_LIMIT_EXPIRE_AFTER) | |
+ end | |
+ | |
+ def raw_request_body | |
+ @request_body ||= JSON.parse(request.raw_post) | |
+ end | |
+ | |
+ def assert_request? | |
+ request.path == '/api/v1/truth/ios_device_check/assert' | |
+ end | |
+ | |
+ def sms_reverification_required? | |
+ return false unless current_user&.user_sms_reverification_required&.user_id | |
+ | |
+ return false if app_attest_path? | |
+ | |
+ if android_client? | |
+ return false if request.headers['x-tru-assertion'] | |
+ credential = doorkeeper_token.integrity_credentials.order(last_verified_at: :desc).first | |
+ unverified_credential = credential.present? ? false : true | |
+ elsif ios_client? | |
+ credential = doorkeeper_token.token_webauthn_credentials.order(last_verified_at: :desc).first | |
+ unverified_credential = credential.present? ? false : true | |
+ else | |
+ unverified_credential = true | |
+ end | |
+ | |
+ blocked_methods = %w(POST PUT PATCH) | |
+ !!(unverified_credential && blocked_methods.include?(request.request_method)) | |
+ end | |
+ | |
+ def app_attest_path? | |
+ request.path.start_with?('/api/v1/truth/ios_device_check') | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api: docs_controller.rb | |
Only in truth-new/opensource/app/controllers/api: mock | |
diff -ru truth-old/opensource/app/controllers/api/oembed_controller.rb truth-new/opensource/app/controllers/api/oembed_controller.rb | |
--- truth-old/opensource/app/controllers/api/oembed_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/oembed_controller.rb 2024-04-01 14:59:13 | |
@@ -7,7 +7,7 @@ | |
before_action :require_public_status! | |
def show | |
- render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default | |
+ render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default, has_video: has_video | |
end | |
private | |
@@ -17,18 +17,28 @@ | |
end | |
def require_public_status! | |
- not_found if @status.hidden? | |
+ not_found if !distributable? | |
end | |
+ def distributable? | |
+ @status.public_visibility? || @status.unlisted_visibility? || @status.group&.everyone? | |
+ end | |
+ | |
def status_finder | |
StatusFinder.new(params[:url]) | |
end | |
def maxwidth_or_default | |
- (params[:maxwidth].presence || 400).to_i | |
+ (params[:maxwidth].presence || 600).to_i | |
end | |
def maxheight_or_default | |
params[:maxheight].present? ? params[:maxheight].to_i : nil | |
+ end | |
+ | |
+ def has_video | |
+ if @status.with_media? | |
+ @status.media_attachments.first.video? | |
+ end | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/pleroma/accounts_controller.rb truth-new/opensource/app/controllers/api/pleroma/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/pleroma/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/pleroma/accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -1,5 +1,7 @@ | |
# frozen_string_literal: true | |
class Api::Pleroma::AccountsController < Api::BaseController | |
+ before_action -> { doorkeeper_authorize! :read }, only: [:mfa, :setup_totp, :backup_codes] | |
+ before_action -> { doorkeeper_authorize! :write }, only: [:confirm_totp, :delete_totp] | |
before_action :require_user! | |
before_action :prepare_two_factor, only: [:setup_totp] | |
before_action :validate_password, only: [:confirm_totp, :delete_totp] | |
diff -ru truth-old/opensource/app/controllers/api/pleroma/user_settings_controller.rb truth-new/opensource/app/controllers/api/pleroma/user_settings_controller.rb | |
--- truth-old/opensource/app/controllers/api/pleroma/user_settings_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/pleroma/user_settings_controller.rb 2024-04-01 14:59:13 | |
@@ -1,15 +1,34 @@ | |
# frozen_string_literal: true | |
class Api::Pleroma::UserSettingsController < Api::BaseController | |
+ before_action -> { doorkeeper_authorize! :write } | |
before_action :require_user! | |
before_action :validate_password | |
before_action :validate_email, only: :change_email | |
before_action :set_new_email, only: :change_email | |
+ around_action :set_locale, only: :change_password | |
def change_password | |
if current_user.reset_password(resource_params[:new_password], resource_params[:new_password_confirmation]) | |
+ OauthAccessToken.where.not(token: doorkeeper_token.token).where(resource_owner_id: current_user.id).update_all(revoked_at: Time.now.utc) | |
+ | |
render json: { status: :success } | |
else | |
- render json: { error: 'Password and password confirmation do not match.' }, status: 400 | |
+ errors = current_user.errors.to_hash | |
+ password_invalid = errors[:password]&.pop | |
+ default_error = I18n.t('users.password_mismatch', locale: :en) | |
+ message, message_with_locale, code = | |
+ if password_invalid.present? | |
+ error = errors[:base]&.pop || default_error | |
+ [error, password_invalid, 'PASSWORD_INVALID'] | |
+ else | |
+ [default_error, I18n.t('users.password_mismatch'), 'PASSWORD_MISMATCH'] | |
+ end | |
+ | |
+ render json: { | |
+ error: message, | |
+ error_code: code, | |
+ error_message: message_with_locale, | |
+ }, status: 400 | |
end | |
end | |
@@ -40,7 +59,13 @@ | |
def destroy_account! | |
current_account.suspend!(origin: :local) | |
- AccountDeletionWorker.perform_async(current_user.account_id) | |
+ account_id = current_user.account_id | |
+ AccountDeletionWorker.perform_async( | |
+ account_id, | |
+ account_id, | |
+ deletion_type: 'self_deletion', | |
+ skip_activitypub: true, | |
+ ) | |
sign_out | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/credentials_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/credentials_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/credentials_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/credentials_controller.rb 2024-04-12 16:26:23 | |
@@ -2,17 +2,23 @@ | |
class Api::V1::Accounts::CredentialsController < Api::BaseController | |
include JwtConcern | |
+ include Clientable | |
- before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, except: [:update] | |
+ TV_REQUIRED_IOS_VERSION = ENV.fetch('TV_REQUIRED_IOS_VERSION', 0).to_i | |
+ TV_REQUIRED_ANDROID_VERSION = ENV.fetch('TV_REQUIRED_ANDROID_VERSION', 0).to_i | |
+ | |
+ before_action -> { doorkeeper_authorize! :read, :'read:accounts', :ads }, except: [:update] | |
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update] | |
skip_before_action :require_functional!, only: [:show] | |
before_action :require_user!, only: [:update, :chat_token] | |
before_action :set_account, only: [:update, :chat_token, :show] | |
before_action :verify_credentials_require_user!, only: [:show] | |
+ before_action :enable_feature_flag, only: [:show] | |
+ before_action :create_tv_user, only: [:show] | |
def show | |
- render json: @account, serializer: REST::CredentialAccountSerializer | |
+ render json: @account, serializer: REST::CredentialAccountSerializer, access_token: doorkeeper_token, android_client: android_client?, tv_account_lookup: true | |
end | |
def chat_token | |
@@ -29,6 +35,7 @@ | |
def update | |
UpdateAccountService.new.call(@account, account_params, raise_error: true) | |
UserSettingsDecorator.new(current_user).update(user_settings_params) if user_settings_params | |
+ current_user.update!(unauth_visibility_params) if unauth_visibility_params | |
ActivityPub::UpdateDistributionWorker.perform_async(@account.id) | |
render json: @account, serializer: REST::CredentialAccountSerializer | |
end | |
@@ -36,9 +43,36 @@ | |
private | |
def account_params | |
- params.permit(:display_name, :location, :website, :note, :avatar, :header, :locked, :discoverable, pleroma_settings_store: {}, fields_attributes: [:name, :value]) | |
+ # Pleroma compatibility | |
+ params[:accepting_messages] = params[:accepting_messages] || accepts_chats_messages || [email protected]_messages | |
+ params[:feeds_onboarded] = truthy_param?(:feeds_onboarded) if params[:feeds_onboarded] | |
+ params[:tv_onboarded] = truthy_param?(:tv_onboarded) if params[:tv_onboarded] | |
+ params[:show_nonmember_group_statuses] = truthy_param?(:show_nonmember_group_statuses) if params[:show_nonmember_group_statuses] | |
+ params[:receive_only_follow_mentions] = truthy_param?(:receive_only_follow_mentions) if params[:receive_only_follow_mentions] | |
+ | |
+ params.permit(:display_name, | |
+ :location, | |
+ :website, | |
+ :note, | |
+ :avatar, | |
+ :header, | |
+ :locked, | |
+ :discoverable, | |
+ :accepting_messages, | |
+ :chats_onboarded, | |
+ :feeds_onboarded, | |
+ :tv_onboarded, | |
+ :show_nonmember_group_statuses, | |
+ :receive_only_follow_mentions, | |
+ pleroma_settings_store: {}, | |
+ fields_attributes: [:name, :value] | |
+ ) | |
end | |
+ def accepts_chats_messages | |
+ params[:accepts_chat_messages].to_s.empty? ? false : params[:accepts_chat_messages].to_s | |
+ end | |
+ | |
def set_account | |
@account = current_account | |
end | |
@@ -55,6 +89,10 @@ | |
} | |
end | |
+ def unauth_visibility_params | |
+ params.permit(:unauth_visibility) | |
+ end | |
+ | |
def verify_credentials_require_user! | |
if !current_user | |
render json: { error: 'This method requires an authenticated user' }, status: 422 | |
@@ -67,5 +105,38 @@ | |
else | |
update_user_sign_in | |
end | |
+ end | |
+ | |
+ def create_tv_user | |
+ return if invalid_ios_version? && !current_account.tv_enabled? | |
+ | |
+ session_id = TvDeviceSession.find_by(oauth_access_token_id: doorkeeper_token.id)&.tv_session_id | |
+ tv_profile_id = TvAccount.find_by(account_id: current_account.id)&.p_profile_id | |
+ | |
+ return if session_id.present? && tv_profile_id.present? | |
+ | |
+ if tv_profile_id.nil? | |
+ TvAccountsCreateWorker.perform_async(current_account.id, doorkeeper_token.id) | |
+ else | |
+ TvAccountsLoginWorker.perform_async(current_account.id, doorkeeper_token.id) | |
+ end | |
+ end | |
+ | |
+ def enable_feature_flag | |
+ return unless required_android_version | |
+ ::Configuration::AccountEnabledFeature.upsert( | |
+ account_id: current_account.id, | |
+ feature_flag_id: 1, | |
+ ) | |
+ end | |
+ | |
+ def invalid_ios_version? | |
+ ios_version = request&.user_agent&.strip&.match(/^TruthSocial\/(\d+) .+/) || [] | |
+ ios_version[1].nil? || TV_REQUIRED_IOS_VERSION.zero? || ios_version[1].to_i < TV_REQUIRED_IOS_VERSION | |
+ end | |
+ | |
+ def required_android_version | |
+ android_version = request&.user_agent&.strip&.match(/^TruthSocialAndroid\/okhttp\/.+\/(\d+)/) || [] | |
+ !android_version[1].nil? && !TV_REQUIRED_ANDROID_VERSION.zero? && android_version[1].to_i == TV_REQUIRED_ANDROID_VERSION | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -14,6 +14,7 @@ | |
def set_account | |
@account = Account.find(params[:account_id]) | |
+ @pagination_enabled = (@account == current_account) | |
end | |
def load_accounts | |
@@ -29,30 +30,38 @@ | |
end | |
def default_accounts | |
- Account.includes(:active_relationships, :account_stat).references(:active_relationships) | |
+ Account.includes(:active_relationships, :account_follower, :account_following, :account_status).references(:active_relationships) | |
end | |
def paginated_follows | |
- Follow.where(target_account: @account).paginate_by_min_id( | |
- limit_param(DEFAULT_ACCOUNTS_LIMIT), | |
- params[:min_id], | |
- params[:max_id] | |
- ) | |
+ if @pagination_enabled | |
+ Follow.where(target_account: @account).paginate_by_max_id( | |
+ limit_param(DEFAULT_ACCOUNTS_LIMIT), | |
+ params[:max_id], | |
+ params[:since_id] | |
+ ) | |
+ else | |
+ Follow.where(target_account: @account).paginate_by_min_id( | |
+ limit_param(DEFAULT_ACCOUNTS_LIMIT), | |
+ nil, | |
+ nil | |
+ ) | |
+ end | |
end | |
def insert_pagination_headers | |
- set_pagination_headers(next_path, prev_path) | |
+ set_pagination_headers(next_path, prev_path) if @pagination_enabled | |
end | |
def next_path | |
if records_continue? | |
- api_v1_account_followers_url pagination_params(min_id: pagination_max_id) | |
+ api_v1_account_followers_url pagination_params(max_id: pagination_max_id) | |
end | |
end | |
def prev_path | |
unless @accounts.empty? | |
- api_v1_account_followers_url pagination_params(max_id: pagination_min_id) | |
+ api_v1_account_followers_url pagination_params(since_id: pagination_since_id) | |
end | |
end | |
@@ -60,7 +69,7 @@ | |
@accounts.last.active_relationships.first.id | |
end | |
- def pagination_min_id | |
+ def pagination_since_id | |
@accounts.first.active_relationships.first.id | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -29,7 +29,7 @@ | |
end | |
def default_accounts | |
- Account.includes(:passive_relationships, :account_stat).references(:passive_relationships) | |
+ Account.includes(:passive_relationships, :account_follower, :account_following, :account_status).references(:passive_relationships) | |
end | |
def paginated_follows | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/lookup_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/lookup_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/lookup_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/lookup_controller.rb 2024-04-01 14:59:13 | |
@@ -6,7 +6,7 @@ | |
before_action :require_authenticated_user!, unless: :allowed_public_access? | |
def show | |
- render json: @account, serializer: REST::AccountSerializer | |
+ render json: @account, serializer: REST::AccountSerializer, tv_account_lookup: true | |
end | |
private | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/relationships_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/relationships_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/relationships_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/relationships_controller.rb 2024-04-05 09:07:34 | |
@@ -6,16 +6,19 @@ | |
def index | |
accounts = Account.without_suspended.where(id: account_ids).select('id') | |
- # .where doesn't guarantee that our results are in the same order | |
- # we requested them, so return the "right" order to the requestor. | |
- @accounts = accounts.index_by(&:id).values_at(*account_ids).compact | |
- render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships | |
+ @accounts = accounts.index_by(&:id).values_at(*account_ids).compact # order results | |
+ render json: Panko::ArraySerializer.new( | |
+ @accounts, each_serializer: REST::V2::RelationshipSerializer, | |
+ context: { | |
+ relationships: relationships, | |
+ } | |
+ ).to_json | |
end | |
private | |
def relationships | |
- AccountRelationshipsPresenter.new(@accounts, current_user.account_id) | |
+ V2::AccountRelationshipsPresenter.new(@accounts, current_user.account_id) | |
end | |
def account_ids | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/search_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/search_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/search_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/search_controller.rb 2024-04-01 14:59:13 | |
@@ -3,10 +3,11 @@ | |
class Api::V1::Accounts::SearchController < Api::BaseController | |
before_action -> { doorkeeper_authorize! :read, :'read:accounts' } | |
before_action :require_user! | |
+ after_action :insert_pagination_headers, unless: -> { @accounts.empty? }, only: :show | |
def show | |
@accounts = account_search | |
- render json: @accounts, each_serializer: REST::AccountSerializer | |
+ render json: @accounts, each_serializer: REST::AccountSerializer, tv_account_lookup: true | |
end | |
private | |
@@ -18,7 +19,26 @@ | |
limit: limit_param(DEFAULT_ACCOUNTS_LIMIT), | |
resolve: truthy_param?(:resolve), | |
following: truthy_param?(:following), | |
+ followers: truthy_param?(:followers), | |
offset: params[:offset] | |
) | |
+ end | |
+ | |
+ def insert_pagination_headers | |
+ set_pagination_headers(next_path) | |
+ end | |
+ | |
+ def next_path | |
+ if records_continue? | |
+ api_v1_accounts_search_url pagination_params(offset: @accounts.size + params[:offset].to_i) | |
+ end | |
+ end | |
+ | |
+ def records_continue? | |
+ @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) | |
+ end | |
+ | |
+ def pagination_params(core_params) | |
+ params.slice(:limit, :followers, :q).permit(:limit, :followers, :q).merge(core_params) | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/statuses_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/statuses_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts/statuses_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts/statuses_controller.rb 2024-04-01 14:59:13 | |
@@ -8,7 +8,11 @@ | |
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } | |
def index | |
- @statuses = load_statuses | |
+ @statuses = load_statuses | |
+ if (ad_indexes = ENV.fetch('X_TRUTH_AD_INDEXES', nil)) | |
+ response.headers['x-truth-ad-indexes'] = ad_indexes | |
+ end | |
+ | |
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) | |
end | |
@@ -49,7 +53,7 @@ | |
def pinned_scope | |
return Status.none if @account.blocking?(current_account) | |
- @account.pinned_statuses | |
+ @account.pinned_statuses.merge!(StatusPin.profile_pins) | |
end | |
def no_replies_scope | |
diff -ru truth-old/opensource/app/controllers/api/v1/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -21,7 +21,7 @@ | |
end | |
def follow | |
- follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: false) | |
+ follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true) | |
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } } | |
export_prometheus_metric(:follows) | |
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options) | |
@@ -33,7 +33,7 @@ | |
end | |
def mute | |
- MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration]&.to_i || 0)) | |
+ MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: params.fetch(:duration, 0).to_i) | |
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships | |
end | |
@@ -80,7 +80,6 @@ | |
end | |
def export_prometheus_metric(metric_to_track) | |
- Prometheus::ApplicationExporter::increment(metric_to_track) | |
+ Prometheus::ApplicationExporter.increment(metric_to_track) | |
end | |
- | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/admin/account_actions_controller.rb truth-new/opensource/app/controllers/api/v1/admin/account_actions_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/admin/account_actions_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/admin/account_actions_controller.rb 2024-04-01 14:59:13 | |
@@ -11,7 +11,7 @@ | |
account_action.current_account = current_account | |
account_action.save! | |
- render_empty | |
+ render json: @account, serializer: REST::Admin::AccountSerializer | |
end | |
private | |
@@ -27,7 +27,8 @@ | |
:warning_preset_id, | |
:text, | |
:send_email_notification, | |
- :duration | |
+ :duration, | |
+ :feature_name | |
) | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1/admin/accounts: statuses_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin/accounts: webauthn_credentials_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/admin/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/admin/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/admin/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/admin/accounts_controller.rb 2024-04-05 09:07:34 | |
@@ -6,14 +6,21 @@ | |
LIMIT = 100 | |
+ before_action :set_log_level | |
+ | |
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show] | |
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show] | |
before_action :require_staff! | |
before_action :set_accounts, only: :index | |
before_action :set_account, except: [:index, :create, :bulk_approve] | |
+ before_action :set_policy, only: :create | |
before_action :require_local_account!, only: [:enable, :approve, :reject] | |
+ before_action :set_geo, only: [:create] | |
+ before_action :set_registrations, only: [:create], if: -> { params[:token].present? } | |
after_action :insert_pagination_headers, only: :index | |
+ after_action :registration_cleanup, only: :create, if: -> { @user.persisted? && @registration } | |
+ after_action :revert_log_level | |
FILTER_PARAMS = %i( | |
local | |
@@ -33,11 +40,25 @@ | |
sms | |
).freeze | |
+ GEO_PARAMS = %i( | |
+ country_name | |
+ country_code | |
+ city_name | |
+ region_code | |
+ region_name | |
+ ).freeze | |
+ | |
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze | |
def index | |
authorize :account, :index? | |
- render json: @accounts, each_serializer: REST::Admin::AccountSerializer | |
+ render json: Panko::ArraySerializer.new( | |
+ @accounts, | |
+ each_serializer: REST::V2::Admin::AccountSerializer, | |
+ context: { | |
+ advertisers: Account.recent_advertisers(@accounts.pluck(:id)), | |
+ } | |
+ ).to_json | |
end | |
def show | |
@@ -47,7 +68,7 @@ | |
def update | |
update_hash = update_params.to_h | |
- if ([email protected]_ready_for_approval? && [email protected]_by_csv_import?) | |
+ if [email protected]_ready_for_approval? && [email protected]_by_csv_import? | |
update_hash[:approved] = true | |
export_prometheus_metric(:approves) | |
end | |
@@ -61,12 +82,15 @@ | |
end | |
def create | |
+ Rails.logger.info("Sign-up logs: attempting to register: #{params[:email]}") | |
+ | |
account = Account.new( | |
username: params[:username], | |
- discoverable: params[:role] != 'moderator' | |
+ discoverable: params[:role] != 'moderator', | |
+ feeds_onboarded: true | |
) | |
- user = User.new( | |
+ @user = User.new( | |
email: params[:email], | |
password: params[:password], | |
sms: params[:sms], | |
@@ -75,19 +99,26 @@ | |
approved: ['true', true].include?(params[:approved]), | |
moderator: params[:role] == 'moderator', | |
confirmed_at: params[:confirmed] ? Time.now.utc : nil, | |
- bypass_invite_request_check: true | |
+ bypass_invite_request_check: true, | |
+ policy: @policy, | |
+ sign_up_city_id: @city, | |
+ sign_up_country_id: @country, | |
+ sign_up_ip: params[:sign_up_ip] | |
) | |
- user.account = account | |
+ @user.account = account | |
account.verify! if ['true', true].include?(params[:verified]) | |
- user.set_waitlist_position unless user.approved | |
+ @user.set_waitlist_position unless @user.approved | |
- if user.save | |
- send_registration_email(user) | |
+ if @user.save | |
+ send_registration_email | |
export_prometheus_metric(:registrations) | |
- render json: user.account, serializer: REST::Admin::AccountCreateSerializer | |
+ dispatch_rmq_event(account, params) | |
+ log_successful_attempt | |
+ render json: @user.account, serializer: REST::Admin::AccountCreateSerializer | |
else | |
- user_errors = user.errors.to_h | |
+ user_errors = @user.errors.to_h | |
+ log_failed_attempt(user_errors) | |
render json: { errors: user_errors }, status: 422 | |
end | |
end | |
@@ -126,13 +157,20 @@ | |
def reject | |
authorize @account.user, :reject? | |
- DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false) | |
+ DeleteAccountService.new.call( | |
+ @account, | |
+ @current_account.id, | |
+ deletion_type: 'api_admin_reject', | |
+ reserve_email: false, | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ ) | |
render json: @account, serializer: REST::Admin::AccountSerializer | |
end | |
def destroy | |
authorize @account, :destroy? | |
- Admin::AccountDeletionWorker.perform_async(@account.id) | |
+ Admin::AccountDeletionWorker.perform_async(@account.id, @current_account.id) | |
render json: @account, serializer: REST::Admin::AccountSerializer | |
end | |
@@ -231,14 +269,87 @@ | |
end | |
def export_prometheus_metric(metric_type) | |
- Prometheus::ApplicationExporter::increment(metric_type) | |
+ Prometheus::ApplicationExporter.increment(metric_type) | |
end | |
- def send_registration_email(user) | |
- if user.approved? | |
- NotificationMailer.user_approved(user.account).deliver_later | |
+ def send_registration_email | |
+ if @user.approved? | |
+ NotificationMailer.user_approved(@user.account).deliver_later | |
else | |
- UserMailer.waitlisted(user).deliver_later | |
+ UserMailer.waitlisted(@user).deliver_later | |
end | |
+ end | |
+ | |
+ def set_policy | |
+ @policy = Policy.last | |
+ end | |
+ | |
+ def geo_params | |
+ params.permit(*GEO_PARAMS) | |
+ end | |
+ | |
+ def set_geo | |
+ geo = GeoService.new( | |
+ city_name: geo_params[:city_name], | |
+ country_code: geo_params[:country_code], | |
+ country_name: geo_params[:country_name], | |
+ region_name: geo_params[:region_name], | |
+ region_code: geo_params[:region_code] | |
+ ) | |
+ | |
+ @city = geo.city | |
+ @country = geo.country | |
+ end | |
+ | |
+ def set_log_level | |
+ @current_log_level = Rails.logger.level | |
+ Rails.logger.level = :debug | |
+ end | |
+ | |
+ def revert_log_level | |
+ Rails.logger.level = @current_log_level | |
+ end | |
+ | |
+ def set_registrations | |
+ @registration = Registration.find_by(token: params[:token]) | |
+ if @registration&.ios_device? | |
+ @registration_credential = @registration.registration_webauthn_credential | |
+ @credential = @registration_credential&.webauthn_credential | |
+ credential_error = 'Webauthn Credential is already associated with an account' | |
+ render json: { errors: credential_error }, status: 422 and return if @credential&.user.present? | |
+ end | |
+ end | |
+ | |
+ def registration_cleanup | |
+ if @registration.ios_device? | |
+ @credential&.update!(user: @user) # Do we want to fail loudly? | |
+ else | |
+ user_params = { user_id: @user.id } | |
+ verification = DeviceVerification.find_by("details ->> 'registration_token' = '#{ActiveRecord::Base.sanitize_sql(@registration.token)}'") | |
+ details = verification.details | |
+ new_details = details.merge(user_params) | |
+ verification.update(details: new_details) | |
+ end | |
+ | |
+ registration_otc = @registration.registration_one_time_challenge | |
+ otc = registration_otc.one_time_challenge | |
+ otc.destroy # This will also cascade delete the RegistrationOneTimeChallenge record. | |
+ @registration.destroy | |
+ end | |
+ | |
+ def dispatch_rmq_event(account, params) | |
+ EventProvider::EventProvider.new('account.created', AccountCreatedEvent, account, params).call | |
+ end | |
+ | |
+ def log_failed_attempt(errors) | |
+ filters = Rails.application.config.filter_parameters | |
+ f = ActiveSupport::ParameterFilter.new filters | |
+ filtered_params = f.filter params | |
+ | |
+ Rails.logger.info("Sign-up logs: unsuccessful registration: #{errors}. params: #{filtered_params}") | |
+ end | |
+ | |
+ def log_successful_attempt | |
+ Rails.logger.info("Sign-up logs: successful registration: #{params[:email]}") | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1/admin: bulk_account_actions_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: chat_messages_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: groups | |
Only in truth-new/opensource/app/controllers/api/v1/admin: groups_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: links_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: policies_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: registrations_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/admin/statuses_controller.rb truth-new/opensource/app/controllers/api/v1/admin/statuses_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/admin/statuses_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/admin/statuses_controller.rb 2024-04-12 16:08:26 | |
@@ -4,8 +4,11 @@ | |
include Authorization | |
include AccountableConcern | |
+ before_action :set_log_level | |
before_action -> { doorkeeper_authorize! :'admin:write' } | |
+ before_action :require_staff! | |
before_action -> { set_status }, except: :index | |
+ after_action :revert_log_level | |
def index | |
@statuses = Status.with_discarded.where(id: params[:ids]) | |
@@ -42,16 +45,27 @@ | |
@status.reblogs.update_all(deleted_at: Time.current, deleted_by_id: resource_params[:moderator_id]) | |
@status.update!(deleted_at: Time.current, deleted_by_id: resource_params[:moderator_id]) | |
RemovalWorker.perform_async(@status.id, redraft: true, notify_user: resource_params[:notify_user], immediate: false) | |
- @status.account.statuses_count = @status.account.statuses_count - 1 | |
- @status.account.save | |
invalidate_cache | |
render json: @status, serializer: REST::Admin::StatusSerializer, source_requested: true | |
end | |
+ def privatize | |
+ @status.privatize(resource_params[:moderator_id], resource_params[:notify_user]) | |
+ invalidate_cache | |
+ render json: @status, serializer: REST::Admin::StatusSerializer, source_requested: true | |
+ end | |
+ | |
+ def publicize | |
+ @status.publicize | |
+ invalidate_cache | |
+ render json: @status, serializer: REST::Admin::StatusSerializer, source_requested: true | |
+ end | |
+ | |
private | |
def set_status | |
@status = Status.with_discarded.find(params[:id] || params[:status_id]) | |
+ @status.performed_by_admin = true | |
end | |
def resource_params | |
@@ -63,4 +77,15 @@ | |
InvalidateSecondaryCacheService.new.call("InvalidateStatusCacheWorker", @status.id) | |
end | |
+ def set_log_level | |
+ return unless request.request_method == 'POST' | |
+ Rails.logger.info("Admin::StatusesController logs: #{params.inspect}") | |
+ @current_log_level = Rails.logger.level | |
+ Rails.logger.level = :debug | |
+ end | |
+ | |
+ def revert_log_level | |
+ return unless request.request_method == 'POST' | |
+ Rails.logger.level = @current_log_level | |
+ end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1/admin: tags_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: trending_groups_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: trending_statuses | |
diff -ru truth-old/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb truth-new/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb 2024-04-01 14:59:13 | |
@@ -1,21 +1,23 @@ | |
# frozen_string_literal: true | |
class Api::V1::Admin::TrendingStatusesController < Api::BaseController | |
+ TRENDING_STATUS_LIMIT = 10 | |
+ | |
before_action -> { doorkeeper_authorize! :'admin:write' } | |
before_action :require_staff! | |
before_action :set_statuses, only: :index | |
- before_action :set_status, only: [:update, :destroy] | |
+ before_action :set_status, only: [:include, :exclude] | |
after_action :set_pagination_headers, only: :index | |
def index | |
render json: @statuses, each_serializer: REST::Admin::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) | |
end | |
- def update | |
- mark_trending | |
+ def include | |
+ render json: TrendingStatusExcludedStatus.destroy(@status.id) | |
end | |
- def destroy | |
- remove_from_trending | |
+ def exclude | |
+ render json: TrendingStatusExcludedStatus.create(status_id: @status.id) | |
end | |
private | |
@@ -24,52 +26,14 @@ | |
@status = Status.find(params[:id]) | |
end | |
- def mark_trending | |
- trending = Trending.where(status: @status).first_or_initialize | |
- trending.user = current_user if trending.new_record? | |
- trending.save! | |
- end | |
- | |
- def remove_from_trending | |
- trending = Trending.find_by(status_id: @status.id) | |
- trending.delete if trending.present? | |
- end | |
- | |
def set_statuses | |
- @statuses = if params[:trending] | |
- Trending.includes(:status).all.flat_map(&:status) | |
- else | |
- fetch_recent_statuses | |
- end | |
+ @statuses = Status.trending_statuses.page(params[:page]).per(TRENDING_STATUS_LIMIT) | |
end | |
- def fetch_recent_statuses | |
- Rails.cache.fetch("admin:trending_statuses:#{params[:page]}", expires_in: 10.minutes) do | |
- Status | |
- .where(created_at: 1.day.ago.beginning_of_day..Time.now) | |
- .joins(:status_stat) | |
- .includes(:preview_cards, :status_stat, account: :account_stat) | |
- .order("status_stats.favourites_count desc") | |
- .reorder('') | |
- .page(params[:page]) | |
- .per(10) | |
- .to_a | |
- end | |
- end | |
- | |
def set_pagination_headers | |
- response.headers["x-page-size"] = 10 | |
- response.headers["x-page"] = params[:page] || 1 | |
- response.headers["x-total"] = if @statuses.is_a?(Array) | |
- @statuses.size | |
- else | |
- @statuses.total_count | |
- end | |
- | |
- response.headers["x-total-pages"] = if @statuses.is_a?(Array) | |
- @statuses.size / 10 | |
- else | |
- @statuses.total_pages | |
- end | |
+ response.headers['x-page-size'] = TRENDING_STATUS_LIMIT | |
+ response.headers['x-page'] = params[:page] || 1 | |
+ response.headers['x-total'] = @statuses.size | |
+ response.headers['x-total-pages'] = @statuses.total_pages | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1/admin: trending_tags_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/admin: truth | |
Only in truth-new/opensource/app/controllers/api/v1/admin: tv | |
diff -ru truth-old/opensource/app/controllers/api/v1/blocks_controller.rb truth-new/opensource/app/controllers/api/v1/blocks_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/blocks_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/blocks_controller.rb 2024-04-01 14:59:13 | |
@@ -17,7 +17,7 @@ | |
end | |
def paginated_blocks | |
- @paginated_blocks ||= Block.eager_load(target_account: :account_stat) | |
+ @paginated_blocks ||= Block.eager_load(target_account: [:account_follower, :account_following, :account_status]) | |
.joins(:target_account) | |
.merge(Account.without_suspended) | |
.where(account: current_account) | |
diff -ru truth-old/opensource/app/controllers/api/v1/endorsements_controller.rb truth-new/opensource/app/controllers/api/v1/endorsements_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/endorsements_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/endorsements_controller.rb 2024-04-01 14:59:13 | |
@@ -25,7 +25,7 @@ | |
end | |
def endorsed_accounts | |
- current_account.endorsed_accounts.includes(:account_stat).without_suspended | |
+ current_account.endorsed_accounts.includes(:account_follower, :account_following, :account_status).without_suspended | |
end | |
def insert_pagination_headers | |
diff -ru truth-old/opensource/app/controllers/api/v1/favourites_controller.rb truth-new/opensource/app/controllers/api/v1/favourites_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/favourites_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/favourites_controller.rb 2023-05-05 13:42:02 | |
@@ -21,7 +21,7 @@ | |
end | |
def results | |
- @_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id( | |
+ @_results ||= account_favourites.includes(:status).joins(:status).to_a_paginated_by_id( | |
limit_param(DEFAULT_STATUSES_LIMIT), | |
params_slice(:max_id, :since_id, :min_id) | |
) | |
Only in truth-new/opensource/app/controllers/api/v1: feeds_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/follow_requests_controller.rb truth-new/opensource/app/controllers/api/v1/follow_requests_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/follow_requests_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/follow_requests_controller.rb 2024-04-01 14:59:13 | |
@@ -37,7 +37,7 @@ | |
end | |
def default_accounts | |
- Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests) | |
+ Account.without_suspended.includes(:follow_requests, :account_follower, :account_following, :account_status).references(:follow_requests) | |
end | |
def paginated_follow_requests | |
Only in truth-new/opensource/app/controllers/api/v1: groups | |
Only in truth-new/opensource/app/controllers/api/v1: groups_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/lists/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/lists/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/lists/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/lists/accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -37,9 +37,9 @@ | |
def load_accounts | |
if unlimited? | |
- @list.accounts.without_suspended.includes(:account_stat).all | |
+ @list.accounts.without_suspended.includes(:account_follower, :account_following, :account_status).all | |
else | |
- @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) | |
+ @list.accounts.without_suspended.includes(:account_follower, :account_following, :account_status).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) | |
end | |
end | |
Only in truth-old/opensource/app/controllers/api/v1: ma1sd | |
diff -ru truth-old/opensource/app/controllers/api/v1/media_controller.rb truth-new/opensource/app/controllers/api/v1/media_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/media_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/media_controller.rb 2024-04-01 14:59:13 | |
@@ -28,7 +28,8 @@ | |
private | |
def status_code_for_media_attachment | |
- @media_attachment.not_processed? ? 206 : 200 | |
+ # @media_attachment.not_processed? ? 206 : 200 | |
+ 200 | |
end | |
def set_media_attachment | |
diff -ru truth-old/opensource/app/controllers/api/v1/notifications_controller.rb truth-new/opensource/app/controllers/api/v1/notifications_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/notifications_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/notifications_controller.rb 2024-04-01 14:59:13 | |
@@ -10,12 +10,12 @@ | |
def index | |
@notifications = load_notifications | |
- render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) | |
+ render json: @notifications, each_serializer: REST::NotificationSerializer, current_user: current_user, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) | |
end | |
def show | |
@notification = current_account.notifications.without_suspended.find(params[:id]) | |
- render json: @notification, serializer: REST::NotificationSerializer | |
+ render json: @notification, serializer: REST::NotificationSerializer, current_user: current_user | |
end | |
def clear | |
@@ -31,17 +31,23 @@ | |
private | |
def load_notifications | |
- notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id( | |
+ notifications = browserable_account_notifications.includes(from_account: [:account_follower, :account_following, :account_status]).to_a_paginated_by_id( | |
limit_param(DEFAULT_NOTIFICATIONS_LIMIT), | |
params_slice(:max_id, :since_id, :min_id) | |
) | |
- Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses| | |
+ notification_with_statuses = Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses| | |
cache_collection(target_statuses, Status) | |
end | |
+ | |
+ Notification.exclude_self_statuses(notification_with_statuses) | |
end | |
def browserable_account_notifications | |
- current_account.notifications.without_suspended.browserable(exclude_types, from_account) | |
+ current_account.notifications.without_suspended.browserable( | |
+ types: Array(browserable_params[:types]), | |
+ exclude_types: Array(browserable_params[:exclude_types]), | |
+ from_account_id: browserable_params[:account_id] | |
+ ) | |
end | |
def target_statuses_from_notifications | |
@@ -53,7 +59,7 @@ | |
end | |
def next_path | |
- unless @notifications.empty? | |
+ if records_continue? | |
api_v1_notifications_url pagination_params(max_id: pagination_max_id) | |
end | |
end | |
@@ -72,19 +78,15 @@ | |
@notifications.first.id | |
end | |
- def exclude_types | |
- val = params.permit(exclude_types: [])[:exclude_types] || [] | |
- val = [val] unless val.is_a?(Enumerable) | |
- val_with_groups = val.clone | |
- val.each { |n| val_with_groups << "#{n}_group"} | |
- val_with_groups | |
+ def browserable_params | |
+ params.permit(:account_id, types: [], exclude_types: []) | |
end | |
- def from_account | |
- params[:account_id] | |
+ def pagination_params(core_params) | |
+ params.slice(:limit, :account_id, :types, :exclude_types).permit(:limit, :account_id, types: [], exclude_types: []).merge(core_params) | |
end | |
- def pagination_params(core_params) | |
- params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params) | |
+ def records_continue? | |
+ @notifications.size == limit_param(DEFAULT_NOTIFICATIONS_LIMIT) | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1: pleroma | |
diff -ru truth-old/opensource/app/controllers/api/v1/polls/votes_controller.rb truth-new/opensource/app/controllers/api/v1/polls/votes_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/polls/votes_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/polls/votes_controller.rb 2024-04-01 14:59:13 | |
@@ -9,16 +9,28 @@ | |
def create | |
VoteService.new.call(current_account, @poll, vote_params[:choices]) | |
- render json: @poll, serializer: REST::PollSerializer | |
+ update_optimistic_data | |
+ render json: REST::V2::PollSerializer.new(context: { optimistic_data: @optimistic_data, current_user: current_user }).serialize(@poll) | |
end | |
private | |
def set_poll | |
- @poll = Poll.attached.find(params[:poll_id]) | |
+ @poll = Poll.find(params[:poll_id]) | |
+ @optimistic_data = { votes_count: @poll.votes_count.to_i, voters_count: @poll.voters_count.to_i, own_votes: @poll.own_votes(current_account), options: @poll.loaded_options } | |
authorize @poll.status, :show? | |
rescue Mastodon::NotPermittedError | |
not_found | |
+ end | |
+ | |
+ def update_optimistic_data | |
+ @optimistic_data[:voters_count] += 1 | |
+ vote_params[:choices].each do |choice| | |
+ @optimistic_data[:votes_count] += 1 | |
+ @optimistic_data[:own_votes].push(choice.to_i) unless @optimistic_data[:own_votes].include?(choice.to_i) | |
+ @optimistic_data[:options][choice.to_i][:votes_count] += 1 if @optimistic_data[:options][choice.to_i] | |
+ end | |
+ @optimistic_data[:voted] = true | |
end | |
def vote_params | |
diff -ru truth-old/opensource/app/controllers/api/v1/polls_controller.rb truth-new/opensource/app/controllers/api/v1/polls_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/polls_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/polls_controller.rb 2024-04-01 14:59:13 | |
@@ -5,7 +5,6 @@ | |
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, only: :show | |
before_action :set_poll | |
- before_action :refresh_poll | |
def show | |
render json: @poll, serializer: REST::PollSerializer, include_results: true | |
@@ -14,13 +13,9 @@ | |
private | |
def set_poll | |
- @poll = Poll.attached.find(params[:id]) | |
+ @poll = Poll.find(params[:id]) | |
authorize @poll.status, :show? | |
rescue Mastodon::NotPermittedError | |
not_found | |
- end | |
- | |
- def refresh_poll | |
- ActivityPub::FetchRemotePollService.new.call(@poll, current_account) if user_signed_in? && @poll.possibly_stale? | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/push/subscriptions_controller.rb truth-new/opensource/app/controllers/api/v1/push/subscriptions_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/push/subscriptions_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/push/subscriptions_controller.rb 2024-04-01 14:59:13 | |
@@ -1,29 +1,24 @@ | |
# frozen_string_literal: true | |
class Api::V1::Push::SubscriptionsController < Api::BaseController | |
+ | |
before_action -> { doorkeeper_authorize! :push } | |
before_action -> { require_user!(requires_approval: false) } | |
+ before_action :set_log_level | |
+ | |
before_action :set_push_subscription | |
- before_action :check_push_subscription, only: [:show, :update] | |
+ before_action :check_push_subscription, only: [:show] | |
+ before_action :create_new_record, only: [:update] | |
skip_before_action :require_functional!, only: [:create] | |
+ after_action :revert_log_level | |
+ DEBUG_ACCOUNT_ID = ENV.fetch('DEBUG_ACCOUNT_ID', 0).to_i | |
+ | |
def create | |
@push_subscription&.destroy! | |
remove_old_subscriptions_for_device! | |
- | |
- @push_subscription = Web::PushSubscription.create!( | |
- endpoint: subscription_params[:endpoint], | |
- device_token: subscription_params[:device_token], | |
- platform: subscription_params[:platform] || 0, | |
- environment: subscription_params[:environment] || 0, | |
- key_p256dh: subscription_params.dig(:keys, :p256dh), | |
- key_auth: subscription_params.dig(:keys, :auth), | |
- data: data_params, | |
- user_id: current_user.id, | |
- access_token_id: doorkeeper_token.id | |
- ) | |
- | |
+ @push_subscription = create_push_subscription | |
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer | |
end | |
@@ -32,7 +27,7 @@ | |
end | |
def update | |
- @push_subscription.update!(data: data_params) | |
+ @push_subscription.update!(data: data_params, device_token: subscription_params[:device_token] || nil) | |
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer | |
end | |
@@ -43,6 +38,20 @@ | |
private | |
+ def create_push_subscription | |
+ Web::PushSubscription.create!( | |
+ endpoint: subscription_params[:endpoint], | |
+ device_token: subscription_params[:device_token], | |
+ platform: subscription_params[:platform] || 0, | |
+ environment: subscription_params[:environment] || 0, | |
+ key_p256dh: subscription_params.dig(:keys, :p256dh), | |
+ key_auth: subscription_params.dig(:keys, :auth), | |
+ data: data_params, | |
+ user_id: current_user.id, | |
+ access_token_id: doorkeeper_token.id | |
+ ) | |
+ end | |
+ | |
def set_push_subscription | |
@push_subscription = Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) | |
end | |
@@ -51,6 +60,12 @@ | |
not_found if @push_subscription.nil? | |
end | |
+ def create_new_record | |
+ return if @push_subscription | |
+ remove_old_subscriptions_for_device! | |
+ @push_subscription = create_push_subscription | |
+ end | |
+ | |
def subscription_params | |
params.require(:subscription).permit(:endpoint, :device_token, :platform, :environment, keys: [:auth, :p256dh]) | |
end | |
@@ -58,10 +73,34 @@ | |
def data_params | |
return {} if params[:data].blank? | |
- params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status, :user_approved, :verify_sms_prompt]) | |
+ params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status, :user_approved, :verify_sms_prompt, :chat, :group_favourite, :group_reblog, :group_mention, :group_approval, :group_delete, :group_role, :group_request, :group_promoted, :group_demoted]) | |
end | |
def remove_old_subscriptions_for_device! | |
- Web::PushSubscription.destroy_by(device_token: subscription_params[:device_token]) if subscription_params[:platform].to_i > 0 && subscription_params[:device_token].present? | |
+ if subscription_params[:platform].to_i > 0 && subscription_params[:device_token].present? | |
+ duplicate_subs = Web::PushSubscription | |
+ .where("access_token_id != ?", doorkeeper_token.id) | |
+ .where( | |
+ device_token: subscription_params[:device_token], | |
+ platform: subscription_params[:platform], | |
+ user_id: current_user.id | |
+ ) | |
+ | |
+ duplicate_subs.destroy_all | |
+ end | |
end | |
+ | |
+ | |
+ def set_log_level | |
+ return unless current_account.id == DEBUG_ACCOUNT_ID | |
+ Rails.logger.info("Subscription logs: #{params.inspect}") | |
+ @current_log_level = Rails.logger.level | |
+ Rails.logger.level = :debug | |
+ end | |
+ | |
+ def revert_log_level | |
+ return unless current_account.id == DEBUG_ACCOUNT_ID | |
+ Rails.logger.level = @current_log_level || :info | |
+ end | |
+ | |
end | |
Only in truth-new/opensource/app/controllers/api/v1: push_notifications | |
Only in truth-new/opensource/app/controllers/api/v1: recommendations | |
diff -ru truth-old/opensource/app/controllers/api/v1/reports_controller.rb truth-new/opensource/app/controllers/api/v1/reports_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/reports_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/reports_controller.rb 2024-04-12 16:08:26 | |
@@ -1,8 +1,13 @@ | |
# frozen_string_literal: true | |
class Api::V1::ReportsController < Api::BaseController | |
+ include Authorization | |
+ | |
before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create] | |
before_action :require_user! | |
+ before_action :set_group, only: [:create], if: -> { report_params[:group_id] } | |
+ before_action :set_external_ad, only: [:create], if: -> { report_params[:external_ad_media_url] } | |
+ before_action :check_for_existing_report, only: [:create] | |
override_rate_limit_headers :create, family: :reports | |
@@ -13,7 +18,10 @@ | |
status_ids: reported_status_ids, | |
comment: report_params[:comment], | |
forward: report_params[:forward], | |
- rule_ids: reported_rule_ids | |
+ rule_ids: reported_rule_ids, | |
+ message_ids: reported_message_ids, | |
+ group_id: group_id, | |
+ external_ad_id: external_ad_id | |
) | |
render json: @report, serializer: REST::ReportSerializer | |
@@ -22,13 +30,34 @@ | |
private | |
def reported_status_ids | |
- reported_account.statuses.with_discarded.find(status_ids).pluck(:id) | |
+ return unless @group.nil? && @external_ad.nil? | |
+ find_statuses.pluck(:id) | |
end | |
+ def group_id | |
+ return @group.id if @group | |
+ | |
+ group = Group.find_by(id: find_statuses.pick(:group_id)) | |
+ authorize group, :show? if group | |
+ group&.id | |
+ end | |
+ | |
+ def external_ad_id | |
+ return @external_ad.id if @external_ad | |
+ end | |
+ | |
+ def find_statuses | |
+ @statuses ||= reported_account.statuses.with_discarded.find(status_ids) | |
+ end | |
+ | |
def reported_rule_ids | |
Rule.find(rule_ids).pluck(:id) | |
end | |
+ def reported_message_ids | |
+ ChatMessage.visible_messages(report_params[:account_id].to_i, "{#{message_ids.map(&:to_i).join(',')}}") | |
+ end | |
+ | |
def status_ids | |
Array(report_params[:status_ids]) | |
end | |
@@ -37,11 +66,61 @@ | |
Array(report_params[:rule_ids]) | |
end | |
+ def message_ids | |
+ Array(report_params[:message_ids]) | |
+ end | |
+ | |
def reported_account | |
- Account.find(report_params[:account_id]) | |
+ if report_params[:group_id] | |
+ GroupMembership.find_by!(group_id: report_params[:group_id], role: 'owner').account | |
+ elsif @external_ad | |
+ Account.find(ENV.fetch('TS_ADVERTISTING_ACCOUNT_ID', nil)) | |
+ else | |
+ Account.find(report_params[:account_id]) | |
+ end | |
end | |
def report_params | |
- params.permit(:account_id, :comment, :forward, status_ids: [], rule_ids: []) | |
+ params.permit(:account_id, :comment, :forward, :group_id, :external_ad_url, :external_ad_media_url, :external_ad_description, status_ids: [], rule_ids: [], message_ids: []) | |
end | |
+ | |
+ def set_group | |
+ @group = Group.find(report_params[:group_id]) | |
+ end | |
+ | |
+ def set_external_ad | |
+ @external_ad = ExternalAd.find_or_create_by(media_url: report_params[:external_ad_media_url], description: report_params[:external_ad_description]) do |ad| | |
+ ad.ad_url = report_params[:external_ad_url] | |
+ end | |
+ end | |
+ | |
+ def check_for_existing_report | |
+ existing_reports = | |
+ if @group | |
+ current_account.reports.where(target_account: reported_account, group_id: group_id, status_ids: []) | |
+ elsif @external_ad | |
+ current_account.reports.where(target_account: reported_account, external_ad_id: external_ad_id, status_ids: []) | |
+ else | |
+ current_account.reports.where(target_account: reported_account, status_ids: reported_status_ids, message_ids: reported_message_ids) | |
+ end | |
+ | |
+ entity = | |
+ if status_ids.any? | |
+ 'Truth' | |
+ elsif message_ids.any? | |
+ 'message' | |
+ elsif group_id | |
+ 'group' | |
+ elsif external_ad_id | |
+ 'ad' | |
+ else | |
+ 'user' | |
+ end | |
+ | |
+ e = "Thanks, but you have already reported this #{entity}." | |
+ | |
+ render json: { error: e }, status: 422 if existing_reports.any? | |
+ end | |
end | |
+ | |
+ | |
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -17,20 +17,23 @@ | |
def load_accounts | |
scope = default_accounts | |
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? | |
- scope.merge(paginated_favourites).to_a | |
+ scope = scope.merge(paginated_favourites).to_a | |
+ @size = scope.size | |
+ | |
+ @size > limit_param(DEFAULT_ACCOUNTS_LIMIT) ? scope.drop(1) : scope | |
end | |
def default_accounts | |
Account | |
.without_suspended | |
- .includes(:favourites, :account_stat) | |
+ .includes(:favourites, :account_follower, :account_following, :account_status) | |
.references(:favourites) | |
.where(favourites: { status_id: @status.id }) | |
end | |
def paginated_favourites | |
Favourite.paginate_by_max_id( | |
- limit_param(DEFAULT_ACCOUNTS_LIMIT), | |
+ limit_param(DEFAULT_ACCOUNTS_LIMIT) + 1, | |
params[:max_id], | |
params[:since_id] | |
) | |
@@ -61,7 +64,7 @@ | |
end | |
def records_continue? | |
- @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) | |
+ @size > limit_param(DEFAULT_ACCOUNTS_LIMIT) | |
end | |
def set_status | |
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/favourites_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/favourites_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/statuses/favourites_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/statuses/favourites_controller.rb 2024-04-01 14:59:13 | |
@@ -2,16 +2,22 @@ | |
class Api::V1::Statuses::FavouritesController < Api::BaseController | |
include Authorization | |
+ include Divergable | |
before_action -> { doorkeeper_authorize! :write, :'write:favourites' } | |
before_action :require_user! | |
+ before_action :diverge_users_without_current_ip, only: [:create] | |
before_action :set_status, only: [:create] | |
+ after_action :create_device_verification_favourite, only: :create | |
+ include Assertable | |
+ | |
def create | |
cached_status = cache_collection([@status], Status).first | |
- FavouriteService.new.call(current_account, @status) | |
- cached_status.status_stat.favourites_count = cached_status.favourites_count + 1 | |
- render json: cached_status, serializer: REST::StatusSerializer, replica_reads: ['reblogged', 'muted', 'bookmarked'] | |
+ @favourite = FavouriteService.new.call(current_account, @status, user_agent: request.user_agent) | |
+ cached_status.status_favourite || cached_status.build_status_favourite | |
+ cached_status.status_favourite.favourites_count = cached_status.favourites_count + 1 | |
+ render json: cached_status, serializer: REST::StatusSerializer, replica_reads: ['reblogged', 'muted', 'bookmarked'], relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => true }) | |
end | |
def destroy | |
@@ -19,13 +25,18 @@ | |
if fav | |
@status = fav.status | |
+ cached_status = cache_collection([@status], Status).first | |
+ cached_status.status_favourite || cached_status.build_status_favourite | |
+ cached_status.status_favourite.favourites_count = cached_status.favourites_count - 1 | |
+ | |
UnfavouriteWorker.perform_async(current_account.id, @status.id) | |
else | |
@status = Status.find(params[:status_id]) | |
+ cached_status = @status | |
authorize @status, :show? | |
end | |
- render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }) | |
+ render json: cached_status, serializer: REST::StatusSerializer, replica_reads: ['reblogged', 'muted', 'bookmarked'], relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }) | |
rescue Mastodon::NotPermittedError | |
not_found | |
end | |
@@ -37,5 +48,25 @@ | |
authorize @status, :show? | |
rescue Mastodon::NotPermittedError | |
not_found | |
+ end | |
+ | |
+ def validate_client | |
+ action_assertable? | |
+ end | |
+ | |
+ def asserting? | |
+ request.headers['x-tru-assertion'] && action_assertable? | |
+ end | |
+ | |
+ def action_assertable? | |
+ %w(create).include?(action_name) ? true : false | |
+ end | |
+ | |
+ def log_android_activity? | |
+ current_user.user_sms_reverification_required && action_assertable? | |
+ end | |
+ | |
+ def create_device_verification_favourite | |
+ DeviceVerificationFavourite.insert(verification_id: @device_verification.id, favourite_id: @favourite.id) if @device_verification && @favourite | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/mutes_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/mutes_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/statuses/mutes_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/statuses/mutes_controller.rb 2024-04-01 14:59:13 | |
@@ -3,11 +3,28 @@ | |
class Api::V1::Statuses::MutesController < Api::BaseController | |
include Authorization | |
- before_action -> { doorkeeper_authorize! :write, :'write:mutes' } | |
+ before_action -> { doorkeeper_authorize! :write, :'write:mutes' }, only: [:create, :destroy] | |
+ before_action -> { doorkeeper_authorize! :write, :'read:mutes' }, only: :index | |
before_action :require_user! | |
- before_action :set_status | |
- before_action :set_conversation | |
+ before_action :set_status, only: [:create, :destroy] | |
+ before_action :set_conversation, only: [:create, :destroy] | |
+ after_action :insert_pagination_headers, only: :index | |
+ MUTED_CONVERSATIONS_LIMIT = 20 | |
+ | |
+ def index | |
+ @statuses = load_muted_conversations | |
+ | |
+ render json: Panko::ArraySerializer.new( | |
+ @statuses, | |
+ each_serializer: REST::V2::StatusSerializer, | |
+ context: { | |
+ current_user: current_user, | |
+ relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), | |
+ } | |
+ ).to_json | |
+ end | |
+ | |
def create | |
current_account.mute_conversation!(@conversation) | |
@mutes_map = { @conversation.id => true } | |
@@ -34,5 +51,36 @@ | |
def set_conversation | |
@conversation = @status.conversation | |
raise Mastodon::ValidationError if @conversation.nil? | |
+ end | |
+ | |
+ def load_muted_conversations | |
+ scope = paginated_conversations | |
+ @size = scope.size | |
+ @size > limit_param(MUTED_CONVERSATIONS_LIMIT) ? scope.take(limit_param(MUTED_CONVERSATIONS_LIMIT)) : scope | |
+ end | |
+ | |
+ def paginated_conversations | |
+ Status.muted_conversations_for_account(current_account.id).paginate_by_limit_offset( | |
+ limit_param(MUTED_CONVERSATIONS_LIMIT) + 1, | |
+ params_slice(:offset) | |
+ ) | |
+ end | |
+ | |
+ def insert_pagination_headers | |
+ set_pagination_headers(next_path) | |
+ end | |
+ | |
+ def next_path | |
+ return unless records_continue? | |
+ | |
+ api_v1_mutes_url pagination_params(offset: @statuses.size + params[:offset].to_i) | |
+ end | |
+ | |
+ def records_continue? | |
+ @size > limit_param(MUTED_CONVERSATIONS_LIMIT) | |
+ end | |
+ | |
+ def pagination_params(core_params) | |
+ params.slice(:limit).permit(:limit).merge(core_params) | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb 2024-04-01 14:59:13 | |
@@ -21,11 +21,11 @@ | |
end | |
def default_accounts | |
- Account.without_suspended.includes(:statuses, :account_stat).references(:statuses) | |
+ Account.without_suspended.includes(:statuses, :account_follower, :account_following, :account_status).references(:statuses) | |
end | |
def paginated_statuses | |
- Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id( | |
+ Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :group]).paginate_by_max_id( | |
limit_param(DEFAULT_ACCOUNTS_LIMIT), | |
params[:max_id], | |
params[:since_id] | |
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb 2024-04-01 14:59:13 | |
@@ -2,16 +2,23 @@ | |
class Api::V1::Statuses::ReblogsController < Api::BaseController | |
include Authorization | |
+ include Divergable | |
before_action -> { doorkeeper_authorize! :write, :'write:statuses' } | |
before_action :require_user! | |
+ before_action :diverge_users_without_current_ip, only: [:create] | |
before_action :set_reblog, only: [:create] | |
+ after_action :create_device_verification_status, only: :create | |
+ include Assertable | |
+ | |
override_rate_limit_headers :create, family: :statuses | |
def create | |
@status = ReblogService.new.call(current_account, @reblog, reblog_params) | |
+ @status.reblog.status_reblog || @status.reblog.build_status_reblog | |
+ @status.reblog.status_reblog.reblogs_count = @status.reblog.reblogs_count + 1 | |
render json: @status, serializer: REST::StatusSerializer | |
end | |
@@ -21,8 +28,9 @@ | |
if @status | |
authorize @status, :unreblog? | |
@status.discard | |
- RemovalWorker.perform_async(@status.id, immediate: true) | |
+ ReblogRemovalWorker.perform_async(@status.id, immediate: true) | |
@reblog = @status.reblog | |
+ InteractionsTracker.new(current_account.id, @reblog.account_id, :reblog, current_account.following?(@reblog.account_id), @reblog.group).untrack | |
else | |
@reblog = Status.find(params[:status_id]) | |
authorize @reblog, :show? | |
@@ -43,6 +51,26 @@ | |
end | |
def reblog_params | |
- params.permit(:visibility) | |
+ params.permit(:visibility).merge(user_agent: request.user_agent) | |
+ end | |
+ | |
+ def validate_client | |
+ action_assertable? | |
+ end | |
+ | |
+ def asserting? | |
+ request.headers['x-tru-assertion'] && action_assertable? | |
+ end | |
+ | |
+ def action_assertable? | |
+ %w(create).include?(action_name) ? true : false | |
+ end | |
+ | |
+ def log_android_activity? | |
+ current_user.user_sms_reverification_required && action_assertable? | |
+ end | |
+ | |
+ def create_device_verification_status | |
+ DeviceVerificationStatus.insert(verification_id: @device_verification.id, status_id: @status.id) if @device_verification && @status | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/statuses_controller.rb truth-new/opensource/app/controllers/api/v1/statuses_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/statuses_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/statuses_controller.rb 2024-04-12 09:09:08 | |
@@ -2,6 +2,9 @@ | |
class Api::V1::StatusesController < Api::BaseController | |
include Authorization | |
+ include Divergable | |
+ include Redisable | |
+ include AdsConcern | |
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy] | |
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy] | |
@@ -9,8 +12,14 @@ | |
before_action :set_status, only: [:show, :context, :ancestors, :descendants] | |
before_action :set_thread, only: [:create] | |
before_action :require_authenticated_user!, unless: :allowed_public_access? | |
+ before_action :diverge_users_without_current_ip, only: [:create] | |
+ before_action :set_group, only: [:create] | |
+ before_action :reject_duplicate_group_status, only: [:create] | |
after_action :insert_pagination_headers, only: :descendants | |
+ after_action :create_device_verification_status, only: :create | |
+ include Assertable | |
+ | |
override_rate_limit_headers :create, family: :statuses | |
# This API was originally unlimited, pagination cannot be introduced without | |
@@ -19,10 +28,17 @@ | |
# than this anyway | |
CONTEXT_LIMIT = 4_096 | |
PAGINATED_LIMIT = 20 | |
+ STATUS_HASH_CACHE_EXPIRE_AFTER = 1.hour.seconds | |
+ DUPLICATE_THRESHOLD = 3 | |
def show | |
@status = cache_collection([@status], Status).first | |
- render json: @status, serializer: REST::StatusSerializer | |
+ | |
+ if (@status.visibility == 'self' && current_user.account_id != @status.account.id) || @status.group&.discarded? | |
+ raise(ActiveRecord::RecordNotFound) | |
+ end | |
+ | |
+ render json: REST::V2::StatusSerializer.new(context: { current_user: current_user }).serialize(@status) | |
end | |
def context | |
@@ -34,6 +50,8 @@ | |
def descendants | |
@descendants = prepare_descendants(PAGINATED_LIMIT) | |
+ include_ad_indexes(@descendants) | |
+ | |
render_context_subitems(@descendants) | |
end | |
@@ -46,6 +64,9 @@ | |
end | |
def create | |
+ whitelisted_visibilities = ['public', 'group', nil] | |
+ render json: { error: 'This action is not allowed' }, status: 403 and return unless whitelisted_visibilities.include?(status_params[:visibility]) | |
+ | |
@status = PostStatusService.new.call(current_user.account, | |
text: status_params[:status], | |
mentions: status_params[:to], | |
@@ -54,25 +75,30 @@ | |
sensitive: status_params[:sensitive], | |
spoiler_text: status_params[:spoiler_text], | |
visibility: status_params[:visibility], | |
+ group: @group, | |
+ group_timeline_visible: status_params[:group_timeline_visible], | |
+ group_visibility: @group_visibility || nil, | |
scheduled_at: status_params[:scheduled_at], | |
application: doorkeeper_token.application, | |
poll: status_params[:poll], | |
quote_id: status_params[:quote_id], | |
idempotency: request.headers['Idempotency-Key'], | |
- with_rate_limit: true) | |
+ with_rate_limit: true, | |
+ ip_address: request.remote_ip, | |
+ domain: Addressable::URI.parse(request.url).normalized_host) | |
- render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer | |
+ render json: REST::V2::StatusSerializer.new(context: { current_user: current_user }).serialize(@status) | |
end | |
def destroy | |
@status = Status.where(account_id: current_user.account).find(params[:id]) | |
authorize @status, :destroy? | |
- | |
@status.reblogs.update_all(deleted_at: Time.current, deleted_by_id: current_user&.account_id) | |
@status.update!(deleted_at: Time.current, deleted_by_id: current_user&.account_id) | |
- RemovalWorker.perform_async(@status.id, redraft: true) | |
+ @thread = Status.find_by(id: @status.in_reply_to_id) if @status.in_reply_to_id | |
+ RemovalWorker.perform_async(@status.id, redraft: true, called_by_id: current_account.id) | |
remove_from_whale_list if @status.account.whale? | |
- @status.account.statuses_count = @status.account.statuses_count - 1 | |
+ @status.status_pins&.destroy_all | |
render json: @status, serializer: REST::StatusSerializer, source_requested: true | |
end | |
@@ -83,7 +109,7 @@ | |
@status = Status.find(params[:id]) | |
authorize @status, :show? | |
rescue Mastodon::NotPermittedError | |
- not_found | |
+ raise ActiveRecord::RecordNotFound | |
end | |
def set_thread | |
@@ -92,6 +118,27 @@ | |
render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404 | |
end | |
+ def set_group | |
+ quoted = status_params[:quote_id].presence && Status.find(status_params[:quote_id]) | |
+ group_id = status_params[:group_id].presence || quoted&.group&.id || @thread&.group&.id | |
+ group = Group.find_by(id: group_id) if group_id | |
+ @group = if group&.discarded? | |
+ false | |
+ elsif quoted | |
+ quoted.group&.everyone? ? group_member?(group) && group : group # We don't want to set group if quoted group is a public group and the "quoter" is not a member. | |
+ else | |
+ group | |
+ end | |
+ | |
+ if @group.present? | |
+ policy = status_params[:quote_id].present? ? :show? : :post? | |
+ authorize(@group, policy) | |
+ @group_visibility = @group.statuses_visibility | |
+ end | |
+ rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError | |
+ render json: { error: I18n.t('statuses.errors.not_permitted_to_post') }, status: 404 | |
+ end | |
+ | |
def status_params | |
params.permit( | |
:status, | |
@@ -99,13 +146,14 @@ | |
:sensitive, | |
:spoiler_text, | |
:visibility, | |
+ :group_id, | |
+ :group_timeline_visible, | |
:scheduled_at, | |
:quote_id, | |
to: [], | |
media_ids: [], | |
poll: [ | |
:multiple, | |
- :hide_totals, | |
:expires_in, | |
options: [], | |
] | |
@@ -143,5 +191,48 @@ | |
def allowed_public_access? | |
current_user || (action_name == 'show' && @status&.account&.user&.unauth_visibility? && !@status&.reply?) | |
+ end | |
+ | |
+ def validate_client | |
+ action_assertable? | |
+ end | |
+ | |
+ def asserting? | |
+ request.headers['x-tru-assertion'] && action_assertable? | |
+ end | |
+ | |
+ def action_assertable? | |
+ %w(create).include?(action_name) ? true : false | |
+ end | |
+ | |
+ def log_android_activity? | |
+ current_user&.user_sms_reverification_required && action_assertable? | |
+ end | |
+ | |
+ def create_device_verification_status | |
+ DeviceVerificationStatus.insert(verification_id: @device_verification.id, status_id: @status.id) if @device_verification && @status | |
+ end | |
+ | |
+ def group_member?(group) | |
+ group&.members&.where(id: current_account&.id)&.exists? | |
+ end | |
+ | |
+ def reject_duplicate_group_status | |
+ return if @group.blank? | |
+ return if status_params[:status].blank? | |
+ | |
+ status_hash = hexdigest status_params[:status] | |
+ key = "status:#{current_account.id}:#{status_hash}" | |
+ # cached_value = redis.get(key).to_i | |
+ # configuration = ::Configuration::FeatureSetting.find_by(name: 'rate_limit_duplicate_group_status_enabled') | |
+ # render json: { error: I18n.t('errors.429') }, status: 429 and return if cached_value.to_i >= DUPLICATE_THRESHOLD && ActiveModel::Type::Boolean.new.cast(configuration&.value) | |
+ | |
+ redis.incrby(key, 1) | |
+ redis.expire(key, STATUS_HASH_CACHE_EXPIRE_AFTER) | |
+ | |
+ cached_value = redis.get(key).to_i | |
+ if cached_value.to_i >= DUPLICATE_THRESHOLD | |
+ Rails.logger.info "Groups rate limit: User -> #{current_user.id} has exceeded the threshold. Current hits -> #{cached_value.to_i}, remote_ip -> #{request.remote_ip}" | |
+ end | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1: tags | |
Only in truth-new/opensource/app/controllers/api/v1: tags_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/timelines: group_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/timelines: group_tag_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/timelines/home_controller.rb truth-new/opensource/app/controllers/api/v1/timelines/home_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/timelines/home_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/timelines/home_controller.rb 2024-04-01 14:59:13 | |
@@ -6,14 +6,23 @@ | |
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } | |
def show | |
- @statuses = load_statuses | |
+ @statuses = load_statuses | |
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq | |
- render json: @statuses, | |
- each_serializer: REST::StatusSerializer, | |
- relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), | |
- account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id), | |
- status: account_home_feed.regenerating? ? 206 : 200 | |
+ if (ad_indexes = ENV.fetch('X_TRUTH_AD_INDEXES', nil)) | |
+ response.headers['x-truth-ad-indexes'] = ad_indexes | |
+ end | |
+ | |
+ render json: Panko::ArraySerializer.new( | |
+ @statuses, | |
+ each_serializer: REST::V2::StatusSerializer, | |
+ context: { | |
+ current_user: current_user, | |
+ relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), | |
+ account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id), | |
+ status: account_home_feed.regenerating? ? 206 : 200, | |
+ } | |
+ ).to_json | |
end | |
private | |
diff -ru truth-old/opensource/app/controllers/api/v1/trends_controller.rb truth-new/opensource/app/controllers/api/v1/trends_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/trends_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/trends_controller.rb 2024-04-01 14:59:13 | |
@@ -2,14 +2,47 @@ | |
class Api::V1::TrendsController < Api::BaseController | |
before_action :set_tags | |
+ after_action :insert_pagination_headers, only: :index | |
+ DEFAULT_LIMIT = 20 | |
+ | |
def index | |
- render json: @tags, each_serializer: REST::TagSerializer | |
+ render json: @tags || [] | |
end | |
private | |
def set_tags | |
- @tags = TrendingTags.get(limit_param(100)) | |
+ @tags = TrendingTagsResult.load_results( | |
+ limit, # in_limit | |
+ offset # in_offset | |
+ ) | |
+ end | |
+ | |
+ def limit | |
+ params[:limit].present? ? params[:limit].to_i : DEFAULT_LIMIT | |
+ end | |
+ | |
+ def offset | |
+ params[:offset].present? ? params[:offset].to_i : 0 | |
+ end | |
+ | |
+ def insert_pagination_headers | |
+ @tags = JSON.parse(@tags || '[]') | |
+ set_pagination_headers(next_path) | |
+ end | |
+ | |
+ def next_path | |
+ if records_continue? | |
+ api_v1_trends_url pagination_params(offset: @tags.size + params[:offset].to_i) | |
+ end | |
+ end | |
+ | |
+ def records_continue? | |
+ @tags.size == limit_param(DEFAULT_LIMIT) | |
+ end | |
+ | |
+ def pagination_params(core_params) | |
+ params.slice(:limit, :page).permit(:limit, :page).merge(core_params) | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb 2024-04-12 16:08:26 | |
@@ -1,15 +1,23 @@ | |
# frozen_string_literal: true | |
class Api::V1::Truth::Admin::AccountsController < Api::BaseController | |
+ include EmailHelper | |
+ | |
before_action :require_staff! | |
- before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:count, :update] | |
- before_action :set_account, only: [:update] | |
+ before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :blacklist, :count, :email_domain_blocks] | |
+ before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, only: [:update, :confirm_totp] | |
+ before_action :set_account, only: [:update, :confirm_totp] | |
+ before_action :set_email_domain_block, only: :email_domain_blocks | |
def index | |
- accounts = Account.includes(:account_stat, :user).ransack(params[:query]) | |
- accounts.sorts = params[:sorts] || "id desc" | |
- accounts = accounts.result.page(params[:page]) | |
- render json: accounts, each_serializer: REST::Admin::AccountSerializer | |
+ @accounts = account_search | |
+ render json: Panko::ArraySerializer.new( | |
+ @accounts, | |
+ each_serializer: REST::V2::Admin::AccountSerializer, | |
+ context: { | |
+ advertisers: Account.recent_advertisers(@accounts.pluck(:id)), | |
+ } | |
+ ).to_json | |
end | |
def update | |
@@ -22,32 +30,84 @@ | |
end | |
end | |
+ def blacklist | |
+ render json: { blacklist: suspended_accounts_exist? }, status: 200 | |
+ end | |
+ | |
def count | |
render json: { count: number_of_accounts }, status: 200 | |
end | |
+ def email_domain_blocks | |
+ render json: { disposable: !!@email_domain_block.disposable }, status: 200 | |
+ end | |
+ | |
+ def confirm_totp | |
+ unless @account.user.validate_and_consume_otp!(totp_params[:code]) | |
+ render json: { | |
+ error_code: 'OTP_CODE_INVALID', | |
+ error_message: I18n.t('otp_authentication.invalid_code'), | |
+ }, status: 422 | |
+ end | |
+ end | |
+ | |
private | |
+ def account_search | |
+ oauth_token = params[:oauth_token] | |
+ if oauth_token.present? | |
+ find_by_token(oauth_token) | |
+ else | |
+ AdminAccountSearchService.new.call( | |
+ params[:query], | |
+ current_account, | |
+ limit: limit_param(DEFAULT_ACCOUNTS_LIMIT) | |
+ ) | |
+ end | |
+ end | |
+ | |
+ def suspended_accounts_exist? | |
+ if Account.joins(:user).where.not(suspended_at: nil).where(user: { sms: blacklist_params[:sms] }).exists? | |
+ 1 | |
+ else | |
+ 0 | |
+ end | |
+ end | |
+ | |
def number_of_accounts | |
- if count_params[:email].present? | |
- User.find_by(email: count_params[:email]).present? ? 1 : 0 | |
+ if (email = count_params[:email]) | |
+ count_by_email(email) | |
elsif count_params[:sms].present? | |
- User.where(sms: count_params[:sms]).size | |
+ # For admin users, allow unlimited accounts | |
+ if User.where(admin: true).where(sms: count_params[:sms]).exists? | |
+ 0 | |
+ else | |
+ User.where(sms: count_params[:sms]).size | |
+ end | |
else | |
0 | |
end | |
end | |
+ def count_by_email(email) | |
+ User.find_by(email: email).present? || CanonicalEmailBlock.block?(email) || UserBaseEmail.find_by(email: email_to_canonical_email(email)).present? ? 1 : 0 | |
+ end | |
+ | |
+ def blacklist_params | |
+ params.permit(:sms) | |
+ end | |
+ | |
def count_params | |
params.permit(:email, :sms) | |
end | |
def account_params | |
- params.require(:account).permit(:username, :display_name, :note) | |
+ params.require(:account).permit(:username, :display_name, :note, :website) | |
end | |
def set_account | |
- @account = Account.find(params[:id]) | |
+ account_id = params[:account_id] || params[:id] | |
+ @account = Account.find(account_id) | |
end | |
def set_new_email | |
@@ -61,12 +121,15 @@ | |
render json: { status: :success } | |
end | |
- # TODO: Vlad to refactor share access-token removal | |
+ def set_email_domain_block | |
+ @email_domain_block = EmailDomainBlock.find_by!(domain: params.require(:domain)) | |
+ end | |
+ | |
def update_users_password | |
@account.user.skip_password_change_notification! | |
if @account.user.reset_password(params[:password], params[:password]) | |
- Doorkeeper::AccessToken.where(resource_owner_id: @account.user.id).delete_all | |
+ OauthAccessToken.where(resource_owner_id: @account.user.id).delete_all | |
@account.user.session_activations.destroy_all | |
@account.user.forget_me! | |
render json: { status: :success }, status: 200 | |
@@ -79,5 +142,14 @@ | |
else | |
render json: @account.errors, status: :unprocessable_entity | |
end | |
+ end | |
+ | |
+ def find_by_token(oauth_token) | |
+ doorkeeper_token = OauthAccessToken.find_by!(token: oauth_token, revoked_at: nil) | |
+ [User.find(doorkeeper_token&.resource_owner_id)&.account] | |
+ end | |
+ | |
+ def totp_params | |
+ params.permit(:code) | |
end | |
-end | |
\ No newline at end of file | |
+end | |
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: channel_notifications_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: email_domain_blocks_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: marketing_notifications_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: media_attachments_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth: ads_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth: android_device_check | |
Only in truth-new/opensource/app/controllers/api/v1/truth: carousels | |
Only in truth-new/opensource/app/controllers/api/v1/truth: ios_device_check | |
Only in truth-new/opensource/app/controllers/api/v1/truth: oauth_tokens_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/truth/passwords_controller.rb truth-new/opensource/app/controllers/api/v1/truth/passwords_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/truth/passwords_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/truth/passwords_controller.rb 2024-04-01 14:59:13 | |
@@ -5,13 +5,29 @@ | |
before_action :set_confirm_user, only: :reset_confirm | |
before_action :set_request_user, only: :reset_request | |
before_action :validate_user_is_present, only: :reset_confirm | |
+ around_action :set_locale, only: :reset_confirm | |
def reset_confirm | |
if @user.reset_password(password_reset_confirm_params[:password], password_reset_confirm_params[:password]) | |
update_users_password | |
render json: { status: :success } | |
else | |
- render json: { error: 'Password and password confirmation do not match.' }, status: 400 | |
+ errors = @user.errors.to_hash | |
+ password_invalid = errors[:password]&.pop | |
+ default_error = I18n.t('users.password_mismatch', locale: :en) | |
+ message, message_with_locale, code = | |
+ if password_invalid.present? | |
+ error = errors[:base]&.pop || default_error | |
+ [error, password_invalid, 'PASSWORD_INVALID'] | |
+ else | |
+ [default_error, I18n.t('users.password_mismatch'), 'PASSWORD_MISMATCH'] | |
+ end | |
+ | |
+ render json: { | |
+ error: message, | |
+ error_code: code, | |
+ error_message: message_with_locale, | |
+ }, status: 400 | |
end | |
end | |
@@ -22,7 +38,7 @@ | |
private | |
def validate_user_is_present | |
- forbidden unless @user.present? | |
+ forbidden if @user.blank? | |
end | |
def update_users_password | |
Only in truth-new/opensource/app/controllers/api/v1/truth: policies_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth: suggestions | |
Only in truth-new/opensource/app/controllers/api/v1/truth/trending: group_tags_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1/truth/trending: groups_controller.rb | |
diff -ru truth-old/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb truth-new/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb | |
--- truth-old/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb 2024-04-01 14:59:13 | |
@@ -1,33 +1,75 @@ | |
# frozen_string_literal: true | |
class Api::V1::Truth::Trending::TruthsController < Api::BaseController | |
- before_action :set_trendings | |
- before_action :set_truths | |
+ before_action -> { doorkeeper_authorize! :read } | |
+ before_action :require_user! | |
+ after_action :insert_pagination_headers | |
- TRENDING_TAGS_LIMIT = 10 | |
+ TRENDING_TRUTHS_LIMIT = 10 | |
+ TRENDING_TRUTHS_DEFAULT_OFFSET = 10 | |
def index | |
- render json: @truths, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@truths, current_user&.account_id) | |
+ render json: Panko::ArraySerializer.new( | |
+ truths, | |
+ each_serializer: REST::V2::StatusSerializer, | |
+ context: { | |
+ current_user: current_user, | |
+ relationships: StatusRelationshipsPresenter.new(@truths, current_user&.account_id), | |
+ } | |
+ ).to_json | |
end | |
private | |
- def set_trendings | |
- @trendings = Trending.limit(TRENDING_TAGS_LIMIT).all | |
+ def trending_truths | |
+ @trending_truths ||= Status.trending_statuses | |
+ .excluding_unauthorized_tv_statuses(current_account.id) | |
+ .paginate_by_limit_offset( | |
+ limit_param(TRENDING_TRUTHS_LIMIT), | |
+ params_slice(:offset) | |
+ ) | |
end | |
- def set_truths | |
- @truths = load_statuses | |
+ def truths | |
+ @truths ||= load_cached_tagged_statuses | |
end | |
- def load_statuses | |
- cached_tagged_statuses | |
+ def load_cached_tagged_statuses | |
+ cache_collection(trending_truths, Status) | |
end | |
- def cached_tagged_statuses | |
- cache_collection(all_trending_timeline_statuses, Status) | |
+ def insert_pagination_headers | |
+ set_pagination_headers(next_path, prev_path) | |
end | |
- def all_trending_timeline_statuses | |
- @trendings.flat_map(&:status) | |
+ def next_path | |
+ api_v1_truth_trending_truths_url offset: max_pagination_offset if records_continue? | |
+ end | |
+ | |
+ def prev_path | |
+ no_prev_path? ? nil : api_v1_truth_trending_truths_url(offset: min_pagination_offset) | |
+ end | |
+ | |
+ def no_prev_path? | |
+ trending_truths.empty? || params[:offset]&.to_i&.zero? || !params[:offset] | |
+ end | |
+ | |
+ def max_pagination_offset | |
+ params[:offset] ? params[:offset].to_i + TRENDING_TRUTHS_DEFAULT_OFFSET.to_i : TRENDING_TRUTHS_DEFAULT_OFFSET | |
+ end | |
+ | |
+ def min_pagination_offset | |
+ params[:offset] ? params[:offset].to_i - TRENDING_TRUTHS_DEFAULT_OFFSET.to_i : nil | |
+ end | |
+ | |
+ def pagination_max_id | |
+ trending_truths.last.id | |
+ end | |
+ | |
+ def pagination_since_id | |
+ trending_truths.first.id | |
+ end | |
+ | |
+ def records_continue? | |
+ trending_truths.size == limit_param(TRENDING_TRUTHS_LIMIT) | |
end | |
end | |
Only in truth-new/opensource/app/controllers/api/v1/truth: videos_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v1: tv | |
Only in truth-new/opensource/app/controllers/api/v1: verify_sms | |
Only in truth-new/opensource/app/controllers/api/v2: feeds_controller.rb | |
Only in truth-new/opensource/app/controllers/api/v2: pleroma | |
Only in truth-new/opensource/app/controllers/api/v2: statuses_controller.rb | |
Only in truth-new/opensource/app/controllers/api: v4 | |
diff -ru truth-old/opensource/app/controllers/api/web/push_subscriptions_controller.rb truth-new/opensource/app/controllers/api/web/push_subscriptions_controller.rb | |
--- truth-old/opensource/app/controllers/api/web/push_subscriptions_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/api/web/push_subscriptions_controller.rb 2024-04-01 14:59:13 | |
@@ -26,6 +26,7 @@ | |
mention: alerts_enabled, | |
poll: alerts_enabled, | |
status: alerts_enabled, | |
+ chat: alerts_enabled | |
}, | |
} | |
@@ -61,6 +62,6 @@ | |
end | |
def data_params | |
- @data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status]) | |
+ @data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status, :chat]) | |
end | |
end | |
Only in truth-new/opensource/app/controllers: apidocs_controller.rb | |
diff -ru truth-old/opensource/app/controllers/application_controller.rb truth-new/opensource/app/controllers/application_controller.rb | |
--- truth-old/opensource/app/controllers/application_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/application_controller.rb 2024-04-12 09:09:08 | |
@@ -24,6 +24,7 @@ | |
rescue_from ActionController::UnknownFormat, with: :not_acceptable | |
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity | |
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests | |
+ rescue_from Mastodon::UnprocessableEntityError, with: :unprocessable_with_message | |
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error | |
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable | |
@@ -99,6 +100,10 @@ | |
respond_with_error(422) | |
end | |
+ def unprocessable_with_message(error_message) | |
+ render json: { error: error_message.to_s }, status: 422 | |
+ end | |
+ | |
def not_acceptable | |
respond_with_error(406) | |
end | |
@@ -144,10 +149,21 @@ | |
'mastodon-light' | |
end | |
- def respond_with_error(code) | |
+ def respond_with_error(status) | |
+ code = Rack::Utils::HTTP_STATUS_CODES[status] | |
respond_to do |format| | |
- format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] } | |
- format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code } | |
+ format.json do | |
+ render json: { | |
+ error: code, | |
+ error_message: code, | |
+ error_code: format_code(code), | |
+ }, status: status | |
+ end | |
+ format.any { render "errors/#{status}", layout: 'error', status: status, formats: [:html] } | |
end | |
+ end | |
+ | |
+ def format_code(string) | |
+ string.upcase.gsub(' ', '_') | |
end | |
end | |
diff -ru truth-old/opensource/app/controllers/auth/passwords_controller.rb truth-new/opensource/app/controllers/auth/passwords_controller.rb | |
--- truth-old/opensource/app/controllers/auth/passwords_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/auth/passwords_controller.rb 2024-04-01 14:59:13 | |
@@ -11,7 +11,7 @@ | |
if resource.errors.empty? | |
resource.session_activations.destroy_all | |
resource.forget_me! | |
- Doorkeeper::AccessToken.where(resource_owner_id: resource.id).update_all(revoked_at: Time.now.utc) | |
+ OauthAccessToken.where(resource_owner_id: resource.id).update_all(revoked_at: Time.now.utc) | |
end | |
end | |
end | |
Only in truth-new/opensource/app/controllers/concerns: ads_concern.rb | |
Only in truth-new/opensource/app/controllers/concerns: assertable.rb | |
diff -ru truth-old/opensource/app/controllers/concerns/cache_concern.rb truth-new/opensource/app/controllers/concerns/cache_concern.rb | |
--- truth-old/opensource/app/controllers/concerns/cache_concern.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/concerns/cache_concern.rb 2024-04-01 14:59:13 | |
@@ -28,7 +28,7 @@ | |
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature' | |
end | |
- def cache_collection(raw, klass) | |
+ def cache_collection(raw, klass, include_removed = false) | |
return raw unless klass.respond_to?(:with_includes) | |
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation) | |
@@ -37,13 +37,25 @@ | |
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id) | |
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys | |
+ raw_hash = raw.index_by(&:id) | |
+ | |
+ if klass.has_attribute?(:tombstone) | |
+ cached_keys_with_value = reload_tombstone_value(cached_keys_with_value, raw_hash) | |
+ end | |
+ | |
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!) | |
unless uncached_ids.empty? | |
- uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id) | |
+ uncached = klass | |
+ uncached = uncached.with_discarded if include_removed | |
+ uncached = uncached.where(id: uncached_ids).with_includes.index_by(&:id) | |
+ if klass.has_attribute?(:tombstone) | |
+ uncached = reload_tombstone_value(uncached, raw_hash) | |
+ end | |
+ | |
uncached.each_value do |item| | |
- Rails.cache.write(item, item, expires_in: 60.minutes) | |
+ Rails.cache.write(item, item, expires_in: 1.hour) | |
end | |
end | |
@@ -52,5 +64,13 @@ | |
def cache_collection_paginated_by_id(raw, klass, limit, options) | |
cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass | |
+ end | |
+ | |
+ def reload_tombstone_value(collection, raw_hash) | |
+ collection.each_with_object({}) do |(k, v), hash| | |
+ next unless v.has_attribute?(:tombstone) && raw_hash[k].has_attribute?(:tombstone) | |
+ hash[k] = [v.tombstone = raw_hash[k].tombstone] | |
+ end | |
+ collection | |
end | |
end | |
Only in truth-new/opensource/app/controllers/concerns: clientable.rb | |
Only in truth-new/opensource/app/controllers/concerns: divergable.rb | |
diff -ru truth-old/opensource/app/controllers/concerns/status_controller_concern.rb truth-new/opensource/app/controllers/concerns/status_controller_concern.rb | |
--- truth-old/opensource/app/controllers/concerns/status_controller_concern.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/concerns/status_controller_concern.rb 2024-04-01 14:59:13 | |
@@ -38,10 +38,7 @@ | |
descendants = cache_collection( | |
@status.descendants( | |
DESCENDANTS_LIMIT, | |
- current_account, | |
- @max_descendant_thread_id, | |
- @since_descendant_thread_id, | |
- DESCENDANTS_DEPTH_LIMIT | |
+ current_account | |
), | |
Status | |
) | |
diff -ru truth-old/opensource/app/controllers/concerns/two_factor_authentication_concern.rb truth-new/opensource/app/controllers/concerns/two_factor_authentication_concern.rb | |
--- truth-old/opensource/app/controllers/concerns/two_factor_authentication_concern.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/concerns/two_factor_authentication_concern.rb 2024-04-01 14:59:13 | |
@@ -39,8 +39,6 @@ | |
if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s | |
restart_session | |
- elsif user.webauthn_enabled? && user_params.key?(:credential) && session[:attempt_user_id] | |
- authenticate_with_two_factor_via_webauthn(user) | |
elsif user_params.key?(:otp_attempt) && session[:attempt_user_id] | |
authenticate_with_two_factor_via_otp(user) | |
elsif user.present? && user.external_or_valid_password?(user_params[:password]) | |
diff -ru truth-old/opensource/app/controllers/concerns/user_tracking_concern.rb truth-new/opensource/app/controllers/concerns/user_tracking_concern.rb | |
--- truth-old/opensource/app/controllers/concerns/user_tracking_concern.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/concerns/user_tracking_concern.rb 2024-04-01 14:59:13 | |
@@ -1,9 +1,11 @@ | |
# frozen_string_literal: true | |
module UserTrackingConcern | |
+ include Redisable | |
+ | |
extend ActiveSupport::Concern | |
- TRACKED_CONTROLLERS = %w(home credentials) | |
- UPDATE_SIGN_IN_HOURS = 24 | |
+ TRACKED_CONTROLLERS = %w(credentials) | |
+ INTERACTIONS_SCORE_TRACKED_CONTROLLER = 'credentials' | |
included do | |
before_action :update_user_sign_in | |
@@ -12,10 +14,27 @@ | |
private | |
def update_user_sign_in | |
- current_user.update_sign_in!(request) if user_needs_sign_in_update? | |
+ if user_needs_sign_in_update? | |
+ current_user.update_sign_in!(request) | |
+ update_account_score | |
+ end | |
end | |
def user_needs_sign_in_update? | |
- TRACKED_CONTROLLERS.include?(controller_name) && user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < UPDATE_SIGN_IN_HOURS.hours.ago) | |
+ TRACKED_CONTROLLERS.include?(controller_name) && user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < Date.today) | |
+ end | |
+ | |
+ def update_account_score | |
+ return unless controller_name == INTERACTIONS_SCORE_TRACKED_CONTROLLER && current_account | |
+ | |
+ current_week = Time.now.strftime('%U').to_i | |
+ last_week = current_week - 1 | |
+ key1 = "interactions_score:#{current_account.id}:#{current_week}" | |
+ key2 = "interactions_score:#{current_account.id}:#{last_week}" | |
+ | |
+ scores = Redis.current.mget(key1, key2) | |
+ scores_sum = scores.compact.map(&:to_i).sum.to_i | |
+ | |
+ current_account.update(interactions_score: scores_sum) if scores_sum | |
end | |
end | |
Only in truth-new/opensource/app/controllers: link_controller.rb | |
diff -ru truth-old/opensource/app/controllers/oauth/mfa_controller.rb truth-new/opensource/app/controllers/oauth/mfa_controller.rb | |
--- truth-old/opensource/app/controllers/oauth/mfa_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/oauth/mfa_controller.rb 2023-05-05 13:42:02 | |
@@ -5,6 +5,7 @@ | |
def challenge | |
params[:grant_type] = "password" | |
+ request.params[:username] = false | |
create | |
end | |
diff -ru truth-old/opensource/app/controllers/settings/deletes_controller.rb truth-new/opensource/app/controllers/settings/deletes_controller.rb | |
--- truth-old/opensource/app/controllers/settings/deletes_controller.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/controllers/settings/deletes_controller.rb 2024-04-01 14:59:13 | |
@@ -43,7 +43,9 @@ | |
def destroy_account! | |
current_account.suspend!(origin: :local) | |
- AccountDeletionWorker.perform_async(current_user.account_id) | |
+ acct_id = current_account.id | |
+ # Self deletion uses acct_id as the deleted_by_id | |
+ AccountDeletionWorker.perform_async(acct_id, acct_id, skip_activitypub: true) | |
sign_out | |
end | |
end | |
Only in truth-new/opensource/app/controllers/well_known: skadnetwork_controller.rb | |
diff -ru truth-old/opensource/app/helpers/application_helper.rb truth-new/opensource/app/helpers/application_helper.rb | |
--- truth-old/opensource/app/helpers/application_helper.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/helpers/application_helper.rb 2024-04-01 14:59:13 | |
@@ -92,7 +92,7 @@ | |
elsif status.private_visibility? || status.limited_visibility? | |
fa_icon('lock', title: I18n.t('statuses.visibilities.private')) | |
elsif status.direct_visibility? | |
- fa_icon('envelope', title: I18n.t('statuses.visibilities.direct')) | |
+ fa_icon('at', title: I18n.t('statuses.visibilities.direct')) | |
end | |
end | |
Only in truth-new/opensource/app/helpers: discarded_helper.rb | |
diff -ru truth-old/opensource/app/helpers/email_helper.rb truth-new/opensource/app/helpers/email_helper.rb | |
--- truth-old/opensource/app/helpers/email_helper.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/helpers/email_helper.rb 2024-04-01 14:59:13 | |
@@ -1,15 +1,27 @@ | |
# frozen_string_literal: true | |
module EmailHelper | |
+ BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS = ENV.fetch('BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS', false) | |
+ | |
def self.included(base) | |
base.extend(self) | |
end | |
def email_to_canonical_email(str) | |
+ username, domain = email_to_canonical_email_by_username_and_domain(str).values_at(:username, :domain) | |
+ "#{username}@#{domain}" | |
+ end | |
+ | |
+ def email_to_canonical_email_by_username_and_domain(str) | |
username, domain = str.downcase.split('@', 2) | |
- username, = username.gsub('.', '').split('+', 2) | |
- "#{username}@#{domain}" | |
+ if BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS && BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS.split(',').map(&:strip).include?(domain) | |
+ username = username.gsub('.', '') | |
+ end | |
+ | |
+ username, = username.split('+', 2) | |
+ | |
+ { username: username, domain: domain } | |
end | |
def email_to_canonical_email_hash(str) | |
diff -ru truth-old/opensource/app/helpers/statuses_helper.rb truth-new/opensource/app/helpers/statuses_helper.rb | |
--- truth-old/opensource/app/helpers/statuses_helper.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/helpers/statuses_helper.rb 2024-04-01 14:59:13 | |
@@ -101,7 +101,7 @@ | |
when 'private' | |
fa_icon 'lock fw' | |
when 'direct' | |
- fa_icon 'envelope fw' | |
+ fa_icon 'at fw' | |
end | |
end | |
diff -ru truth-old/opensource/app/javascript/mastodon/components/status.js truth-new/opensource/app/javascript/mastodon/components/status.js | |
--- truth-old/opensource/app/javascript/mastodon/components/status.js 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/javascript/mastodon/components/status.js 2024-04-01 14:59:13 | |
@@ -457,8 +457,6 @@ | |
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) }, | |
}; | |
- const visibilityIcon = visibilityIconInfo[status.get('visibility')]; | |
- | |
return ( | |
<HotKeys handlers={handlers}> | |
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}> | |
Only in truth-new/opensource/app/javascript/mastodon/locales/locale-data: README.md | |
diff -ru truth-old/opensource/app/javascript/mastodon/selectors/index.js truth-new/opensource/app/javascript/mastodon/selectors/index.js | |
--- truth-old/opensource/app/javascript/mastodon/selectors/index.js 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/javascript/mastodon/selectors/index.js 2024-04-01 14:59:13 | |
@@ -3,12 +3,11 @@ | |
import { me } from '../initial_state'; | |
const getAccountBase = (state, id) => state.getIn(['accounts', id], null); | |
-const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); | |
const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); | |
const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]); | |
export const makeGetAccount = () => { | |
- return createSelector([getAccountBase, getAccountCounters, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => { | |
+ return createSelector([getAccountBase, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => { | |
if (base === null) { | |
return null; | |
} | |
diff -ru truth-old/opensource/app/javascript/packs/public.js truth-new/opensource/app/javascript/packs/public.js | |
--- truth-old/opensource/app/javascript/packs/public.js 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/javascript/packs/public.js 2024-04-01 14:59:13 | |
@@ -318,6 +318,10 @@ | |
} | |
}); | |
}); | |
+ | |
+ delegate(document, '[data-behavior="close-window"]', 'click', () => { | |
+ window.open('', '_parent', '').close(); | |
+ }); | |
} | |
loadPolyfills() | |
diff -ru truth-old/opensource/app/javascript/styles/application.scss truth-new/opensource/app/javascript/styles/application.scss | |
--- truth-old/opensource/app/javascript/styles/application.scss 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/javascript/styles/application.scss 2024-04-01 14:59:13 | |
@@ -28,3 +28,5 @@ | |
@import 'mastodon/rtl'; | |
@import 'mastodon/accessibility'; | |
@import 'mastodon/inbox'; | |
+@import 'mastodon/links'; | |
+@import 'mastodon/utils'; | |
Only in truth-new/opensource/app/javascript/styles: docs.scss | |
Only in truth-new/opensource/app/javascript/styles/mastodon: links.scss | |
Only in truth-new/opensource/app/javascript/styles/mastodon: utils.scss | |
diff -ru truth-old/opensource/app/lib/access_token_extension.rb truth-new/opensource/app/lib/access_token_extension.rb | |
--- truth-old/opensource/app/lib/access_token_extension.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/access_token_extension.rb 2024-04-01 14:59:13 | |
@@ -3,6 +3,8 @@ | |
module AccessTokenExtension | |
extend ActiveSupport::Concern | |
+ include Paginable | |
+ | |
included do | |
after_commit :push_to_streaming_api | |
end | |
diff -ru truth-old/opensource/app/lib/activitypub/activity/create.rb truth-new/opensource/app/lib/activitypub/activity/create.rb | |
--- truth-old/opensource/app/lib/activitypub/activity/create.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/activitypub/activity/create.rb 2024-04-01 15:02:56 | |
@@ -111,7 +111,7 @@ | |
thread: replied_to_status, | |
conversation: conversation_from_uri(@object['conversation']), | |
media_attachment_ids: process_attachments.take(4).map(&:id), | |
- poll: process_poll, | |
+ polls: process_poll || [], | |
quote: quote_from_url(@object['quoteUrl']), | |
} | |
end | |
@@ -284,15 +284,13 @@ | |
items = @object['oneOf'] | |
end | |
- voters_count = @object['votersCount'] | |
+ poll_options = items.map.with_index { |v, i| { option_number: i, text: v['name'] } } | |
- @account.polls.new( | |
+ [Poll.new( | |
multiple: multiple, | |
expires_at: expires_at, | |
- options: items.map { |item| item['name'].presence || item['content'] }.compact, | |
- cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }, | |
- voters_count: voters_count | |
- ) | |
+ options_attributes: poll_options | |
+ )] | |
end | |
def poll_vote? | |
@@ -313,7 +311,6 @@ | |
end | |
increment_voters_count! unless already_voted | |
- ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals? | |
end | |
def resolve_thread(status) | |
diff -ru truth-old/opensource/app/lib/activitypub/activity/delete.rb truth-new/opensource/app/lib/activitypub/activity/delete.rb | |
--- truth-old/opensource/app/lib/activitypub/activity/delete.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/activitypub/activity/delete.rb 2024-04-01 14:59:13 | |
@@ -13,7 +13,13 @@ | |
def delete_person | |
lock_or_return("delete_in_progress:#{@account.id}") do | |
- DeleteAccountService.new.call(@account, reserve_username: false, skip_activitypub: true) | |
+ DeleteAccountService.new.call( | |
+ @account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'activitypub_delete_person', | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ ) | |
end | |
end | |
diff -ru truth-old/opensource/app/lib/activitypub/activity/update.rb truth-new/opensource/app/lib/activitypub/activity/update.rb | |
--- truth-old/opensource/app/lib/activitypub/activity/update.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/activitypub/activity/update.rb 2024-04-01 14:59:13 | |
@@ -26,7 +26,5 @@ | |
status = Status.find_by(uri: object_uri, account_id: @account.id) | |
return if status.nil? || status.preloadable_poll.nil? | |
- | |
- ActivityPub::ProcessPollService.new.call(status.preloadable_poll, @object) | |
end | |
end | |
diff -ru truth-old/opensource/app/lib/activitypub/tag_manager.rb truth-new/opensource/app/lib/activitypub/tag_manager.rb | |
--- truth-old/opensource/app/lib/activitypub/tag_manager.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/activitypub/tag_manager.rb 2024-04-01 14:59:13 | |
@@ -27,6 +27,12 @@ | |
when :note, :comment, :activity | |
return activity_account_status_url(target.account, target) if target.reblog? | |
short_account_status_url(target.account, target) | |
+ when :group | |
+ group_url(target) | |
+ when :group_note | |
+ group_status_url(target) | |
+ when :group_request | |
+ group_request_url(target) | |
end | |
end | |
@@ -35,12 +41,18 @@ | |
case target.object_type | |
when :person | |
- target.instance_actor? ? instance_actor_url : account_url(target) | |
+ account_url(target) | |
when :note, :comment, :activity | |
return activity_account_status_url(target.account, target) if target.reblog? | |
account_status_url(target.account, target) | |
when :emoji | |
emoji_url(target) | |
+ when :group | |
+ group_url(target) | |
+ when :group_note | |
+ group_status_url(target) | |
+ when :group_request | |
+ group_request_url(target) | |
end | |
end | |
@@ -48,6 +60,12 @@ | |
account_url(username: username) | |
end | |
+ def url_for_chat_message(id) | |
+ message = ChatMessage.find(id) | |
+ chat = message.chat | |
+ "#{root_url}chats/#{chat.id}/messages/#{id}" | |
+ end | |
+ | |
def generate_uri_for(_target) | |
URI.join(root_url, 'payloads', SecureRandom.uuid) | |
end | |
@@ -168,5 +186,19 @@ | |
end | |
rescue ActiveRecord::RecordNotFound | |
nil | |
+ end | |
+ | |
+ private | |
+ | |
+ def group_status_url(target) | |
+ "https://#{Rails.configuration.x.web_domain}/group/#{target.group.slug}/statuses/#{target.id}" | |
+ end | |
+ | |
+ def group_request_url(target) | |
+ "https://#{Rails.configuration.x.web_domain}/group/#{target.group.slug}/manage/requests" | |
+ end | |
+ | |
+ def group_url(target) | |
+ "https://#{Rails.configuration.x.web_domain}/group/#{target.slug}" | |
end | |
end | |
Only in truth-new/opensource/app/lib: event_provider | |
Only in truth-new/opensource/app/lib: events | |
diff -ru truth-old/opensource/app/lib/feed_manager.rb truth-new/opensource/app/lib/feed_manager.rb | |
--- truth-old/opensource/app/lib/feed_manager.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/feed_manager.rb 2024-04-01 14:59:13 | |
@@ -153,11 +153,11 @@ | |
# @param [Account] into_account | |
# @return [void] | |
def unmerge_from_home(from_account, into_account) | |
- timeline_key = key(:home, into_account.id) | |
- oldest_home_score = redis_timelines.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0 | |
+ timeline_key = key(:home, into_account.id) | |
+ timeline_status_ids = redis.zrange(timeline_key, 0, -1) | |
- from_account.statuses.select('id, reblog_of_id').where('id > ?', oldest_home_score).reorder(nil).find_each do |status| | |
- remove_from_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?) | |
+ from_account.statuses.select('id, reblog_of_id').where(id: timeline_status_ids).reorder(nil).find_each do |status| | |
+ remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?) | |
end | |
end | |
@@ -166,11 +166,11 @@ | |
# @param [List] list | |
# @return [void] | |
def unmerge_from_list(from_account, list) | |
- timeline_key = key(:list, list.id) | |
- oldest_list_score = redis_timelines.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0 | |
+ timeline_key = key(:list, list.id) | |
+ timeline_status_ids = redis.zrange(timeline_key, 0, -1) | |
- from_account.statuses.select('id, reblog_of_id').where('id > ?', oldest_list_score).reorder(nil).find_each do |status| | |
- remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?) | |
+ from_account.statuses.select('id, reblog_of_id').where(id: timeline_status_ids).reorder(nil).find_each do |status| | |
+ remove_from_feed(:list, list.id, status, aggregate_reblogs: list.account.user&.aggregates_reblogs?) | |
end | |
end | |
@@ -180,7 +180,7 @@ | |
# @return [void] | |
def clear_from_home(account, target_account) | |
timeline_key = key(:home, account.id) | |
- timeline_status_ids = redis_timelines.zrange(timeline_key, 0, -1) | |
+ timeline_status_ids = status_ids_to_plain_numbers(redis_timelines.zrange(timeline_key, 0, -1)) | |
statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a | |
reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id) | |
with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id) | |
@@ -200,7 +200,7 @@ | |
# @return [void] | |
def clear_from_list(list, target_account) | |
timeline_key = key(:list, list.id) | |
- timeline_status_ids = redis_timelines.zrange(timeline_key, 0, -1) | |
+ timeline_status_ids = status_ids_to_plain_numbers(redis_timelines.zrange(timeline_key, 0, -1)) | |
statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a | |
reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id) | |
with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id) | |
@@ -232,11 +232,11 @@ | |
aggregate = account.user&.aggregates_reblogs? | |
timeline_key = key(:home, account.id) | |
- account.statuses.limit(limit).each do |status| | |
+ account.statuses.where.not(visibility: :group).limit(limit).each do |status| | |
add_to_feed(:home, account.id, status, aggregate) | |
end | |
- account.following.includes(:account_stat).find_each do |target_account| | |
+ account.following.includes(:account_follower, :account_following, :account_status).find_each do |target_account| | |
if redis_timelines.zcard(timeline_key) >= limit | |
oldest_home_score = redis_timelines.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i | |
last_status_score = Mastodon::Snowflake.id_at(account.last_status_at) | |
@@ -282,7 +282,7 @@ | |
# references to. | |
redis_timelines.pipelined do | |
reblogged_id_sets.each do |feed_id, future| | |
- future.value.each do |reblogged_id| | |
+ status_ids_to_plain_numbers(future.value).each do |reblogged_id| | |
reblog_set_key = key(type, feed_id, "reblogs:#{reblogged_id}") | |
redis_timelines.del(reblog_set_key) | |
end | |
@@ -310,6 +310,10 @@ | |
redis.publish("timeline:whale:#{status.account_id}", Oj.dump(event: :delete, payload: status.id.to_s)) | |
true | |
+ end | |
+ | |
+ def status_ids_to_plain_numbers(status_ids) | |
+ status_ids.map { |id| (id.is_a? Integer) || ((id.is_a? String) && id.force_encoding('UTF-8').valid_encoding? && (!id.include? '\\')) ? id : id.reverse.unpack('q').first } | |
end | |
private | |
diff -ru truth-old/opensource/app/lib/formatter.rb truth-new/opensource/app/lib/formatter.rb | |
--- truth-old/opensource/app/lib/formatter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/formatter.rb 2024-04-01 14:59:13 | |
@@ -33,9 +33,11 @@ | |
linkable_accounts = get_linkable_accounts(status) | |
linkable_accounts << status.account | |
+ external_links = options[:external_links] ? { external_links: options[:external_links].index_by(&:url) } : {} | |
+ | |
html = raw_content | |
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog | |
- html = encode_and_link_urls(html, linkable_accounts) | |
+ html = encode_and_link_urls(html, linkable_accounts, external_links) | |
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify] | |
html = simple_format(html, {}, sanitize: false) | |
html = quotify(html, status) if status.quote? && !options[:escape_quotify] | |
@@ -103,6 +105,21 @@ | |
html.html_safe # rubocop:disable Rails/OutputSafety | |
end | |
+ def format_chat_message(message) | |
+ linkable_usernames = [] | |
+ | |
+ message.scan(Account::MENTION_RE).each do |match| | |
+ username = match[1] | |
+ linkable_usernames << username | |
+ end | |
+ | |
+ linkable_accounts = Account.ci_find_by_usernames(linkable_usernames).to_a | |
+ | |
+ html = encode_and_link_urls(message, linkable_accounts) | |
+ html = simple_format(html, {}, sanitize: false) | |
+ html.html_safe # rubocop:disable Rails/OutputSafety | |
+ end | |
+ | |
def linkify(text) | |
html = encode_and_link_urls(text) | |
html = simple_format(html, {}, sanitize: false) | |
@@ -155,7 +172,8 @@ | |
def count_tag_nesting(tag) | |
if tag[1] == '/' then -1 | |
elsif tag[-2] == '/' then 0 | |
- else 1 | |
+ else | |
+ 1 | |
end | |
end | |
@@ -282,8 +300,11 @@ | |
end | |
def link_to_url(entity, options = {}) | |
- url = Addressable::URI.parse(entity[:url]) | |
- | |
+ url = if options[:external_links] && (link_id = options[:external_links][entity[:url]]&.id) | |
+ link_url(link_id, subdomain: 'links') | |
+ else | |
+ Addressable::URI.parse(entity[:url]) | |
+ end | |
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' } | |
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me] | |
Only in truth-new/opensource/app/lib: hostile_rate_limiter.rb | |
diff -ru truth-old/opensource/app/lib/inline_renderer.rb truth-new/opensource/app/lib/inline_renderer.rb | |
--- truth-old/opensource/app/lib/inline_renderer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/inline_renderer.rb 2024-04-01 14:59:13 | |
@@ -21,11 +21,13 @@ | |
serializer = REST::ReactionSerializer | |
when :encrypted_message | |
serializer = REST::EncryptedMessageSerializer | |
+ when :chat | |
+ serializer = REST::ChatSerializer | |
else | |
return | |
end | |
- serializable_resource = ActiveModelSerializers::SerializableResource.new(@object, serializer: serializer, scope: current_user, scope_name: :current_user) | |
+ serializable_resource = ActiveModelSerializers::SerializableResource.new(@object, serializer: serializer, scope: current_user, scope_name: :current_user, current_user: @current_account&.user) | |
serializable_resource.as_json | |
end | |
Only in truth-new/opensource/app/lib: interactions_tracker.rb | |
Only in truth-old/opensource/app/lib: potential_friendship_tracker.rb | |
diff -ru truth-old/opensource/app/lib/prometheus/application_exporter.rb truth-new/opensource/app/lib/prometheus/application_exporter.rb | |
--- truth-old/opensource/app/lib/prometheus/application_exporter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/prometheus/application_exporter.rb 2024-04-01 14:59:13 | |
@@ -18,19 +18,37 @@ | |
follows: 'number of accounts following account', | |
unfollows: 'number of accounts unfollowing accounts', | |
links: 'number of posted links', | |
- approves: 'number of approved users' | |
+ approves: 'number of approved users', | |
+ ad_impressions: 'number of ad impressions', | |
+ chats: 'number of chats', | |
+ chat_messages: 'number of chat messages', | |
} | |
+ @histogram_instances = {} | |
+ histogram_metrics = { | |
+ video_passthrough_encoding: 'duration for processing passthrough video encoding', | |
+ } | |
+ | |
prometheus_client = PrometheusExporter::Client.default | |
counter_metrics.each do |key, value| | |
@counter_instances[key] = prometheus_client.register(:counter, key, value) | |
end | |
+ histogram_metrics.each do |key, value| | |
+ @histogram_instances[key] = prometheus_client.register(:histogram, key, value) | |
+ end | |
+ | |
def increment(metric, labels = {}) | |
return if Rails.env.test? || Rails.env.development? | |
- @counter_instances[metric]&.increment(labels) | |
+ @counter_instances[metric]&.increment(labels) | |
+ end | |
+ | |
+ def observe_duration(metric, duration, labels = {}) | |
+ return if Rails.env.test? || Rails.env.development? | |
+ | |
+ @histogram_instances[metric]&.observe(duration, labels) | |
end | |
end | |
-end | |
\ No newline at end of file | |
+end | |
Only in truth-new/opensource/app/lib: queue_manager.rb | |
diff -ru truth-old/opensource/app/lib/rate_limiter.rb truth-new/opensource/app/lib/rate_limiter.rb | |
--- truth-old/opensource/app/lib/rate_limiter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/rate_limiter.rb 2023-11-29 12:30:01 | |
@@ -5,7 +5,7 @@ | |
FAMILIES = { | |
follows: { | |
- limit: 400, | |
+ limit: 250, | |
period: 24.hours.freeze, | |
}.freeze, | |
@@ -23,8 +23,8 @@ | |
def initialize(by, options = {}) | |
@by = by | |
@family = options[:family] | |
- @limit = FAMILIES[@family][:limit] | |
- @period = FAMILIES[@family][:period].to_i | |
+ @limit = self.class::FAMILIES[@family][:limit] | |
+ @period = self.class::FAMILIES[@family][:period].to_i | |
end | |
def record! | |
@@ -32,16 +32,21 @@ | |
if count.nil? | |
redis.set(key, 0) | |
+ count = 0 | |
redis.expire(key, (@period - (last_epoch_time % @period) + 1).to_i) | |
end | |
- raise Mastodon::RateLimitExceededError, "Rate limit hit by RateLimiter #{@family}" if count.present? && count.to_i >= @limit && ENV['SKIP_IP_RATE_LIMITING'] != 'true' | |
+ raise error, "Rate limit hit by #{self.class.name} #{@family}" if count.to_i >= @limit && ENV['SKIP_IP_RATE_LIMITING'] != 'true' | |
redis.incr(key) | |
end | |
def rollback! | |
redis.decr(key) | |
+ end | |
+ | |
+ def error | |
+ Mastodon::RateLimitExceededError | |
end | |
def to_headers(now = Time.now.utc) | |
diff -ru truth-old/opensource/app/lib/request.rb truth-new/opensource/app/lib/request.rb | |
--- truth-old/opensource/app/lib/request.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/request.rb 2024-04-01 14:59:13 | |
@@ -12,6 +12,27 @@ | |
@socket = socket_class.open(host, port) | |
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay | |
end | |
+ | |
+ def reset_counter | |
+ @deadline = nil | |
+ end | |
+ | |
+ def readpartial(size, buffer = nil) | |
+ @deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + @read_timeout | |
+ | |
+ timeout = false | |
+ loop do | |
+ result = @socket.read_nonblock(size, buffer, exception: false) | |
+ | |
+ return :eof if result.nil? | |
+ | |
+ remaining_time = @deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC) | |
+ raise HTTP::TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout || remaining_time <= 0 | |
+ return result if result != :wait_readable | |
+ | |
+ timeout = true unless @socket.to_io.wait_readable(remaining_time) | |
+ end | |
+ end | |
end | |
class Request | |
@@ -101,10 +122,9 @@ | |
private | |
def set_common_headers! | |
- parsed_url = Addressable::URI.parse(@url) | |
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}" | |
@headers['User-Agent'] = Mastodon::Version.user_agent | |
- @headers['Host'] = "#{@url.host}:#{parsed_url.inferred_port}" | |
+ @headers['Host'] = "#{@url.host}" | |
@headers['Date'] = Time.now.utc.httpdate | |
@headers['Accept-Encoding'] = 'gzip' if @verb != :head | |
end | |
@@ -142,7 +162,7 @@ | |
end | |
def use_proxy?(url) | |
- parsed = URI.parse(url) | |
+ parsed = URI.parse(Addressable::URI.encode(url)) | |
return false if private_address?(parsed.host) | |
Rails.configuration.x.http_client_proxy.present? | |
end | |
@@ -161,6 +181,7 @@ | |
address = Resolv.getaddress(hostname) | |
[ | |
+ IPAddr.new('127.0.0.1'), | |
IPAddr.new('10.0.0.0/8'), | |
IPAddr.new('172.16.0.0/12'), | |
IPAddr.new('192.168.0.0/16'), | |
@@ -174,7 +195,7 @@ | |
end | |
module ClientLimit | |
- def body_with_limit(limit = 1.megabyte) | |
+ def body_with_limit(limit = 4.megabyte) | |
raise Mastodon::LengthValidationError if content_length.present? && content_length > limit | |
if charset.nil? | |
diff -ru truth-old/opensource/app/lib/status_filter.rb truth-new/opensource/app/lib/status_filter.rb | |
--- truth-old/opensource/app/lib/status_filter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/status_filter.rb 2024-04-01 14:59:13 | |
@@ -3,15 +3,18 @@ | |
class StatusFilter | |
attr_reader :status, :account | |
- def initialize(status, account, preloaded_relations = {}) | |
+ def initialize(status, account, preloaded_relations = {}, root_status = nil, urls = [], marketing_push_notification = false) | |
@status = status | |
@account = account | |
@preloaded_relations = preloaded_relations | |
+ @root_status = root_status | |
+ @urls = urls | |
+ @marketing_push_notification = marketing_push_notification | |
end | |
def filtered? | |
- return false if !account.nil? && account.id == status.account_id | |
- blocked_by_policy? || (account_present? && filtered_status?) || silenced_account? | |
+ return false if !account.nil? && account.id == status.account_id && !deleted_status? | |
+ blocked_by_policy? || (account_present? && filtered_status?) || silenced_account? || privatized_status? || contains_recent_link? || contains_bad_link? || deleted_status? | |
end | |
private | |
@@ -54,5 +57,30 @@ | |
def policy_allows_show? | |
StatusPolicy.new(account, status, @preloaded_relations).show? | |
+ end | |
+ | |
+ def privatized_status? | |
+ status.visibility == 'self' && status.account != account | |
+ end | |
+ | |
+ def contains_recent_link? | |
+ return unless @root_status | |
+ time_difference = (Time.now - status.created_at).round | |
+ delay_minutes = @marketing_push_notification ? 900 : 300 | |
+ | |
+ @urls.any? && !status.account.whale? && status.account != account && time_difference < delay_minutes | |
+ end | |
+ | |
+ def contains_bad_link? | |
+ return unless @root_status && @marketing_push_notification && @urls.any? | |
+ | |
+ time_difference = (status.created_at - @marketing_push_notification.created_at).round | |
+ return if time_difference > 6.hours.to_i | |
+ | |
+ !status.account.whale? && status.account != account | |
+ end | |
+ | |
+ def deleted_status? | |
+ status.deleted_at? | |
end | |
end | |
diff -ru truth-old/opensource/app/lib/status_finder.rb truth-new/opensource/app/lib/status_finder.rb | |
--- truth-old/opensource/app/lib/status_finder.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/lib/status_finder.rb 2024-04-01 14:59:13 | |
@@ -3,8 +3,9 @@ | |
class StatusFinder | |
attr_reader :url | |
- def initialize(url) | |
+ def initialize(url, allow_activity: false) | |
@url = url | |
+ @allowed_actions = allow_activity ? ['show', 'activity'] : ['show'] | |
end | |
def status | |
@@ -27,8 +28,6 @@ | |
end | |
def verify_action! | |
- unless recognized_params[:action] == 'show' | |
- raise ActiveRecord::RecordNotFound | |
- end | |
+ raise ActiveRecord::RecordNotFound unless @allowed_actions.include?(recognized_params[:action]) | |
end | |
end | |
Only in truth-old/opensource/app/lib: status_reach_finder.rb | |
Only in truth-new/opensource/app/lib: url_placeholder.rb | |
diff -ru truth-old/opensource/app/mailers/notification_mailer.rb truth-new/opensource/app/mailers/notification_mailer.rb | |
--- truth-old/opensource/app/mailers/notification_mailer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/mailers/notification_mailer.rb 2024-04-01 14:59:13 | |
@@ -42,9 +42,16 @@ | |
return unless @me.user.functional? && @status.present? | |
+ subject = | |
+ if notification.count | |
+ I18n.t('notification_mailer.favourite_group.subject', name: @account.acct, count_others: notification.count - 1, actor: "others") | |
+ else | |
+ I18n.t('notification_mailer.favourite.subject', name: @account.acct) | |
+ end | |
+ | |
locale_for_account(@me) do | |
thread_by_conversation(@status.conversation) | |
- mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) | |
+ mail to: @me.user.email, subject: subject | |
end | |
end | |
@@ -103,7 +110,7 @@ | |
return unless @resource.active_for_authentication? | |
I18n.with_locale(@resource.locale || I18n.default_locale) do | |
- mail to: @resource.email, subject: I18n.t('notification_mailer.user_approved.web.subject') | |
+ mail to: @resource.email, subject: I18n.t('notification_mailer.user_approved.title', name: @resource.account.username) | |
end | |
end | |
diff -ru truth-old/opensource/app/models/account.rb truth-new/opensource/app/models/account.rb | |
--- truth-old/opensource/app/models/account.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/account.rb 2024-04-05 09:07:34 | |
@@ -3,7 +3,6 @@ | |
# | |
# Table name: accounts | |
# | |
-# id :bigint(8) not null, primary key | |
# username :string default(""), not null | |
# domain :string | |
# private_key :text | |
@@ -31,6 +30,7 @@ | |
# shared_inbox_url :string default(""), not null | |
# followers_url :string default(""), not null | |
# protocol :integer default("ostatus"), not null | |
+# id :bigint(8) not null, primary key | |
# memorial :boolean default(FALSE), not null | |
# moved_to_account_id :bigint(8) | |
# featured_collection_url :string | |
@@ -52,6 +52,14 @@ | |
# location :text default(""), not null | |
# website :text default(""), not null | |
# whale :boolean default(FALSE) | |
+# interactions_score :integer | |
+# file_s3_host :string(64) | |
+# accepting_messages :boolean default(TRUE), not null | |
+# chats_onboarded :boolean default(FALSE), not null | |
+# feeds_onboarded :boolean default(FALSE), not null | |
+# show_nonmember_group_statuses :boolean default(TRUE), not null | |
+# tv_onboarded :boolean default(FALSE), not null | |
+# receive_only_follow_mentions :boolean default(FALSE), not null | |
# | |
class Account < ApplicationRecord | |
@@ -63,6 +71,8 @@ | |
hub_url | |
) | |
+ attribute :message_expiration, :interval | |
+ | |
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i | |
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[a-z0-9]+)?)/i | |
@@ -83,6 +93,7 @@ | |
TRUST_LEVELS = { | |
untrusted: 0, | |
trusted: 1, | |
+ hostile: -1, | |
}.freeze | |
enum protocol: [:ostatus, :activitypub] | |
@@ -100,9 +111,15 @@ | |
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? } | |
validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? } | |
validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? } | |
+ validates :location, length: { maximum: 500 }, if: -> { local? && will_save_change_to_location? } | |
+ validates :url, length: { maximum: 500 }, if: -> { local? && will_save_change_to_url? } | |
+ validates :website, length: { maximum: 500 }, if: -> { local? && will_save_change_to_website? } | |
validate :check_website_field_for_javascript | |
+ after_update_commit :invalidate_statuses, if: -> { saved_change_to_username? } | |
+ after_update_commit :invalidate_ads_cache | |
+ | |
scope :remote, -> { where.not(domain: nil) } | |
scope :local, -> { where(domain: nil) } | |
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } | |
@@ -122,12 +139,14 @@ | |
scope :searchable, -> { without_suspended.where(moved_to_account_id: nil) } | |
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) } | |
scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) } | |
- scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) } | |
+ # TODO: evaluate last_status_at/current_sign_in_at nulls last instead of case statements | |
+ scope :by_recent_status, -> { order(Arel.sql('(case when last_status_at is null then 1 else 0 end) asc, last_status_at desc, accounts.id desc')) } | |
scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) } | |
scope :popular, -> { order('account_stats.followers_count desc') } | |
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) } | |
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) } | |
scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) } | |
+ scope :excluded_by_group_account_block, ->(group_id) { where.not(GroupAccountBlock.where('group_account_blocks.account_id = accounts.id').where('group_account_blocks.group_id = ?', group_id).arel.exists) } | |
delegate :email, | |
:unconfirmed_email, | |
@@ -145,14 +164,17 @@ | |
:locale, | |
:hides_network?, | |
:shows_application?, | |
+ :sms, | |
to: :user, | |
prefix: true, | |
allow_nil: true | |
delegate :chosen_languages, to: :user, prefix: false, allow_nil: true | |
- update_index 'accounts#account', :self | |
+ attr_accessor :seen | |
+ update_index 'accounts', :self | |
+ | |
def contains_prohibited_terms? | |
user_and_display_name_downcase = "#{username} #{display_name}".downcase | |
Status::PROHIBITED_TERMS_ON_INDEX.any? { |term| user_and_display_name_downcase.include? term } | |
@@ -203,11 +225,11 @@ | |
end | |
def to_webfinger_s | |
- "acct:#{local_username_and_domain}" | |
+ "acct:#{username}@#{Rails.configuration.x.local_domain}" | |
end | |
def searchable? | |
- !(suspended? || moved?) | |
+ !moved? | |
end | |
def possibly_stale? | |
@@ -252,25 +274,33 @@ | |
update!(suspended_at: date, suspension_origin: origin) | |
end | |
create_canonical_email_block! | |
+ InteractionsTracker.new(id).remove_total_score | |
end | |
def unsuspend! | |
transaction do | |
deletion_request&.destroy! | |
update!(suspended_at: nil, suspension_origin: nil) | |
+ user&.enable! | |
destroy_canonical_email_block! | |
end | |
end | |
+ def deleted? | |
+ user.nil? | |
+ end | |
+ | |
def verify! | |
transaction do | |
update!(verified: true) | |
+ user.update!(unauth_visibility: true) | |
end | |
end | |
def unverify! | |
transaction do | |
update!(verified: false) | |
+ user.update!(unauth_visibility: false) | |
end | |
end | |
@@ -294,6 +324,14 @@ | |
update!(memorial: true) | |
end | |
+ def accept_messages! | |
+ update!(accepting_messages: true) | |
+ end | |
+ | |
+ def unaccept_messages! | |
+ update!(accepting_messages: false) | |
+ end | |
+ | |
def sign? | |
true | |
end | |
@@ -350,6 +388,10 @@ | |
self[:fields] = fields | |
end | |
+ def account_fields | |
+ (self[:fields] || []).map { |f| AccountField.new(self, f) } | |
+ end | |
+ | |
DEFAULT_FIELDS_SIZE = 4 | |
def build_fields | |
@@ -391,7 +433,7 @@ | |
end | |
def check_website_field_for_javascript | |
- errors.add(:base, "Please enter a valid website") if JAVASCRIPT_RE.match(website) | |
+ errors.add(:base, 'Please enter a valid website') if JAVASCRIPT_RE.match(website) | |
end | |
# TODO: follow_requests profile feature toggle "locked" | |
@@ -430,7 +472,14 @@ | |
WhaleCacheInvalidationWorker.perform_async(id) | |
end | |
+ def tv_enabled? | |
+ feature_enabled? 'tv' | |
+ end | |
+ def for_you_enabled? | |
+ feature_enabled? 'for_you' | |
+ end | |
+ | |
class Field < ActiveModelSerializers::Model | |
attributes :name, :value, :verified_at, :account | |
@@ -471,6 +520,45 @@ | |
end | |
end | |
+ class AccountField | |
+ attr_reader :name, :value, :account | |
+ attr_accessor :verified_at | |
+ | |
+ def initialize(account, attributes) | |
+ @original_field = attributes | |
+ string_limit = account.local? ? 255 : 2047 | |
+ @account = account | |
+ @name = attributes['name'].strip[0, string_limit] | |
+ @value = attributes['value'].strip[0, string_limit] | |
+ @verified_at = attributes['verified_at']&.to_datetime | |
+ end | |
+ | |
+ def verified? | |
+ verified_at.present? | |
+ end | |
+ | |
+ def value_for_verification | |
+ @value_for_verification ||= if account.local? | |
+ value | |
+ else | |
+ ActionController::Base.helpers.strip_tags(value) | |
+ end | |
+ end | |
+ | |
+ def verifiable? | |
+ value_for_verification.present? && value_for_verification.start_with?('http://', 'https://') | |
+ end | |
+ | |
+ def mark_verified! | |
+ self.verified_at = Time.now.utc | |
+ @original_field['verified_at'] = verified_at | |
+ end | |
+ | |
+ def to_h | |
+ { name: name, value: value, verified_at: verified_at } | |
+ end | |
+ end | |
+ | |
class << self | |
def readonly_attributes | |
super - %w(statuses_count following_count followers_count) | |
@@ -484,13 +572,13 @@ | |
def ci_find_by_username(username = nil) | |
return nil unless username.present? | |
- includes(:user).where("LOWER(username) = ?", username.downcase).take | |
+ includes(:user).find_by('LOWER(username) = ?', username.downcase) | |
end | |
def ci_find_by_usernames(usernames = []) | |
return Account.none if usernames.empty? | |
- where("LOWER(username) IN (?)", usernames.compact.map { |un| un.downcase }) | |
+ where('LOWER(username) IN (?)', usernames.compact.map { |un| un.downcase }) | |
end | |
def search_for(terms, limit = 10, offset = 0) | |
@@ -509,7 +597,7 @@ | |
SQL | |
records = find_by_sql([sql, limit, offset]) | |
- ActiveRecord::Associations::Preloader.new.preload(records, :account_stat) | |
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status, :tv_channel_account]) | |
records | |
end | |
@@ -558,7 +646,7 @@ | |
records = find_by_sql([sql, account.id, account.id, limit, offset]) | |
end | |
- ActiveRecord::Associations::Preloader.new.preload(records, :account_stat) | |
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status, :tv_channel_account]) | |
records | |
end | |
@@ -591,7 +679,31 @@ | |
@emojis ||= CustomEmoji.from_text(emojifiable_text, domain) | |
end | |
+ def recent_ads | |
+ statuses.where('created_at > ?', 1.month.ago) | |
+ .where(in_reply_to_id: nil) | |
+ .where(Ad.where('statuses.id = ads.status_id').arel.exists) | |
+ end | |
+ | |
+ # Identifies the accounts that have advertised recently based on a list of account_ids. | |
+ # | |
+ # @param [Array<Integer>] account_ids The list of account IDs to check. | |
+ # @return [Array<Integer>] Returns an array containing the account IDs that have advertised recently. | |
+ # | |
+ def self.recent_advertisers(account_ids, recently = 1.month.ago) | |
+ Status | |
+ .select(:account_id) | |
+ .where(account_id: account_ids) | |
+ .where('statuses.created_at > ?', recently) | |
+ .where(in_reply_to_id: nil) | |
+ .joins(:ad) | |
+ .group(:account_id) | |
+ .reorder('') | |
+ .pluck(:account_id) | |
+ end | |
+ | |
before_create :generate_keys | |
+ before_save :set_file_s3_host, if: -> { will_save_change_to_avatar_file_name? || will_save_change_to_header_file_name? } | |
before_validation :prepare_contents, if: :local? | |
before_validation :prepare_username, on: :create | |
before_destroy :clean_feed_manager | |
@@ -631,6 +743,7 @@ | |
def create_canonical_email_block! | |
return unless local? && user_email.present? | |
+ return if CanonicalEmailBlock.block?(user_email) | |
CanonicalEmailBlock.create(reference_account: self, email: user_email) | |
rescue ActiveRecord::RecordNotUnique | |
@@ -641,5 +754,38 @@ | |
return unless local? | |
CanonicalEmailBlock.where(reference_account: self).delete_all | |
+ end | |
+ | |
+ def set_file_s3_host | |
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name] | |
+ end | |
+ | |
+ def invalidate_statuses | |
+ InvalidateAccountStatusesWorker.perform_async(id) | |
+ end | |
+ | |
+ def invalidate_ads_cache | |
+ InvalidateAdsAccountsWorker.perform_async(id) if OauthAccessToken.exists?(resource_owner_id: user&.id, scopes: 'ads') | |
+ end | |
+ | |
+ def fields_changed(account) | |
+ updatable_fields = %w(bio display_name avatar_url header_url followers_count following_count website location username verified) | |
+ changed_fields = account.saved_changes.keys | |
+ | |
+ updated_fields = changed_fields.select { |f| updatable_fields.include?(f) } | |
+ updated_fields << 'avatar_url' if changed_fields.include?('avatar_file_name') | |
+ updated_fields << 'header_url' if changed_fields.include?('header_file_name') | |
+ updated_fields.map(&:upcase) | |
+ end | |
+ | |
+ def feature_enabled?(feature) | |
+ feature_flag = ::Configuration::FeatureFlag | |
+ .joins("LEFT JOIN configuration.account_enabled_features ON configuration.feature_flags.feature_flag_id = configuration.account_enabled_features.feature_flag_id AND configuration.account_enabled_features.account_id = #{id}") | |
+ .where(name: feature) | |
+ .select('configuration.feature_flags.name, configuration.feature_flags.status, configuration.account_enabled_features.account_id') | |
+ .to_a | |
+ .first | |
+ | |
+ feature_flag&.enabled? == true || (feature_flag&.account_based? == true && !feature_flag&.account_id.nil?) | |
end | |
end | |
diff -ru truth-old/opensource/app/models/account_domain_block.rb truth-new/opensource/app/models/account_domain_block.rb | |
--- truth-old/opensource/app/models/account_domain_block.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/account_domain_block.rb 2024-04-01 14:59:13 | |
@@ -27,6 +27,6 @@ | |
end | |
def remove_relationship_cache | |
- Rails.cache.delete_matched("relationship:#{account_id}:*") | |
+ Rails.cache.delete_matched("relationship/#{account_id}/*") | |
end | |
end | |
Only in truth-new/opensource/app/models: account_follower_statistic.rb | |
Only in truth-new/opensource/app/models: account_following_statistic.rb | |
diff -ru truth-old/opensource/app/models/account_stat.rb truth-new/opensource/app/models/account_stat.rb | |
--- truth-old/opensource/app/models/account_stat.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/account_stat.rb 2024-04-01 14:59:13 | |
@@ -18,7 +18,7 @@ | |
belongs_to :account, inverse_of: :account_stat | |
- update_index('accounts#account', :account) | |
+ update_index('accounts', :account) | |
def following_count | |
[attributes['following_count'], 0].max | |
@@ -31,4 +31,4 @@ | |
def statuses_count | |
[attributes['statuses_count'], 0].max | |
end | |
-end | |
+end | |
\ No newline at end of file | |
Only in truth-new/opensource/app/models: account_status_statistic.rb | |
diff -ru truth-old/opensource/app/models/account_suggestions.rb truth-new/opensource/app/models/account_suggestions.rb | |
--- truth-old/opensource/app/models/account_suggestions.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/account_suggestions.rb 2024-04-01 14:59:13 | |
@@ -2,24 +2,17 @@ | |
class AccountSuggestions | |
SOURCES = [ | |
- AccountSuggestions::SettingSource, | |
- AccountSuggestions::PastInteractionsSource, | |
- AccountSuggestions::GlobalSource, | |
+ {klass: AccountSuggestions::SettingSource, limit: 300}, | |
+ {klass: AccountSuggestions::PastInteractionsSource, limit: 25}, | |
+ {klass: AccountSuggestions::GlobalSource, limit: 25} | |
].freeze | |
- # Since we iterate through 3 arrays, this number is the max # of suggestions that will be returned | |
- # Ex: if the total limit is 120 and the client requests 5 at a time, the total # of pages that can be returned is 24 | |
- TOTAL_RESULTS_LIMIT = 150 | |
- | |
- # The total limit divided by the # of sources | |
- ARRAY_LIMIT = TOTAL_RESULTS_LIMIT / SOURCES.length.floor | |
- | |
def self.get(account) | |
- SOURCES.each_with_object([]) do |source_class, suggestions| | |
- source_suggestions = source_class.new.get( | |
+ SOURCES.each_with_object([]) do |obj, suggestions| | |
+ source_suggestions = obj[:klass].new.get( | |
account, | |
skip_account_ids: suggestions.map(&:account_id), | |
- limit: ARRAY_LIMIT | |
+ limit: obj[:limit] | |
) | |
suggestions.concat(source_suggestions) | |
@@ -27,8 +20,8 @@ | |
end | |
def self.remove(account, target_account_id) | |
- SOURCES.each do |source_class| | |
- source = source_class.new | |
+ SOURCES.each do |obj| | |
+ source = obj[:klass].new | |
source.remove(account, target_account_id) | |
end | |
end | |
Only in truth-new/opensource/app/models: ad.rb | |
Only in truth-new/opensource/app/models: ad_attribution.rb | |
diff -ru truth-old/opensource/app/models/admin/account_action.rb truth-new/opensource/app/models/admin/account_action.rb | |
--- truth-old/opensource/app/models/admin/account_action.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/admin/account_action.rb 2024-04-01 14:59:13 | |
@@ -19,6 +19,9 @@ | |
unsuspend | |
verify | |
unverify | |
+ enable_sms_reverification | |
+ disable_sms_reverification | |
+ enable_feature | |
).freeze | |
attr_accessor :target_account, | |
@@ -26,7 +29,8 @@ | |
:type, | |
:text, | |
:report_id, | |
- :warning_preset_id | |
+ :warning_preset_id, | |
+ :feature_name | |
attr_reader :warning, :send_email_notification, :include_statuses, :duration | |
@@ -54,7 +58,6 @@ | |
end | |
process_email! | |
- process_reports! | |
process_queue! | |
end | |
@@ -106,6 +109,12 @@ | |
handle_remove_avatar! | |
when 'remove_header' | |
handle_remove_header! | |
+ when 'enable_sms_reverification' | |
+ handle_enable_sms_reverification! | |
+ when 'disable_sms_reverification' | |
+ handle_disable_sms_reverification! | |
+ when 'enable_feature' | |
+ handle_enable_feature! | |
end | |
end | |
@@ -152,18 +161,24 @@ | |
log_action(:ban, target_account.user) | |
target_account.suspend! | |
target_account.user.disable! | |
+ remove_scheduled_unsuspensions | |
+ InteractionsTracker.new(target_account).remove_total_score | |
+ DisabledUserUnfollowWorker.perform_async(target_account.id) | |
+ revoke_access_tokens(target_account) | |
end | |
def handle_disable! | |
authorize(target_account.user, :disable?) | |
log_action(:disable, target_account.user) | |
target_account.user&.disable! | |
+ DisabledUserUnfollowWorker.perform_in(7.days, target_account.id) | |
end | |
def handle_enable! | |
authorize(target_account.user, :enable?) | |
log_action(:enable, target_account.user) | |
target_account.user&.enable! | |
+ DisabledUserRefollowWorker.perform_async(target_account.id) | |
end | |
def handle_sensitive! | |
@@ -188,6 +203,7 @@ | |
authorize(target_account, :suspend?) | |
log_action(:suspend, target_account) | |
target_account.suspend!(origin: :local) | |
+ InteractionsTracker.new(target_account).remove_total_score | |
schedule_unsuspension! unless account_suspension_policy.strikes_expended? | |
end | |
@@ -196,6 +212,7 @@ | |
authorize(target_account, :unsuspend?) | |
log_action(:unsuspend, target_account) | |
target_account.unsuspend! | |
+ DisabledUserRefollowWorker.perform_async(target_account.id) | |
end | |
def handle_verify! | |
@@ -224,16 +241,37 @@ | |
target_account.save! | |
end | |
- def text_for_warning | |
- [warning_preset&.text, text].compact.join("\n\n") | |
+ def handle_enable_sms_reverification! | |
+ authorize(target_account.user, :enable_sms_reverification?) | |
+ log_action(:enable_sms_reverification, target_account.user) | |
+ user = target_account.user | |
+ UserSmsReverificationRequired.create(user: user) if user | |
end | |
- def queue_suspension_worker! | |
- Admin::SuspensionWorker.perform_async(target_account.id) | |
+ def handle_disable_sms_reverification! | |
+ authorize(target_account.user, :disable_sms_reverification?) | |
+ log_action(:disable_sms_reverification, target_account.user) | |
+ user = target_account.user | |
+ UserSmsReverificationRequired.find(user.id)&.destroy if user | |
end | |
+ def handle_enable_feature! | |
+ authorize(target_account.user, :enable_feature?) | |
+ feature_flag = ::Configuration::FeatureFlag.find_by!(name: feature_name) | |
+ ::Configuration::AccountEnabledFeature.create!(account_id: target_account.id, feature_flag: feature_flag) | |
+ end | |
+ | |
+ def text_for_warning | |
+ [warning_preset&.text, text].compact.join("\n\n") | |
+ end | |
+ | |
def process_queue! | |
- queue_suspension_worker! if type == 'suspend' | |
+ case type | |
+ when 'suspend', 'ban' | |
+ Admin::SuspensionWorker.perform_async(target_account.id) | |
+ when 'unsuspend' | |
+ Admin::UnsuspensionWorker.perform_async(target_account.id) | |
+ end | |
end | |
def process_email! | |
@@ -272,5 +310,15 @@ | |
def account_suspension_policy | |
@account_suspension_policy ||= AccountSuspensionPolicy.new(target_account) | |
+ end | |
+ | |
+ def remove_scheduled_unsuspensions | |
+ queue = Sidekiq::ScheduledSet.new | |
+ jobs = queue.select { |job| job.klass == 'Admin::UnsuspensionWorker' && job.args[0] == target_account.id } | |
+ jobs.each(&:delete) | |
+ end | |
+ | |
+ def revoke_access_tokens(target_account) | |
+ OauthAccessToken.where(resource_owner_id: target_account.user.id).update_all(revoked_at: Time.now.utc) | |
end | |
end | |
diff -ru truth-old/opensource/app/models/announcement.rb truth-new/opensource/app/models/announcement.rb | |
--- truth-old/opensource/app/models/announcement.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/announcement.rb 2024-04-01 14:59:13 | |
@@ -77,7 +77,7 @@ | |
end | |
end | |
- ActiveRecord::Associations::Preloader.new.preload(records, :custom_emoji) | |
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status]) | |
records | |
end | |
diff -ru truth-old/opensource/app/models/application_record.rb truth-new/opensource/app/models/application_record.rb | |
--- truth-old/opensource/app/models/application_record.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/application_record.rb 2024-04-01 14:59:13 | |
@@ -7,7 +7,7 @@ | |
class << self | |
def update_index(_type_name, *_args, &_block) | |
- super if Chewy.enabled? | |
+ super if Chewy.indexing_enabled? | |
end | |
end | |
Only in truth-new/opensource/app/models: avatars_carousel.rb | |
Only in truth-new/opensource/app/models: banned_word.rb | |
Only in truth-new/opensource/app/models: blocked_link.rb | |
diff -ru truth-old/opensource/app/models/bookmark.rb truth-new/opensource/app/models/bookmark.rb | |
--- truth-old/opensource/app/models/bookmark.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/bookmark.rb 2024-04-01 14:59:13 | |
@@ -13,7 +13,7 @@ | |
class Bookmark < ApplicationRecord | |
include Paginable | |
- update_index('statuses#status', :status) if Chewy.enabled? | |
+ update_index('statuses', :status) if Chewy.indexing_enabled? | |
belongs_to :account, inverse_of: :bookmarks | |
belongs_to :status, inverse_of: :bookmarks | |
Only in truth-new/opensource/app/models: chat.rb | |
Only in truth-new/opensource/app/models: chat_event.rb | |
Only in truth-new/opensource/app/models: chat_member.rb | |
Only in truth-new/opensource/app/models: chat_member_removal.rb | |
Only in truth-new/opensource/app/models: chat_message.rb | |
Only in truth-new/opensource/app/models: chat_message_hidden.rb | |
Only in truth-new/opensource/app/models: chat_message_reaction.rb | |
Only in truth-new/opensource/app/models: chat_search_result.rb | |
Only in truth-new/opensource/app/models: city.rb | |
diff -ru truth-old/opensource/app/models/concerns/account_associations.rb truth-new/opensource/app/models/concerns/account_associations.rb | |
--- truth-old/opensource/app/models/concerns/account_associations.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/account_associations.rb 2024-04-12 09:09:08 | |
@@ -30,7 +30,7 @@ | |
# Media | |
has_many :media_attachments, dependent: :destroy | |
- has_many :polls, dependent: :destroy | |
+ has_many :polls, dependent: :destroy, through: :statuses | |
# Report relationships | |
has_many :reports, dependent: :destroy, inverse_of: :account | |
@@ -66,5 +66,27 @@ | |
# Follow recommendations | |
has_one :follow_recommendation_suppression, inverse_of: :account, dependent: :destroy | |
+ | |
+ # Chats | |
+ has_many :chat_accounts | |
+ has_many :chats, -> { distinct }, through: :chat_accounts | |
+ has_many :chat_messages | |
+ | |
+ # Groups | |
+ has_many :group_memberships | |
+ has_many :group_mutes | |
+ | |
+ has_one :tv_account | |
+ has_one :tv_channel_account | |
+ has_and_belongs_to_many :tv_channels, join_table: 'tv.channel_accounts', association_foreign_key: 'channel_id', inverse_of: :account | |
+ # Feeds | |
+ has_many :account_feeds, class_name: 'Feeds::AccountFeed' | |
+ has_many :feeds, through: :account_feeds | |
+ | |
+ # Recommendation suppressions | |
+ has_many :group_recommendation_suppressions, class_name: 'Recommendations::GroupSuppression' | |
+ has_many :account_recommendation_suppressions, class_name: 'Recommendations::AccountSuppression' | |
+ # Features | |
+ has_and_belongs_to_many :feature_flags, class_name: 'Configuration::FeatureFlag', join_table: 'configuration.account_enabled_features', association_foreign_key: 'feature_flag_id', inverse_of: :account | |
end | |
end | |
diff -ru truth-old/opensource/app/models/concerns/account_counters.rb truth-new/opensource/app/models/concerns/account_counters.rb | |
--- truth-old/opensource/app/models/concerns/account_counters.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/account_counters.rb 2024-04-01 14:59:13 | |
@@ -3,85 +3,30 @@ | |
module AccountCounters | |
extend ActiveSupport::Concern | |
- ALLOWED_COUNTER_KEYS = %i(statuses_count following_count followers_count).freeze | |
- | |
included do | |
- has_one :account_stat, inverse_of: :account | |
- after_save :save_account_stat | |
+ has_one :account_follower, class_name: AccountFollowerStatistic.name, inverse_of: :account | |
+ has_one :account_following, class_name: AccountFollowingStatistic.name, inverse_of: :account | |
+ has_one :account_status, class_name: AccountStatusStatistic.name, inverse_of: :account | |
+ | |
end | |
- delegate :statuses_count, | |
- :statuses_count=, | |
- :following_count, | |
- :following_count=, | |
- :followers_count, | |
- :followers_count=, | |
- :last_status_at, | |
- to: :account_stat | |
- | |
- # @param [Symbol] key | |
- def increment_count!(key) | |
- update_count!(key, 1) | |
+ def followers_count | |
+ account_follower&.followers_count || 0 | |
end | |
- # @param [Symbol] key | |
- def decrement_count!(key) | |
- update_count!(key, -1) | |
+ def following_count | |
+ account_following&.following_count || 0 | |
end | |
- # @param [Symbol] key | |
- # @param [Integer] value | |
- def update_count!(key, value) | |
- raise ArgumentError, "Invalid key #{key}" unless ALLOWED_COUNTER_KEYS.include?(key) | |
- raise ArgumentError, 'Do not call update_count! on dirty objects' if association(:account_stat).loaded? && account_stat&.changed? && account_stat.changed_attribute_names_to_save == %w(id) | |
- | |
- value = value.to_i | |
- default_value = value.positive? ? value : 0 | |
- | |
- # We do an upsert using manually written SQL, as Rails' upsert method does | |
- # not seem to support writing expressions in the UPDATE clause, but only | |
- # re-insert the provided values instead. | |
- # Even ARel seem to be missing proper handling of upserts. | |
- sql = if value.positive? && key == :statuses_count | |
- <<-SQL.squish | |
- INSERT INTO account_stats(account_id, #{key}, created_at, updated_at, last_status_at) | |
- VALUES (:account_id, :default_value, now(), now(), now()) | |
- ON CONFLICT (account_id) DO UPDATE | |
- SET #{key} = account_stats.#{key} + :value, | |
- last_status_at = now(), | |
- updated_at = now() | |
- RETURNING id; | |
- SQL | |
- else | |
- <<-SQL.squish | |
- INSERT INTO account_stats(account_id, #{key}, created_at, updated_at) | |
- VALUES (:account_id, :default_value, now(), now()) | |
- ON CONFLICT (account_id) DO UPDATE | |
- SET #{key} = account_stats.#{key} + :value, | |
- updated_at = now() | |
- RETURNING id; | |
- SQL | |
- end | |
- | |
- sql = AccountStat.sanitize_sql([sql, account_id: id, default_value: default_value, value: value]) | |
- account_stat_id = AccountStat.connection.exec_query(sql)[0]['id'] | |
- | |
- # Reload account_stat if it was loaded, taking into account newly-created unsaved records | |
- if association(:account_stat).loaded? | |
- account_stat.id = account_stat_id if account_stat.new_record? | |
- account_stat.reload | |
- end | |
+ def statuses_count | |
+ account_status&.statuses_count || 0 | |
end | |
- def account_stat | |
- super || build_account_stat | |
+ def last_status_at | |
+ account_status&.last_status_at | |
end | |
- private | |
- | |
- def save_account_stat | |
- return unless association(:account_stat).loaded? && account_stat&.changed? | |
- | |
- account_stat.save | |
+ def last_following_status_at | |
+ account_status&.last_following_status_at | |
end | |
end | |
diff -ru truth-old/opensource/app/models/concerns/account_interactions.rb truth-new/opensource/app/models/concerns/account_interactions.rb | |
--- truth-old/opensource/app/models/concerns/account_interactions.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/account_interactions.rb 2024-04-01 14:59:13 | |
@@ -80,6 +80,7 @@ | |
has_many :following, -> { order('follows.id desc') }, through: :active_relationships, source: :target_account | |
has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account | |
+ has_many :followers_unordered, through: :passive_relationships, source: :account | |
# Block relationships | |
has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy | |
@@ -95,6 +96,10 @@ | |
has_many :conversation_mutes, dependent: :destroy | |
has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy | |
has_many :announcement_mutes, dependent: :destroy | |
+ | |
+ # Group relationships | |
+ has_many :group_membership_requests, dependent: :destroy | |
+ has_many :group_account_blocks, dependent: :destroy | |
end | |
def follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false, bypass_limit: false) | |
@@ -127,6 +132,7 @@ | |
def block!(other_account, uri: nil) | |
remove_potential_friendship(other_account) | |
+ remove_follower_interactions(other_account) | |
block_relationships.create_with(uri: uri) | |
.find_or_create_by!(target_account: other_account) | |
end | |
@@ -138,6 +144,7 @@ | |
mute.save! | |
remove_potential_friendship(other_account) | |
+ remove_follower_interactions(other_account) | |
# When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't. | |
if mute.hide_notifications? != notifications | |
@@ -251,26 +258,6 @@ | |
.where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago) | |
end | |
- def remote_followers_hash(url_prefix) | |
- Rails.cache.fetch("followers_hash:#{id}:#{url_prefix}") do | |
- digest = "\x00" * 32 | |
- followers.where(Account.arel_table[:uri].matches("#{url_prefix}%", false, true)).pluck_each(:uri) do |uri| | |
- Xorcist.xor!(digest, Digest::SHA256.digest(uri)) | |
- end | |
- digest.unpack('H*')[0] | |
- end | |
- end | |
- | |
- def local_followers_hash | |
- Rails.cache.fetch("followers_hash:#{id}:local") do | |
- digest = "\x00" * 32 | |
- followers.where(domain: nil).pluck_each(:username) do |username| | |
- Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username))) | |
- end | |
- digest.unpack('H*')[0] | |
- end | |
- end | |
- | |
def whale_following | |
following.where(whale: true) | |
end | |
@@ -278,7 +265,13 @@ | |
private | |
def remove_potential_friendship(other_account, mutual = false) | |
- PotentialFriendshipTracker.remove(id, other_account.id) | |
- PotentialFriendshipTracker.remove(other_account.id, id) if mutual | |
+ InteractionsTracker.new(id, other_account.id).remove | |
+ InteractionsTracker.new(other_account.id, id).remove if mutual | |
+ end | |
+ | |
+ def remove_follower_interactions(other_account) | |
+ InteractionsTracker.new(id, other_account.id, false, true).remove | |
+ Redis.current.del("avatars_carousel_list_#{id}") | |
+ InvalidateSecondaryCacheService.new.call("InvalidateAvatarsCarouselCacheWorker", id) | |
end | |
end | |
diff -ru truth-old/opensource/app/models/concerns/account_merging.rb truth-new/opensource/app/models/concerns/account_merging.rb | |
--- truth-old/opensource/app/models/concerns/account_merging.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/account_merging.rb 2024-04-01 14:59:13 | |
@@ -12,16 +12,17 @@ | |
# to check for (and skip past) uniqueness errors | |
owned_classes = [ | |
- Status, StatusPin, MediaAttachment, Poll, Report, Tombstone, Favourite, | |
+ Status, StatusPin, MediaAttachment, Report, Tombstone, Favourite, | |
Follow, FollowRequest, Block, Mute, AccountIdentityProof, | |
- AccountModerationNote, AccountPin, AccountStat, ListAccount, | |
- PollVote, Mention, AccountDeletionRequest, AccountNote, FollowRecommendationSuppression | |
+ AccountModerationNote, AccountPin, AccountFollowerStatistic, | |
+ AccountFollowingStatistic, AccountStatusStatistic, ListAccount, | |
+ Mention, AccountDeletionRequest, AccountNote, FollowRecommendationSuppression | |
] | |
owned_classes.each do |klass| | |
klass.where(account_id: other_account.id).find_each do |record| | |
record.update_attribute(:account_id, id) | |
- rescue ActiveRecord::RecordNotUnique | |
+ rescue | |
next | |
end | |
end | |
@@ -45,7 +46,7 @@ | |
# Some follow relationships have moved, so the cache is stale | |
Rails.cache.delete_matched("followers_hash:#{id}:*") | |
- Rails.cache.delete_matched("relationships:#{id}:*") | |
- Rails.cache.delete_matched("relationships:*:#{id}") | |
+ Rails.cache.delete_matched("relationships/#{id}/*") | |
+ Rails.cache.delete_matched("relationships/*/#{id}") | |
end | |
end | |
diff -ru truth-old/opensource/app/models/concerns/attachmentable.rb truth-new/opensource/app/models/concerns/attachmentable.rb | |
--- truth-old/opensource/app/models/concerns/attachmentable.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/attachmentable.rb 2024-04-01 14:59:13 | |
@@ -20,10 +20,10 @@ | |
).freeze | |
included do | |
- before_post_process :obfuscate_file_name | |
- before_post_process :set_file_extensions | |
- before_post_process :check_image_dimensions | |
- before_post_process :set_file_content_type | |
+ before_validation :obfuscate_file_name | |
+ before_validation :set_file_extensions | |
+ before_validation :check_image_dimensions | |
+ before_validation :set_file_content_type | |
end | |
private | |
Only in truth-new/opensource/app/models/concerns: group_counters.rb | |
Only in truth-new/opensource/app/models/concerns: group_relationship_cacheable.rb | |
diff -ru truth-old/opensource/app/models/concerns/paginable.rb truth-new/opensource/app/models/concerns/paginable.rb | |
--- truth-old/opensource/app/models/concerns/paginable.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/paginable.rb 2024-04-01 14:59:13 | |
@@ -28,5 +28,11 @@ | |
paginate_by_max_id(limit, options[:max_id], options[:since_id]).to_a | |
end | |
end | |
+ | |
+ def self.paginate_by_limit_offset(limit, params) | |
+ query = limit(limit) | |
+ query = query.offset(params[:offset]) if params[:offset].present? | |
+ query | |
+ end | |
end | |
end | |
Only in truth-new/opensource/app/models/concerns: queriable.rb | |
diff -ru truth-old/opensource/app/models/concerns/rate_limitable.rb truth-new/opensource/app/models/concerns/rate_limitable.rb | |
--- truth-old/opensource/app/models/concerns/rate_limitable.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/rate_limitable.rb 2023-05-05 13:42:02 | |
@@ -14,7 +14,11 @@ | |
def rate_limiter(by, options = {}) | |
return @rate_limiter if defined?(@rate_limiter) | |
- @rate_limiter = RateLimiter.new(by, options) | |
+ @rate_limiter = if by.is_a?(Account) && by.trust_level == Account::TRUST_LEVELS[:hostile] | |
+ HostileRateLimiter.new(by, options) | |
+ else | |
+ RateLimiter.new(by, options) | |
+ end | |
end | |
class_methods do | |
diff -ru truth-old/opensource/app/models/concerns/relationship_cacheable.rb truth-new/opensource/app/models/concerns/relationship_cacheable.rb | |
--- truth-old/opensource/app/models/concerns/relationship_cacheable.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/relationship_cacheable.rb 2024-04-01 14:59:13 | |
@@ -10,7 +10,7 @@ | |
private | |
def remove_relationship_cache | |
- Rails.cache.delete("relationship:#{account_id}:#{target_account_id}") | |
- Rails.cache.delete("relationship:#{target_account_id}:#{account_id}") | |
+ Rails.cache.delete("relationship/#{account_id}/#{target_account_id}") | |
+ Rails.cache.delete("relationship/#{target_account_id}/#{account_id}") | |
end | |
end | |
diff -ru truth-old/opensource/app/models/concerns/remotable.rb truth-new/opensource/app/models/concerns/remotable.rb | |
--- truth-old/opensource/app/models/concerns/remotable.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/remotable.rb 2024-04-01 14:59:13 | |
@@ -28,11 +28,11 @@ | |
public_send("#{attachment_name}=", ResponseWithLimit.new(response, limit)) | |
end | |
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e | |
- Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}" | |
+ Rails.logger.info "Error fetching remote #{attachment_name}: #{e}" | |
public_send("#{attachment_name}=", nil) if public_send("#{attachment_name}_file_name").present? | |
raise e unless suppress_errors && !should_raise_error(parsed_url.host) | |
rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e | |
- Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}" | |
+ Rails.logger.info "Error fetching remote #{attachment_name}: #{e}" | |
public_send("#{attachment_name}=", nil) if public_send("#{attachment_name}_file_name").present? | |
end | |
diff -ru truth-old/opensource/app/models/concerns/status_threading_concern.rb truth-new/opensource/app/models/concerns/status_threading_concern.rb | |
--- truth-old/opensource/app/models/concerns/status_threading_concern.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/concerns/status_threading_concern.rb 2024-04-01 14:59:13 | |
@@ -2,151 +2,16 @@ | |
module StatusThreadingConcern | |
extend ActiveSupport::Concern | |
- include Redisable | |
def ancestors(limit, account = nil) | |
- find_statuses_from_tree_path(ancestor_ids(limit), account) | |
+ StatusRepliesV1.new(self).ancestors(limit, account) | |
end | |
def descendants(limit, account = nil, offset = 0, max_child_id = nil, since_child_id = nil, depth = nil) | |
- find_statuses_from_tree_path(descendant_ids(limit, offset, max_child_id, since_child_id, depth), account, promote: true) | |
+ StatusRepliesV1.new(self).descendants(limit, account, offset, max_child_id, since_child_id, depth) | |
end | |
def self_replies(limit) | |
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted]).reorder(id: :asc).limit(limit) | |
- end | |
- | |
- private | |
- | |
- def ancestor_ids(limit) | |
- key = "ancestors:#{id}" | |
- ancestors = Rails.cache.fetch(key) | |
- | |
- if ancestors.nil? || ancestors[:limit] < limit | |
- ids = ancestor_statuses(limit).pluck(:id).reverse! | |
- Rails.cache.write key, limit: limit, ids: ids | |
- ids | |
- else | |
- ancestors[:ids].last(limit) | |
- end | |
- end | |
- | |
- def ancestor_statuses(limit) | |
- Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id, limit: limit]) | |
- WITH RECURSIVE search_tree(id, in_reply_to_id, path) | |
- AS ( | |
- SELECT id, in_reply_to_id, ARRAY[id] | |
- FROM statuses | |
- WHERE id = :id | |
- UNION ALL | |
- SELECT statuses.id, statuses.in_reply_to_id, path || statuses.id | |
- FROM search_tree | |
- JOIN statuses ON statuses.id = search_tree.in_reply_to_id | |
- WHERE NOT statuses.id = ANY(path) | |
- ) | |
- SELECT id | |
- FROM search_tree | |
- ORDER BY path | |
- LIMIT :limit | |
- SQL | |
- end | |
- | |
- def descendant_ids(limit, offset, max_child_id, since_child_id, depth) | |
- key = "descendants:#{conversation_id}" | |
- field = "#{id}:#{limit}:#{offset}" | |
- if (cached_descendants = get_descendants_from_cache(key, field)) | |
- cached_descendants | |
- else | |
- ids = descendant_statuses(limit, offset, max_child_id, since_child_id, depth).pluck(:id) | |
- redis.hset(key, field, ids.to_json) | |
- redis.expire(key, 1.hour.seconds) | |
- ids | |
- end | |
- end | |
- | |
- def descendant_statuses(limit, offset, max_child_id, since_child_id, depth) | |
- # use limit + 1 and depth + 1 because 'self' is included | |
- depth += 1 if depth.present? | |
- offset += 1 if offset.present? | |
- | |
- descendants_with_self = Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, offset: offset, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth]) | |
- WITH RECURSIVE search_tree(id, path) | |
- AS ( | |
- SELECT id, ARRAY[id] | |
- FROM statuses | |
- WHERE id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE) | |
- UNION ALL | |
- SELECT statuses.id, path || statuses.id | |
- FROM search_tree | |
- JOIN statuses ON statuses.in_reply_to_id = search_tree.id | |
- WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path) | |
- ) | |
- SELECT id | |
- FROM search_tree | |
- ORDER BY path | |
- OFFSET :offset | |
- LIMIT :limit | |
- SQL | |
- | |
- descendants_with_self | |
- end | |
- | |
- def find_statuses_from_tree_path(ids, account, promote: false) | |
- statuses = Status.with_accounts(ids).to_a | |
- account_ids = statuses.map(&:account_id).uniq | |
- domains = statuses.filter_map(&:account_domain).uniq | |
- relations = relations_map_for_account(account, account_ids, domains) | |
- | |
- statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? } | |
- | |
- # Order ancestors/descendants by tree path | |
- statuses.sort_by! { |status| ids.index(status.id) } | |
- | |
- # Bring self-replies to the top | |
- if promote | |
- promote_by!(statuses) { |status| status.in_reply_to_account_id == status.account_id } | |
- else | |
- statuses | |
- end | |
- end | |
- | |
- def promote_by!(arr) | |
- insert_at = arr.find_index { |item| !yield(item) } | |
- | |
- return arr if insert_at.nil? | |
- | |
- arr.each_with_index do |item, index| | |
- next if index <= insert_at || !yield(item) | |
- | |
- arr.insert(insert_at, arr.delete_at(index)) | |
- insert_at += 1 | |
- end | |
- | |
- arr | |
- end | |
- | |
- def relations_map_for_account(account, account_ids, domains) | |
- return {} if account.nil? | |
- | |
- { | |
- blocking: Account.blocking_map(account_ids, account.id), | |
- blocked_by: Account.blocked_by_map(account_ids, account.id), | |
- muting: Account.muting_map(account_ids, account.id), | |
- following: Account.following_map(account_ids, account.id), | |
- domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id), | |
- } | |
- end | |
- | |
- def get_descendants_from_cache(key, field) | |
- cached_descendants_raw = redis.hget(key, field) | |
- | |
- return if cached_descendants_raw.nil? | |
- | |
- begin | |
- parsed = JSON.parse(cached_descendants_raw) | |
- parsed.is_a?(Array) ? parsed : false | |
- rescue JSON::ParserError | |
- false | |
- end | |
end | |
end | |
Only in truth-new/opensource/app/models/concerns: status_threading_concern_v2.rb | |
Only in truth-new/opensource/app/models: configuration | |
Only in truth-new/opensource/app/models: country.rb | |
Only in truth-new/opensource/app/models: current.rb | |
diff -ru truth-old/opensource/app/models/device.rb truth-new/opensource/app/models/device.rb | |
--- truth-old/opensource/app/models/device.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/device.rb 2024-04-01 14:59:13 | |
@@ -15,7 +15,7 @@ | |
# | |
class Device < ApplicationRecord | |
- belongs_to :access_token, class_name: 'Doorkeeper::AccessToken' | |
+ belongs_to :access_token, class_name: 'OauthAccessToken' | |
belongs_to :account | |
has_many :one_time_keys, dependent: :destroy, inverse_of: :device | |
Only in truth-new/opensource/app/models: device_verification.rb | |
Only in truth-new/opensource/app/models: device_verification_chat_message.rb | |
Only in truth-new/opensource/app/models: device_verification_favourite.rb | |
Only in truth-new/opensource/app/models: device_verification_registration.rb | |
Only in truth-new/opensource/app/models: device_verification_status.rb | |
Only in truth-new/opensource/app/models: device_verification_user.rb | |
diff -ru truth-old/opensource/app/models/email_domain_block.rb truth-new/opensource/app/models/email_domain_block.rb | |
--- truth-old/opensource/app/models/email_domain_block.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/email_domain_block.rb 2024-04-01 14:59:13 | |
@@ -3,11 +3,12 @@ | |
# | |
# Table name: email_domain_blocks | |
# | |
-# id :bigint(8) not null, primary key | |
-# domain :string default(""), not null | |
-# created_at :datetime not null | |
-# updated_at :datetime not null | |
-# parent_id :bigint(8) | |
+# id :bigint(8) not null, primary key | |
+# domain :string default(""), not null | |
+# created_at :datetime not null | |
+# updated_at :datetime not null | |
+# parent_id :bigint(8) | |
+# disposable :boolean | |
# | |
class EmailDomainBlock < ApplicationRecord | |
@@ -17,6 +18,7 @@ | |
has_many :children, class_name: 'EmailDomainBlock', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy | |
validates :domain, presence: true, uniqueness: true, domain: true | |
+ validate :real_domain | |
def with_dns_records=(val) | |
@with_dns_records = ActiveModel::Type::Boolean.new.cast(val) | |
@@ -28,6 +30,16 @@ | |
alias with_dns_records with_dns_records? | |
+ def with_domain_validation=(val) | |
+ @with_domain_validation = ActiveModel::Type::Boolean.new.cast(val) | |
+ end | |
+ | |
+ def with_domain_validation? | |
+ @with_domain_validation | |
+ end | |
+ | |
+ alias with_domain_validation with_domain_validation? | |
+ | |
def self.block?(email) | |
_, domain = email.split('@', 2) | |
@@ -40,5 +52,19 @@ | |
end | |
where(domain: domain).exists? | |
+ end | |
+ | |
+ private | |
+ | |
+ def real_domain | |
+ if @with_domain_validation && domain.present? && !domain.nil? | |
+ begin | |
+ if Addressable::URI.parse("http://#{domain}").domain.nil? | |
+ errors.add(:domain, 'is invalid domain') | |
+ end | |
+ rescue Addressable::URI::InvalidURIError | |
+ errors.add(:domain, 'is invalid domain') | |
+ end | |
+ end | |
end | |
end | |
Only in truth-new/opensource/app/models: external_ad.rb | |
diff -ru truth-old/opensource/app/models/favourite.rb truth-new/opensource/app/models/favourite.rb | |
--- truth-old/opensource/app/models/favourite.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/favourite.rb 2024-04-01 14:59:13 | |
@@ -12,8 +12,9 @@ | |
class Favourite < ApplicationRecord | |
include Paginable | |
+ extend Queriable | |
- update_index('statuses#status', :status) | |
+ update_index('statuses', :status) | |
belongs_to :account, inverse_of: :favourites | |
belongs_to :status, inverse_of: :favourites | |
@@ -24,19 +25,5 @@ | |
before_validation do | |
self.status = status.reblog if status&.reblog? | |
- end | |
- | |
- after_create :increment_cache_counters | |
- after_destroy :decrement_cache_counters | |
- | |
- private | |
- | |
- def increment_cache_counters | |
- status&.increment_count!(:favourites_count) | |
- end | |
- | |
- def decrement_cache_counters | |
- return if association(:status).loaded? && status.marked_for_destruction? | |
- status&.decrement_count!(:favourites_count) | |
end | |
end | |
diff -ru truth-old/opensource/app/models/feed.rb truth-new/opensource/app/models/feed.rb | |
--- truth-old/opensource/app/models/feed.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/feed.rb 2024-04-01 14:59:13 | |
@@ -17,18 +17,28 @@ | |
@fanout_key = feed_key(@type, @account.id) | |
status_ids = !@whale_following.empty? && @type == :home ? get_with_whales : get_fanout_only | |
- Status.where(id: status_ids).cache_ids | |
+ | |
+ Status. | |
+ where(id: status_ids). | |
+ where.not(visibility: "self"). | |
+ or(Status.where(id: status_ids).where(account_id: @account.id)). | |
+ cache_ids | |
end | |
+ def clear! | |
+ key_to_clear = feed_key(@type, @account.id) | |
+ redis_timelines.del(key_to_clear) | |
+ end | |
+ | |
protected | |
def get_fanout_only | |
@max_id = '+inf' if @max_id.blank? | |
if @min_id.blank? | |
@since_id = '-inf' if @since_id.blank? | |
- redis_timelines.zrevrangebyscore(@fanout_key, "(#{@max_id}", "(#{@since_id}", limit: [0, @limit], with_scores: true).map(&:first).map(&:to_i) | |
+ FeedManager.instance.status_ids_to_plain_numbers(redis_timelines.zrevrangebyscore(@fanout_key, "(#{@max_id}", "(#{@since_id}", limit: [0, @limit], with_scores: true).map(&:first)).map(&:to_i) | |
else | |
- redis_timelines.zrangebyscore(@fanout_key, "(#{@min_id}", "(#{@max_id}", limit: [0, @limit], with_scores: true).map(&:first).map(&:to_i) | |
+ FeedManager.instance.status_ids_to_plain_numbers(redis_timelines.zrangebyscore(@fanout_key, "(#{@min_id}", "(#{@max_id}", limit: [0, @limit], with_scores: true).map(&:first)).map(&:to_i) | |
end | |
end | |
@@ -63,7 +73,7 @@ | |
pipeline.zrange(feed_key(:whale, account_id), '0', '-1') | |
end | |
end | |
- subsets.flatten.map(&:to_i) | |
+ FeedManager.instance.status_ids_to_plain_numbers(subsets.flatten).map(&:to_i) | |
end | |
def whale_following | |
Only in truth-new/opensource/app/models: feeds | |
diff -ru truth-old/opensource/app/models/follow.rb truth-new/opensource/app/models/follow.rb | |
--- truth-old/opensource/app/models/follow.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/follow.rb 2024-04-01 14:59:13 | |
@@ -40,10 +40,8 @@ | |
end | |
before_validation :set_uri, only: :create | |
- after_create :increment_cache_counters | |
after_create :invalidate_hash_cache | |
after_destroy :remove_endorsements | |
- after_destroy :decrement_cache_counters | |
after_destroy :invalidate_hash_cache | |
private | |
@@ -54,16 +52,6 @@ | |
def remove_endorsements | |
AccountPin.where(target_account_id: target_account_id, account_id: account_id).delete_all | |
- end | |
- | |
- def increment_cache_counters | |
- account&.increment_count!(:following_count) | |
- target_account&.increment_count!(:followers_count) | |
- end | |
- | |
- def decrement_cache_counters | |
- account&.decrement_count!(:following_count) | |
- target_account&.decrement_count!(:followers_count) | |
end | |
def invalidate_hash_cache | |
Only in truth-new/opensource/app/models: follow_delete.rb | |
Only in truth-new/opensource/app/models: follow_suggestion.rb | |
diff -ru truth-old/opensource/app/models/form/account_batch.rb truth-new/opensource/app/models/form/account_batch.rb | |
--- truth-old/opensource/app/models/form/account_batch.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/form/account_batch.rb 2024-04-01 14:59:13 | |
@@ -81,7 +81,16 @@ | |
records = accounts.includes(:user) | |
records.each { |account| authorize(account.user, :reject?) } | |
- .each { |account| DeleteAccountService.new.call(account, reserve_email: false, reserve_username: false) } | |
+ .each do |account| | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ current_account.id, | |
+ deletion_type: 'account_batch_reject', | |
+ reserve_email: false, | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ ) | |
+ end | |
end | |
def suppress_follow_recommendation! | |
diff -ru truth-old/opensource/app/models/form/status_batch.rb truth-new/opensource/app/models/form/status_batch.rb | |
--- truth-old/opensource/app/models/form/status_batch.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/form/status_batch.rb 2024-04-01 14:59:13 | |
@@ -34,8 +34,9 @@ | |
def delete_statuses | |
Status.where(id: status_ids).reorder(nil).find_each do |status| | |
+ status.reblogs.update_all(deleted_at: Time.current, deleted_by_id: current_account.id) | |
status.update!(deleted_at: Time.current, deleted_by_id: current_account.id) | |
- RemovalWorker.perform_async(status.id, immediate: true) | |
+ RemovalWorker.perform_async(status.id, immediate: false) | |
Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) | |
log_action :destroy, status | |
end | |
Only in truth-new/opensource/app/models: group.rb | |
Only in truth-new/opensource/app/models: group_account_block.rb | |
Only in truth-new/opensource/app/models: group_avatar.rb | |
Only in truth-new/opensource/app/models: group_deletion_request.rb | |
Only in truth-new/opensource/app/models: group_feed.rb | |
Only in truth-new/opensource/app/models: group_filter.rb | |
Only in truth-new/opensource/app/models: group_header.rb | |
Only in truth-new/opensource/app/models: group_membership.rb | |
Only in truth-new/opensource/app/models: group_membership_request.rb | |
Only in truth-new/opensource/app/models: group_mute.rb | |
Only in truth-new/opensource/app/models: group_stat.rb | |
Only in truth-new/opensource/app/models: group_suggestion.rb | |
Only in truth-new/opensource/app/models: group_suggestion_delete.rb | |
Only in truth-new/opensource/app/models: group_tag.rb | |
Only in truth-new/opensource/app/models: groups_carousel.rb | |
Only in truth-new/opensource/app/models: groups_feed.rb | |
Only in truth-new/opensource/app/models: link.rb | |
Only in truth-new/opensource/app/models: logs | |
diff -ru truth-old/opensource/app/models/media_attachment.rb truth-new/opensource/app/models/media_attachment.rb | |
--- truth-old/opensource/app/models/media_attachment.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/media_attachment.rb 2024-04-12 09:09:12 | |
@@ -27,12 +27,13 @@ | |
# thumbnail_updated_at :datetime | |
# thumbnail_remote_url :string | |
# external_video_id :string | |
+# file_s3_host :string(64) | |
# | |
class MediaAttachment < ApplicationRecord | |
self.inheritance_column = nil | |
- enum type: [:image, :gifv, :video, :unknown, :audio] | |
+ enum type: [:image, :gifv, :video, :unknown, :audio, :tv] | |
enum processing: [:queued, :in_progress, :complete, :failed], _prefix: true | |
MAX_DESCRIPTION_LENGTH = 1_500 | |
@@ -48,10 +49,10 @@ | |
small | |
).freeze | |
- IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif).freeze | |
- VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg).freeze | |
- VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze | |
- AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/ogg audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze | |
+ IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif).freeze | |
+ VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg application/octet-stream).freeze | |
+ VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime application/octet-stream).freeze | |
+ AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/ogg audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze | |
BLURHASH_OPTIONS = { | |
x_comp: 4, | |
@@ -152,13 +153,14 @@ | |
}.freeze | |
IMAGE_LIMIT = 10.megabytes | |
- VIDEO_LIMIT = 40.megabytes | |
+ VIDEO_LIMIT = 450.megabytes | |
- MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px | |
- MAX_VIDEO_FRAME_RATE = 60 | |
+ MAX_VIDEO_MATRIX_LIMIT = 8_294_400 # 3840 x 2160 | |
+ MAX_VIDEO_FRAME_RATE = 60 | |
+ MAX_VIDEO_DURATION_LIMIT = 900 # 900 seconds | |
- belongs_to :account, inverse_of: :media_attachments, optional: true | |
- belongs_to :status, inverse_of: :media_attachments, optional: true | |
+ belongs_to :account, inverse_of: :media_attachments, optional: true | |
+ belongs_to :status, inverse_of: :media_attachments, optional: true | |
belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true | |
has_many :moderation_records | |
@@ -169,13 +171,14 @@ | |
convert_options: GLOBAL_CONVERT_OPTIONS | |
before_file_post_process :set_type_and_extension | |
- before_file_post_process :do_not_process_video_files | |
+ before_file_post_process :check_video_dimensions | |
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES | |
- validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format? | |
- validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format? | |
+ validates_attachment_size :file, less_than: ->(m) { m.larger_media_format? ? VIDEO_LIMIT : IMAGE_LIMIT } | |
remotable_attachment :file, VIDEO_LIMIT, suppress_errors: false, download_on_assign: false, attribute_name: :remote_url | |
+ remotable_attachment :file, IMAGE_LIMIT | |
+ | |
has_attached_file :thumbnail, | |
styles: THUMBNAIL_STYLES, | |
processors: [:lazy_thumbnail, :blurhash_transcoder, :color_extractor], | |
@@ -183,7 +186,7 @@ | |
validates_attachment_content_type :thumbnail, content_type: IMAGE_MIME_TYPES | |
validates_attachment_size :thumbnail, less_than: IMAGE_LIMIT | |
- remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false | |
+ remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true | |
include Attachmentable | |
@@ -193,7 +196,7 @@ | |
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? } | |
scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } | |
- scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } | |
+ scope :unattached, -> { select('*').from('(select a.* from media_attachments a where a.status_id is null and a.scheduled_status_id is null and not exists (select 1 from chats.message_media_attachments m where m.media_attachment_id = a.id)) as media_attachments') } | |
scope :local, -> { where(remote_url: '') } | |
scope :remote, -> { where.not(remote_url: '') } | |
scope :cached, -> { remote.where.not(file_file_name: nil) } | |
@@ -255,10 +258,13 @@ | |
end | |
after_commit :enqueue_processing, on: :create | |
+ after_commit :publish_event, if: -> { saved_change_to_processing?(to: 'complete') } | |
after_commit :reset_parent_cache, on: :update | |
before_create :prepare_description, unless: :local? | |
before_create :set_shortcode | |
+ before_save :set_file_s3_host | |
+ | |
before_create :set_processing | |
after_post_process :set_meta | |
@@ -275,7 +281,9 @@ | |
private | |
def file_styles(attachment) | |
- if attachment.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(attachment.instance.file_content_type) | |
+ # only convert gifs to video. Don't convert hevc and webm videos | |
+ # Let those fall to the video_styles where they will just be passthrough encoded | |
+ if attachment.instance.file_content_type == 'image/gif' # || VIDEO_CONVERTIBLE_MIME_TYPES.include?(attachment.instance.file_content_type) | |
VIDEO_CONVERTED_STYLES | |
elsif IMAGE_MIME_TYPES.include?(attachment.instance.file_content_type) | |
IMAGE_STYLES | |
@@ -312,6 +320,10 @@ | |
end | |
end | |
+ def set_file_s3_host | |
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name] | |
+ end | |
+ | |
def prepare_description | |
self.description = description.strip[0...MAX_DESCRIPTION_LENGTH] unless description.nil? | |
end | |
@@ -354,6 +366,10 @@ | |
file.instance_write :meta, populate_meta | |
end | |
+ def publish_event | |
+ EventProvider::EventProvider.new('asset.created', ::AssetCreatedEvent, self).call | |
+ end | |
+ | |
def populate_meta | |
meta = (file.instance_read(:meta) || {}).with_indifferent_access.slice(*META_KEYS) | |
@@ -402,10 +418,10 @@ | |
def enqueue_processing | |
if video? | |
- self.processing = :complete | |
- self.save | |
- else | |
- PostProcessMediaWorker.perform_async(id) if delay_processing? | |
+ self.processing = :queued | |
+ save | |
+ elsif delay_processing? | |
+ PostProcessMediaWorker.perform_async(id) | |
end | |
end | |
diff -ru truth-old/opensource/app/models/notification.rb truth-new/opensource/app/models/notification.rb | |
--- truth-old/opensource/app/models/notification.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/notification.rb 2024-04-01 14:59:13 | |
@@ -15,6 +15,8 @@ | |
# | |
class Notification < ApplicationRecord | |
+ self.primary_key = :id | |
+ | |
self.inheritance_column = nil | |
include Paginable | |
@@ -27,7 +29,7 @@ | |
'FollowRequest' => :follow_request, | |
'Favourite' => :favourite, | |
'Poll' => :poll, | |
- 'User' => :user_approved, | |
+ 'User' => :user_approved | |
}.freeze | |
TYPES = %i( | |
@@ -41,10 +43,27 @@ | |
poll | |
user_approved | |
verify_sms_prompt | |
+ chat | |
+ chat_message | |
+ chat_message_deleted | |
mention_group | |
reblog_group | |
follow_group | |
favourite_group | |
+ group_favourite | |
+ group_favourite_group | |
+ group_reblog | |
+ group_reblog_group | |
+ group_mention | |
+ group_mention_group | |
+ group_approval | |
+ group_delete | |
+ group_role | |
+ group_request | |
+ group_request_group | |
+ group_accepted | |
+ group_promoted | |
+ group_demoted | |
).freeze | |
TARGET_STATUS_INCLUDES_BY_TYPE = { | |
@@ -59,6 +78,12 @@ | |
poll: [poll: :status], | |
user_approved: :user, | |
verify_sms_prompt: :user, | |
+ group_favourite: [favourite: :status], | |
+ group_favourite_group: [favourite: :status], | |
+ group_reblog: [status: :reblog], | |
+ group_reblog_group: [status: :reblog], | |
+ group_mention: [mention: :status], | |
+ group_mention_group: [mention: :status] | |
}.freeze | |
attr_accessor :passed_from_account | |
@@ -75,41 +100,61 @@ | |
belongs_to :favourite, foreign_key: 'activity_id', optional: true | |
belongs_to :poll, foreign_key: 'activity_id', optional: true | |
belongs_to :user, foreign_key: 'activity_id', optional: true | |
+ belongs_to :group, foreign_key: 'activity_id', optional: true | |
+ belongs_to :group_membership_request, foreign_key: 'activity_id', optional: true | |
validates :type, inclusion: { in: TYPES } | |
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) } | |
- scope :browserable, ->(exclude_types = [], account_id = nil) { | |
- types = TYPES - exclude_types.map(&:to_sym) | |
- | |
- if account_id.nil? | |
- where(type: types) | |
- else | |
- where(type: types, from_account_id: account_id) | |
- end | |
- } | |
- | |
def type | |
@type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym | |
end | |
def target_status | |
case type | |
- when :status, :favourite_group, :mention_group, :reblog_group | |
+ when :status, :favourite_group, :mention_group, :reblog_group, :group_favourite_group, :group_mention_group, :group_reblog_group | |
status | |
- when :reblog | |
+ when :reblog, :group_reblog | |
status&.reblog | |
- when :favourite | |
+ when :favourite, :group_favourite | |
favourite&.status | |
- when :mention | |
+ when :mention, :group_mention | |
mention&.status | |
when :poll | |
poll&.status | |
end | |
end | |
+ def target_group | |
+ case type | |
+ when :group_approval, :group_promoted, :group_demoted, :group_delete | |
+ group | |
+ when :group_request | |
+ group_membership_request | |
+ end | |
+ end | |
+ | |
class << self | |
+ def browserable(types: [], exclude_types: [], from_account_id: nil) | |
+ requested_types = begin | |
+ if types.empty? | |
+ TYPES | |
+ else | |
+ types.map(&:to_sym) & TYPES | |
+ end | |
+ end | |
+ | |
+ exclude_types_with_groups = exclude_types.clone | |
+ exclude_types.each { |n| exclude_types_with_groups << "#{n}_group"} | |
+ requested_types -= exclude_types_with_groups.map(&:to_sym) | |
+ | |
+ all.tap do |scope| | |
+ scope.merge!(where(from_account_id: from_account_id)) if from_account_id.present? | |
+ scope.merge!(where(type: requested_types)) unless requested_types.size == TYPES.size | |
+ end | |
+ end | |
+ | |
def preload_cache_collection_target_statuses(notifications, &_block) | |
notifications.group_by(&:type).each do |type, grouped_notifications| | |
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type] | |
@@ -130,13 +175,13 @@ | |
cached_status = cached_statuses_by_id[notification.target_status.id] | |
case notification.type | |
- when :status, :favourite_group, :mention_group, :reblog_group | |
+ when :status, :favourite_group, :mention_group, :reblog_group, :group_favourite_group, :group_mention_group, :group_reblog_group | |
notification.status = cached_status | |
- when :reblog | |
+ when :reblog, :group_reblog | |
notification.status.reblog = cached_status | |
- when :favourite | |
+ when :favourite, :group_favourite | |
notification.favourite.status = cached_status | |
- when :mention | |
+ when :mention, :group_mention | |
notification.mention.status = cached_status | |
when :poll | |
notification.poll.status = cached_status | |
@@ -145,6 +190,10 @@ | |
notifications | |
end | |
+ | |
+ def exclude_self_statuses(notifications) | |
+ notifications.delete_if { |n| n.target_status&.visibility == 'self' } | |
+ end | |
end | |
after_initialize :set_from_account | |
@@ -166,7 +215,22 @@ | |
self.from_account_id = activity&.users&.first&.account_id | |
when 'User' | |
self.from_account_id = activity&.account_id | |
+ when 'ChatMessage' | |
+ self.from_account_id = activity&.created_by_account_id | |
+ when 'Group' | |
+ self.from_account_id = set_by_notification_type | |
+ when 'GroupMembershipRequest' | |
+ self.from_account_id = activity&.account&.id | |
end | |
+ end | |
+ end | |
+ | |
+ def set_by_notification_type | |
+ case type | |
+ when :group_delete | |
+ activity&.owner_account&.id | |
+ else | |
+ activity&.owner_account&.id | |
end | |
end | |
end | |
Only in truth-new/opensource/app/models: notifications_marketing.rb | |
Only in truth-new/opensource/app/models: notifications_marketing_analytic.rb | |
Only in truth-new/opensource/app/models: oauth_access_token.rb | |
Only in truth-new/opensource/app/models: oauth_access_tokens | |
Only in truth-new/opensource/app/models: one_time_challenge.rb | |
Only in truth-new/opensource/app/models: password_history.rb | |
Only in truth-new/opensource/app/models: policy.rb | |
diff -ru truth-old/opensource/app/models/poll.rb truth-new/opensource/app/models/poll.rb | |
--- truth-old/opensource/app/models/poll.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/poll.rb 2024-04-01 14:59:13 | |
@@ -1,115 +1,82 @@ | |
# frozen_string_literal: true | |
# == Schema Information | |
# | |
-# Table name: polls | |
+# Table name: polls.polls | |
# | |
-# id :bigint(8) not null, primary key | |
-# account_id :bigint(8) | |
-# status_id :bigint(8) | |
-# expires_at :datetime | |
-# options :string default([]), not null, is an Array | |
-# cached_tallies :bigint(8) default([]), not null, is an Array | |
-# multiple :boolean default(FALSE), not null | |
-# hide_totals :boolean default(FALSE), not null | |
-# votes_count :bigint(8) default(0), not null | |
-# last_fetched_at :datetime | |
-# created_at :datetime not null | |
-# updated_at :datetime not null | |
-# lock_version :integer default(0), not null | |
-# voters_count :bigint(8) | |
+# poll_id :integer not null, primary key | |
+# expires_at :datetime not null | |
+# multiple_choice :boolean default(FALSE), not null | |
# | |
class Poll < ApplicationRecord | |
include Expireable | |
- belongs_to :account | |
- belongs_to :status | |
+ self.table_name = 'polls.polls' | |
+ self.primary_key = :poll_id | |
- has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :delete_all | |
+ has_and_belongs_to_many :statuses, join_table: 'polls.status_polls' | |
+ has_many :options, class_name: 'PollOption', inverse_of: :poll, dependent: :delete_all | |
+ has_many :votes, class_name: 'PollVote', inverse_of: :poll | |
has_many :notifications, as: :activity, dependent: :destroy | |
+ has_one :statistic_polls, class_name: 'StatisticPoll', inverse_of: :poll | |
+ has_one :status_polls, class_name: 'StatusPolls' | |
+ has_one :status, through: :status_polls, source: :status | |
+ has_one :account, through: :status | |
- validates :options, presence: true | |
- validates :expires_at, presence: true, if: :local? | |
- validates_with PollValidator, on: :create, if: :local? | |
+ accepts_nested_attributes_for :options | |
- scope :attached, -> { where.not(status_id: nil) } | |
- scope :unattached, -> { where(status_id: nil) } | |
+ validates :expires_at, presence: true | |
+ validates_with PollValidator, on: :create | |
- before_validation :prepare_options, if: :local? | |
- before_validation :prepare_votes_count | |
+ alias_attribute :multiple, :multiple_choice | |
- after_initialize :prepare_cached_tallies | |
- | |
- after_commit :reset_parent_cache, on: :update | |
- | |
def loaded_options | |
- options.map.with_index { |title, key| Option.new(self, key.to_s, title, show_totals_now? ? cached_tallies[key] : nil) } | |
+ loaded_poll_options | |
end | |
- def possibly_stale? | |
- remote? && last_fetched_before_expiration? && time_passed_since_last_fetch? | |
+ def loaded_poll_options | |
+ options = PollOption | |
+ .joins('LEFT JOIN statistics.poll_options using("poll_id", "option_number")') | |
+ .where(options: { poll_id: id }) | |
+ .order(option_number: :asc) | |
+ .select('polls.options.*, statistics.poll_options.votes votes_count') | |
+ options.map { |option| { title: option.text, votes_count: option.has_attribute?(:votes_count) && option.votes_count ? option.votes_count : 0 } } | |
end | |
def voted?(account) | |
- account.id == account_id || votes.where(account: account).exists? | |
+ PollVote.where(poll_id: id, account: account).exists? | |
end | |
def own_votes(account) | |
- votes.where(account: account).pluck(:choice) | |
+ PollVote.where(poll_id: id, account: account).pluck(:option_number) || [] | |
end | |
delegate :local?, to: :account | |
+ def local? | |
+ true | |
+ end | |
+ | |
def remote? | |
- !local? | |
+ false | |
end | |
def emojis | |
- @emojis ||= CustomEmoji.from_text(options.join(' '), account.domain) | |
+ [] | |
end | |
- class Option < ActiveModelSerializers::Model | |
- attributes :id, :title, :votes_count, :poll | |
- | |
- def initialize(poll, id, title, votes_count) | |
- super( | |
- poll: poll, | |
- id: id, | |
- title: title, | |
- votes_count: votes_count, | |
- ) | |
- end | |
+ def votes_count | |
+ statistic_polls&.votes || 0 | |
end | |
- private | |
- | |
- def prepare_cached_tallies | |
- self.cached_tallies = options.map { 0 } if cached_tallies.empty? | |
+ def voters_count | |
+ statistic_polls&.voters || 0 | |
end | |
- def prepare_votes_count | |
- self.votes_count = cached_tallies.sum unless cached_tallies.empty? | |
+ def statistic_polls | |
+ super || build_statistic_polls | |
end | |
- def prepare_options | |
- self.options = options.map(&:strip).reject(&:blank?) | |
- end | |
- | |
- def reset_parent_cache | |
- return if status_id.nil? | |
- Rails.cache.delete("statuses/#{status_id}") | |
- end | |
- | |
- def last_fetched_before_expiration? | |
- last_fetched_at.nil? || expires_at.nil? || last_fetched_at < expires_at | |
- end | |
- | |
- def time_passed_since_last_fetch? | |
- last_fetched_at.nil? || last_fetched_at < 1.minute.ago | |
- end | |
- | |
- def show_totals_now? | |
- expired? || !hide_totals? | |
- end | |
+ delegate :id, to: :account, prefix: true | |
end | |
Only in truth-new/opensource/app/models: poll_option.rb | |
diff -ru truth-old/opensource/app/models/poll_vote.rb truth-new/opensource/app/models/poll_vote.rb | |
--- truth-old/opensource/app/models/poll_vote.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/poll_vote.rb 2024-04-01 14:59:13 | |
@@ -1,39 +1,24 @@ | |
# frozen_string_literal: true | |
# == Schema Information | |
# | |
-# Table name: poll_votes | |
+# Table name: polls.votes | |
# | |
-# id :bigint(8) not null, primary key | |
-# account_id :bigint(8) | |
-# poll_id :bigint(8) | |
-# choice :integer default(0), not null | |
-# created_at :datetime not null | |
-# updated_at :datetime not null | |
-# uri :string | |
+# poll_id :integer not null, primary key | |
+# option_number :integer not null, primary key | |
+# account_id :bigint(8) not null, primary key | |
# | |
class PollVote < ApplicationRecord | |
+ self.table_name = 'polls.votes' | |
+ self.primary_keys = :poll_id, :option_number, :account_id | |
+ | |
belongs_to :account | |
- belongs_to :poll, inverse_of: :votes | |
+ belongs_to :poll | |
- validates :choice, presence: true | |
+ validates :poll_id, :option_number, :account_id, presence: true | |
validates_with VoteValidator | |
- after_create_commit :increment_counter_cache | |
- | |
- delegate :local?, to: :account | |
- | |
def object_type | |
:vote | |
- end | |
- | |
- private | |
- | |
- def increment_counter_cache | |
- poll.cached_tallies[choice] = (poll.cached_tallies[choice] || 0) + 1 | |
- poll.save | |
- rescue ActiveRecord::StaleObjectError | |
- poll.reload | |
- retry | |
end | |
end | |
diff -ru truth-old/opensource/app/models/preview_card.rb truth-new/opensource/app/models/preview_card.rb | |
--- truth-old/opensource/app/models/preview_card.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/preview_card.rb 2024-04-01 14:59:13 | |
@@ -24,11 +24,12 @@ | |
# embed_url :string default(""), not null | |
# image_storage_schema_version :integer | |
# blurhash :string | |
+# file_s3_host :string(64) | |
# | |
class PreviewCard < ApplicationRecord | |
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze | |
- LIMIT = 1.megabytes | |
+ LIMIT = 2_500.kilobytes | |
BLURHASH_OPTIONS = { | |
x_comp: 4, | |
@@ -50,9 +51,12 @@ | |
validates_attachment_size :image, less_than: LIMIT | |
remotable_attachment :image, LIMIT | |
+ attribute :ad, :boolean, default: false | |
+ | |
scope :cached, -> { where.not(image_file_name: [nil, '']) } | |
before_save :extract_dimensions, if: :link? | |
+ before_save :set_file_s3_host, if: -> { will_save_change_to_image_file_name? } | |
def local? | |
false | |
@@ -74,6 +78,7 @@ | |
# rubocop:disable Naming/MethodParameterName | |
def image_styles(f) | |
+ return {} if f.instance.ad && f.instance.image_content_type == 'image/gif' | |
styles = { | |
original: { | |
geometry: '800x800>', | |
@@ -102,5 +107,10 @@ | |
self.width = width | |
self.height = height | |
+ end | |
+ | |
+ | |
+ def set_file_s3_host | |
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name] | |
end | |
end | |
Only in truth-new/opensource/app/models: procedure.rb | |
Only in truth-new/opensource/app/models: recommendations | |
Only in truth-new/opensource/app/models: region.rb | |
Only in truth-new/opensource/app/models: registration.rb | |
Only in truth-new/opensource/app/models: registration_one_time_challenge.rb | |
Only in truth-new/opensource/app/models: registration_webauthn_credential.rb | |
diff -ru truth-old/opensource/app/models/relationship_filter.rb truth-new/opensource/app/models/relationship_filter.rb | |
--- truth-old/opensource/app/models/relationship_filter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/relationship_filter.rb 2024-04-01 14:59:13 | |
@@ -60,13 +60,13 @@ | |
def relationship_scope(value) | |
case value | |
when 'following' | |
- account.following.eager_load(:account_stat).reorder(nil) | |
+ account.following.eager_load(:account_status).reorder(nil) | |
when 'followed_by' | |
- account.followers.eager_load(:account_stat).reorder(nil) | |
+ account.followers.eager_load(:account_status).reorder(nil) | |
when 'mutual' | |
- account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following)) | |
+ account.followers.eager_load(:account_status).reorder(nil).merge(Account.where(id: account.following)) | |
when 'invited' | |
- Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil) | |
+ Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_status).reorder(nil) | |
else | |
raise "Unknown relationship: #{value}" | |
end | |
@@ -112,7 +112,7 @@ | |
def activity_scope(value) | |
case value | |
when 'dormant' | |
- AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago))) | |
+ AccountStatusStatistic.where(last_status_at: nil).or(AccountStatusStatistic.where(AccountStatusStatistic.arel_table[:last_status_at].lt(1.month.ago))) | |
else | |
raise "Unknown activity: #{value}" | |
end | |
diff -ru truth-old/opensource/app/models/report.rb truth-new/opensource/app/models/report.rb | |
--- truth-old/opensource/app/models/report.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/report.rb 2024-04-01 14:59:13 | |
@@ -3,7 +3,6 @@ | |
# | |
# Table name: reports | |
# | |
-# id :bigint(8) not null, primary key | |
# status_ids :bigint(8) default([]), not null, is an Array | |
# comment :text default(""), not null | |
# action_taken :boolean default(FALSE), not null | |
@@ -11,11 +10,15 @@ | |
# updated_at :datetime not null | |
# account_id :bigint(8) not null | |
# action_taken_by_account_id :bigint(8) | |
+# id :bigint(8) not null, primary key | |
# target_account_id :bigint(8) not null | |
# assigned_account_id :bigint(8) | |
# uri :string | |
# forwarded :boolean | |
# rule_ids :integer default([]), not null, is an Array | |
+# message_ids :bigint(8) default([]), is an Array | |
+# group_id :bigint(8) | |
+# external_ad_id :integer | |
# | |
class Report < ApplicationRecord | |
@@ -28,6 +31,7 @@ | |
belongs_to :target_account, class_name: 'Account' | |
belongs_to :action_taken_by_account, class_name: 'Account', optional: true | |
belongs_to :assigned_account, class_name: 'Account', optional: true | |
+ belongs_to :external_ad, optional: true | |
has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy | |
@@ -76,7 +80,6 @@ | |
target_account.update(trust_level: Account::TRUST_LEVELS[:trusted]) | |
end | |
- RemovalWorker.push_bulk(Status.with_discarded.discarded.where(id: status_ids).pluck(:id)) { |status_id| [status_id, { immediate: true }] } | |
update!(action_taken: true, action_taken_by_account_id: acting_account.id) | |
end | |
diff -ru truth-old/opensource/app/models/rule.rb truth-new/opensource/app/models/rule.rb | |
--- truth-old/opensource/app/models/rule.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/rule.rb 2024-04-01 14:59:13 | |
@@ -12,13 +12,14 @@ | |
# updated_at :datetime not null | |
# rule_type :integer default("content") | |
# subtext :text default(""), not null | |
+# name :text default(""), not null | |
# | |
class Rule < ApplicationRecord | |
include Discard::Model | |
self.discard_column = :deleted_at | |
- enum rule_type: { content: 0, account: 1 } | |
+ enum rule_type: { content: 0, account: 1, rule_type_group: 2 } | |
validates :text, presence: true, length: { maximum: 300 } | |
diff -ru truth-old/opensource/app/models/search.rb truth-new/opensource/app/models/search.rb | |
--- truth-old/opensource/app/models/search.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/search.rb 2024-04-01 14:59:13 | |
@@ -1,5 +1,5 @@ | |
# frozen_string_literal: true | |
class Search < ActiveModelSerializers::Model | |
- attributes :accounts, :statuses, :hashtags | |
+ attributes :accounts, :statuses, :hashtags, :groups | |
end | |
diff -ru truth-old/opensource/app/models/session_activation.rb truth-new/opensource/app/models/session_activation.rb | |
--- truth-old/opensource/app/models/session_activation.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/session_activation.rb 2024-04-01 14:59:13 | |
@@ -16,7 +16,7 @@ | |
class SessionActivation < ApplicationRecord | |
belongs_to :user, inverse_of: :session_activations | |
- belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy, optional: true | |
+ belongs_to :access_token, class_name: 'OauthAccessToken', dependent: :destroy, optional: true | |
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy, optional: true | |
delegate :token, | |
@@ -70,7 +70,7 @@ | |
end | |
def assign_access_token | |
- self.access_token = Doorkeeper::AccessToken.create!(access_token_attributes) | |
+ self.access_token = OauthAccessToken.create!(access_token_attributes) | |
end | |
def access_token_attributes | |
diff -ru truth-old/opensource/app/models/site_upload.rb truth-new/opensource/app/models/site_upload.rb | |
--- truth-old/opensource/app/models/site_upload.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/site_upload.rb 2023-05-05 13:42:02 | |
@@ -12,6 +12,7 @@ | |
# meta :json | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
+# file_s3_host :string(64) | |
# | |
class SiteUpload < ApplicationRecord | |
@@ -22,6 +23,7 @@ | |
validates :var, presence: true, uniqueness: true | |
before_save :set_meta | |
+ before_save :set_file_s3_host | |
after_commit :clear_cache | |
def cache_key | |
@@ -41,5 +43,10 @@ | |
def clear_cache | |
Rails.cache.delete(cache_key) | |
+ end | |
+ | |
+ | |
+ def set_file_s3_host | |
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name] | |
end | |
end | |
Only in truth-new/opensource/app/models: statistic_poll.rb | |
Only in truth-new/opensource/app/models: statistic_poll_option.rb | |
diff -ru truth-old/opensource/app/models/status.rb truth-new/opensource/app/models/status.rb | |
--- truth-old/opensource/app/models/status.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/status.rb 2024-04-12 16:08:26 | |
@@ -21,20 +21,25 @@ | |
# account_id :bigint(8) not null | |
# application_id :bigint(8) | |
# in_reply_to_account_id :bigint(8) | |
-# poll_id :bigint(8) | |
-# deleted_at :datetime | |
# quote_id :bigint(8) | |
+# deleted_at :datetime | |
# deleted_by_id :bigint(8) | |
+# group_id :bigint(8) | |
+# group_timeline_visible :boolean default(FALSE) | |
+# has_poll :boolean default(FALSE), not null | |
# | |
class Status < ApplicationRecord | |
+ | |
before_destroy :unlink_from_conversations | |
include Discard::Model | |
include Paginable | |
include Cacheable | |
include StatusThreadingConcern | |
+ include StatusThreadingConcernV2 | |
include RateLimitable | |
+ extend LinksParserConcern | |
rate_limit by: :account, family: :statuses | |
@@ -43,22 +48,34 @@ | |
# If `override_timestamps` is set at creation time, Snowflake ID creation | |
# will be based on current time instead of `created_at` | |
attr_accessor :override_timestamps | |
+ attr_accessor :interactive_ad, :statuses_count_before_filter | |
- update_index 'statuses#status', :proper, unless: -> { skip_indexing? } | |
+ attr_accessor :seen | |
- enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility | |
+ attribute :tv_program_status?, :boolean, default: false | |
+ # Used to bypass some validations if we know the operation was initiated from an admin | |
+ attr_accessor :performed_by_admin | |
+ | |
+ # attr_accessor :tombstone | |
+ attribute :tombstone, :boolean, default: false | |
+ | |
+ update_index 'statuses', :proper, unless: -> { skip_indexing? } | |
+ | |
+ enum visibility: [:public, :unlisted, :private, :direct, :limited, :self, :group], _suffix: :visibility | |
+ | |
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true | |
belongs_to :account, inverse_of: :statuses | |
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account', optional: true | |
belongs_to :conversation, optional: true | |
- belongs_to :preloadable_poll, class_name: 'Poll', foreign_key: 'poll_id', optional: true | |
- belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true | |
+ belongs_to :thread, -> { with_discarded }, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true | |
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true | |
- belongs_to :quote, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quoted, optional: true | |
+ belongs_to :quote, -> { with_discarded }, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quoted, optional: true | |
+ belongs_to :group, inverse_of: :statuses, optional: true | |
+ | |
has_many :favourites, inverse_of: :status, dependent: :destroy | |
has_many :bookmarks, inverse_of: :status, dependent: :destroy | |
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy | |
@@ -68,25 +85,40 @@ | |
has_many :media_attachments, dependent: :nullify | |
has_many :quoted, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quote, dependent: :nullify | |
has_many :moderation_records, dependent: :nullify | |
+ has_many :status_pins, inverse_of: :status, dependent: :destroy | |
+ has_many :moderation_results, dependent: :destroy, class_name: Statuses::ModerationResult.name | |
has_and_belongs_to_many :tags | |
has_and_belongs_to_many :preview_cards | |
+ has_and_belongs_to_many :links | |
has_one :notification, as: :activity, dependent: :destroy | |
- has_one :status_stat, inverse_of: :status | |
- has_one :poll, inverse_of: :status, dependent: :destroy | |
- has_one :trending, dependent: :destroy | |
+ has_one :status_favourite, class_name: StatusFavouriteStatistic.name, inverse_of: :status | |
+ has_one :status_reply, class_name: StatusReplyStatistic.name, inverse_of: :status | |
+ has_one :status_reblog, class_name: StatusReblogStatistic.name, inverse_of: :status | |
+ has_one :analysis, class_name: Statuses::Analysis.name | |
+ has_and_belongs_to_many :polls, inverse_of: :status, join_table: 'polls.status_polls' | |
+ accepts_nested_attributes_for :polls | |
+ | |
+ has_one :status_polls, class_name: 'StatusPolls' | |
+ has_one :preloadable_poll, through: :status_polls, source: :poll | |
+ | |
+ has_one :trending_status, class_name: 'TrendingStatus', dependent: :destroy | |
+ has_one :ad | |
+ has_one :tv_program_status | |
+ has_one :tv_program, through: :tv_program_status | |
+ has_one :tv_status | |
+ | |
validates :uri, uniqueness: true, presence: true, unless: :local? | |
- validates :text, presence: true, unless: -> { with_media? || reblog? } | |
+ validates :text, presence: true, unless: -> { with_media? || reblog? || ad? } | |
validates_with StatusLengthValidator | |
validates_with DisallowedHashtagsValidator | |
+ validates_with StatusGroupValidator, unless: -> { performed_by_admin } | |
validates :reblog, uniqueness: { scope: :account }, if: :reblog? | |
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog? | |
- validates :quote_visibility, inclusion: { in: %w(public unlisted) }, if: :quote? | |
+ validates :quote_visibility, inclusion: { in: %w(public unlisted group) }, if: :quote? | |
- accepts_nested_attributes_for :poll | |
- | |
default_scope { recent.kept } | |
scope :recent, -> { reorder(id: :desc) } | |
@@ -104,54 +136,79 @@ | |
scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) } | |
scope :tagged_with_all, ->(tag_ids) { | |
Array(tag_ids).reduce(self) do |result, id| | |
- result.joins("INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}") | |
+ tag_id = id.to_i | |
+ result.joins("INNER JOIN statuses_tags t#{tag_id} ON t#{tag_id}.status_id = statuses.id AND t#{tag_id}.tag_id = #{tag_id}") | |
end | |
} | |
scope :tagged_with_none, ->(tag_ids) { | |
Array(tag_ids).reduce(self) do |result, id| | |
- result.joins("LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}") | |
- .where("t#{id}.tag_id IS NULL") | |
+ tag_id = id.to_i | |
+ result.joins("LEFT OUTER JOIN statuses_tags t#{tag_id} ON t#{tag_id}.status_id = statuses.id AND t#{tag_id}.tag_id = #{tag_id}") | |
+ .where("t#{tag_id}.tag_id IS NULL") | |
end | |
} | |
+ scope :trending_statuses, -> { joins(:trending_status).reorder('sort_order ASC') } | |
+ scope :excluding_unauthorized_tv_statuses, lambda { |account_id| | |
+ where.not(TvProgramStatus.where('tv.program_statuses.status_id = statuses.id') | |
+ .where.not(Configuration::AccountEnabledFeature.where(feature_flag_id: Configuration::FeatureFlag.where(name: 'tv'), account_id: account_id).arel.exists) | |
+ .arel.exists) | |
+ } | |
cache_associated :application, | |
:media_attachments, | |
:conversation, | |
- :status_stat, | |
+ :status_favourite, | |
+ :status_reply, | |
+ :status_reblog, | |
:tags, | |
:preview_cards, | |
- :preloadable_poll, | |
- account: [:account_stat, :user], | |
- active_mentions: { account: :account_stat }, | |
+ :ad, | |
+ :links, | |
+ tv_program: [:tv_channel], | |
+ account: [:account_follower, :account_following, :account_status, :user], | |
+ active_mentions: { account: [:account_follower, :account_following, :account_status] }, | |
reblog: [ | |
:application, | |
:tags, | |
:preview_cards, | |
:media_attachments, | |
:conversation, | |
- :status_stat, | |
- :preloadable_poll, | |
- account: [:account_stat, :user], | |
- active_mentions: { account: :account_stat }, | |
+ :status_favourite, | |
+ :status_reply, | |
+ :status_reblog, | |
+ :ad, | |
+ :links, | |
+ account: [:account_follower, :account_following, :account_status, :user], | |
+ active_mentions: { account: [:account_follower, :account_following, :account_status] }, | |
], | |
quote: [ | |
- :application, | |
- :tags, | |
- :preview_cards, | |
- :media_attachments, | |
- :conversation, | |
- :status_stat, | |
- account: [:account_stat, :user], | |
- active_mentions: { account: :account_stat }, | |
- ], | |
+ :application, | |
+ :tags, | |
+ :preview_cards, | |
+ :media_attachments, | |
+ :conversation, | |
+ :status_favourite, | |
+ :status_reply, | |
+ :status_reblog, | |
+ :links, | |
+ :ad, | |
+ account: [:account_follower, :account_following, :account_status, :user], | |
+ active_mentions: { account: [:account_follower, :account_following, :account_status] }, | |
+ ], | |
thread: [ | |
- :application, | |
- :tags, | |
- :preview_cards, | |
- :media_attachments, | |
- account: [:account_stat, :user], | |
- active_mentions: { account: :account_stat }, | |
- ] | |
+ :application, | |
+ :tags, | |
+ :preview_cards, | |
+ :media_attachments, | |
+ :links, | |
+ :ad, | |
+ account: [:account_follower, :account_following, :account_status, :user], | |
+ active_mentions: { account: [:account_follower, :account_following, :account_status] }, | |
+ ], | |
+ group: [ | |
+ :group_stat, | |
+ :tags, | |
+ ] | |
delegate :domain, to: :account, prefix: true | |
@@ -175,14 +232,22 @@ | |
!reblog_of_id.nil? | |
end | |
+ def ad? | |
+ !!(interactive_ad || ad) | |
+ end | |
+ | |
def trending? | |
- trending.present? | |
+ trending_status.present? | |
end | |
def quote? | |
!quote_id.nil? && quote | |
end | |
+ def group? | |
+ !group_id.nil? && group | |
+ end | |
+ | |
def quote_visibility | |
quote&.visibility | |
end | |
@@ -200,7 +265,13 @@ | |
end | |
def object_type | |
- reply? ? :comment : :note | |
+ if group? | |
+ :group_note | |
+ elsif reply? | |
+ :comment | |
+ else | |
+ :note | |
+ end | |
end | |
def proper | |
@@ -224,6 +295,7 @@ | |
end | |
def distributable? | |
+ # TODO: how do we consider group posts? they may need LDSigning for efficiency | |
public_visibility? || unlisted_visibility? | |
end | |
@@ -245,21 +317,20 @@ | |
return @emojis if defined?(@emojis) | |
fields = [spoiler_text, text] | |
- fields += preloadable_poll.options unless preloadable_poll.nil? | |
@emojis = CustomEmoji.from_text(fields.join(' '), account.domain) + (quote? ? CustomEmoji.from_text([quote.spoiler_text, quote.text].join(' '), quote.account.domain) : []) | |
end | |
def replies_count | |
- status_stat&.replies_count || 0 | |
+ status_reply&.replies_count || 0 | |
end | |
def reblogs_count | |
- status_stat&.reblogs_count || 0 | |
+ status_reblog&.reblogs_count || 0 | |
end | |
def favourites_count | |
- status_stat&.favourites_count || 0 | |
+ status_favourite&.favourites_count || 0 | |
end | |
def skip_indexing? | |
@@ -271,20 +342,39 @@ | |
PROHIBITED_TERMS_ON_INDEX.any? { |term| text_downcase.include? term } | |
end | |
- def increment_count!(key) | |
- update_status_stat!(key => public_send(key) + 1) | |
+ def text_hash | |
+ return if text.blank? | |
+ | |
+ Digest::SHA2.hexdigest(text.strip) | |
end | |
- def decrement_count!(key) | |
- update_status_stat!(key => [public_send(key) - 1, 0].max) | |
+ def privatize(mod_id, _notify_user) | |
+ logger.debug "Status: #{id} PRIVATIZED" | |
+ reblogs.update_all(deleted_at: Time.current, deleted_by_id: mod_id) | |
+ update!(visibility: :self) | |
+ purge_cache | |
+ account.save | |
+ save | |
end | |
- after_create_commit :increment_counter_caches | |
- after_destroy_commit :decrement_counter_caches | |
+ def publicize | |
+ if visibility == 'self' | |
+ reblogs.update_all(deleted_at: nil, deleted_by_id: nil) | |
+ update!(visibility: group? ? :group : :public) | |
+ purge_cache | |
+ account.save | |
+ save | |
+ end | |
+ end | |
+ after_create_commit :increment_counter_caches, if: :group? | |
+ after_destroy_commit :decrement_counter_caches, if: :group? | |
+ | |
after_create_commit :store_uri, if: :local? | |
after_create_commit :update_statistics, if: :local? | |
+ after_create_commit :mark_tv_status | |
+ | |
around_create Mastodon::Snowflake::Callbacks | |
before_validation :prepare_contents, if: :local? | |
@@ -293,11 +383,9 @@ | |
before_validation :set_conversation | |
before_validation :set_local | |
- after_create :set_poll_id | |
- | |
class << self | |
def selectable_visibilities | |
- visibilities.keys - %w(direct limited) | |
+ %w(public unlisted private) | |
end | |
def favourites_map(status_ids, account_id) | |
@@ -316,27 +404,76 @@ | |
ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true } | |
end | |
- def pins_map(status_ids, account_id) | |
- StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true } | |
+ def pins_map(status_ids, account_id, group_id = nil) | |
+ StatusPin | |
+ .select('status_id') | |
+ .where(status_id: status_ids) | |
+ .where(account_id: account_id) | |
+ .where(pin_location: group_id ? :group : :profile) | |
+ .each_with_object({}) { |p, h| h[p.status_id] = true } | |
end | |
+ def groups_map(statuses) | |
+ statuses_slugs = statuses.map { |s| [s.id, extract_group_slugs(s.text)] }.to_h | |
+ slugs = statuses_slugs.values.uniq | |
+ return {} unless slugs.any? | |
+ | |
+ groups = Group.where(slug: slugs).includes(:tags, :group_stat).references(:tags, :group_stat).index_by(&:slug) | |
+ statuses_slugs.map { |status_id, slug| [status_id, groups[slug]] }.to_h.compact | |
+ end | |
+ | |
+ def polls_map(statuses, current_account_id) | |
+ statuses_with_polls = statuses.map { |s| s.id if s.has_poll }.compact.uniq | |
+ return {} unless statuses_with_polls.any? | |
+ | |
+ rendered_polls = StatusPolls.polls(account_id: current_account_id, status_ids: statuses_with_polls) | |
+ return {} unless rendered_polls | |
+ | |
+ rendered_polls.map { |poll| [poll['status_id'], poll['poll_json']] }.to_h.compact | |
+ end | |
+ | |
def reload_stale_associations!(cached_items) | |
account_ids = [] | |
+ status_ids = [] | |
+ group_ids = [] | |
cached_items.each do |item| | |
account_ids << item.account_id | |
account_ids << item.reblog.account_id if item.reblog? && item.reblog&.account_id | |
+ status_ids << item.id | |
+ group_ids << item.group_id if item.group_id | |
end | |
account_ids.uniq! | |
return if account_ids.empty? | |
- accounts = Account.where(id: account_ids).includes(:account_stat, :user).index_by(&:id) | |
+ accounts = Account.where(id: account_ids).includes(:account_follower, :account_following, :account_status, :user).references(:account_follower, :account_following, :account_status, :user).index_by(&:id) | |
+ statuses = Status.with_discarded.select([:favourites_count, :replies_count, :reblogs_count]).where(id: status_ids).includes(:status_favourite, :status_reply, :status_reblog).references(:status_favourite, :status_reply, :status_reblog).index_by(&:id) | |
+ groups = Group.where(id: group_ids).index_by(&:id) | |
cached_items.each do |item| | |
item.account = accounts[item.account_id] | |
item.reblog.account = accounts[item.reblog.account_id] if item.reblog? && item.reblog&.account_id | |
+ item.group = groups[item.group.id] if item.group? | |
+ | |
+ if statuses[item.id].status_favourite | |
+ item.status_favourite = statuses[item.id].status_favourite | |
+ else | |
+ item.build_status_favourite | |
+ end | |
+ | |
+ if statuses[item.id].status_reply | |
+ item.status_reply = statuses[item.id].status_reply | |
+ else | |
+ item.build_status_reply | |
+ end | |
+ | |
+ if statuses[item.id].status_reblog | |
+ item.status_reblog = statuses[item.id].status_reblog | |
+ else | |
+ item.build_status_reblog | |
+ end | |
end | |
end | |
@@ -375,20 +512,26 @@ | |
status&.distributable? ? status : nil | |
end | |
end | |
- end | |
- def status_stat | |
- super || build_status_stat | |
+ def muted_conversations_for_account(account_id) | |
+ sanitized_id = connection.quote(account_id.to_i) | |
+ select('*').from("(select s.* from statuses s | |
+ inner join conversations c on c.id = s.conversation_id | |
+ inner join conversation_mutes cm on cm.conversation_id = c.id | |
+ where cm.account_id = #{sanitized_id} and in_reply_to_id is null) as statuses") | |
+ end | |
+ | |
+ def tv_channels_statuses | |
+ find_by_sql(" | |
+ WITH distinct_statuses_by_channel AS( | |
+ select * from (select distinct on (channel_id) channel_id, status_id from tv.program_statuses tvp inner join tv.channels tvc using(channel_id) where tvc.enabled = true order by channel_id, start_time desc) sub order by channel_id asc | |
+ ) | |
+ select * from statuses s inner join distinct_statuses_by_channel dsc on s.id = dsc.status_id order by s.created_at desc") | |
+ end | |
end | |
private | |
- def update_status_stat!(attrs) | |
- return if marked_for_destruction? || destroyed? | |
- | |
- status_stat.update(attrs) | |
- end | |
- | |
def store_uri | |
update_column(:uri, ActivityPub::TagManager.instance.uri_for(self)) if uri.nil? | |
end | |
@@ -402,10 +545,6 @@ | |
self.reblog = reblog.reblog if reblog? && reblog.reblog? | |
end | |
- def set_poll_id | |
- update_column(:poll_id, poll.id) unless poll.nil? | |
- end | |
- | |
def set_visibility | |
self.visibility = reblog.visibility if reblog? && visibility.nil? | |
self.visibility = (account.locked? ? :private : :public) if visibility.nil? | |
@@ -421,6 +560,7 @@ | |
self.in_reply_to_account_id = carried_over_reply_to_account_id | |
self.conversation_id = thread.conversation_id if conversation_id.nil? | |
redis.del("descendants:#{conversation_id}") | |
+ InvalidateSecondaryCacheService.new.call('InvalidateDescendantsCacheWorker', conversation_id) | |
elsif conversation_id.nil? | |
self.conversation = Conversation.new | |
end | |
@@ -445,19 +585,11 @@ | |
end | |
def increment_counter_caches | |
- return if direct_visibility? | |
- | |
- account&.increment_count!(:statuses_count) | |
- reblog&.increment_count!(:reblogs_count) if reblog? | |
- thread&.increment_count!(:replies_count) if in_reply_to_id.present? && distributable? | |
+ group&.increment_count!(:statuses_count) | |
end | |
def decrement_counter_caches | |
- return if direct_visibility? | |
- | |
- account&.decrement_count!(:statuses_count) | |
- reblog&.decrement_count!(:reblogs_count) if reblog? | |
- thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && distributable? | |
+ group&.decrement_count!(:statuses_count) | |
end | |
def unlink_from_conversations | |
@@ -468,6 +600,19 @@ | |
inbox_owners.each do |inbox_owner| | |
AccountConversation.remove_status(inbox_owner, self) | |
+ end | |
+ end | |
+ | |
+ def purge_cache | |
+ Rails.cache.delete(self) | |
+ InvalidateSecondaryCacheService.new.call('InvalidateStatusCacheWorker', self) | |
+ end | |
+ | |
+ def mark_tv_status | |
+ related_status_id = reblog_of_id.presence || quote_id.presence || in_reply_to_id.presence | |
+ | |
+ if tv_program_status? || (related_status_id && TvStatus.find_by(status_id: related_status_id).present?) | |
+ TvStatus.create!(status: self) | |
end | |
end | |
end | |
Only in truth-new/opensource/app/models: status_distribution_br2.rb | |
Only in truth-new/opensource/app/models: status_distribution_or1.rb | |
Only in truth-new/opensource/app/models: status_favourite_statistic.rb | |
diff -ru truth-old/opensource/app/models/status_pin.rb truth-new/opensource/app/models/status_pin.rb | |
--- truth-old/opensource/app/models/status_pin.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/status_pin.rb 2024-04-01 14:59:13 | |
@@ -3,16 +3,27 @@ | |
# | |
# Table name: status_pins | |
# | |
-# id :bigint(8) not null, primary key | |
-# account_id :bigint(8) not null | |
-# status_id :bigint(8) not null | |
-# created_at :datetime not null | |
-# updated_at :datetime not null | |
+# id :bigint(8) not null, primary key | |
+# account_id :bigint(8) not null | |
+# status_id :bigint(8) not null | |
+# created_at :datetime not null | |
+# updated_at :datetime not null | |
+# pin_location :enum default("profile"), not null | |
# | |
class StatusPin < ApplicationRecord | |
belongs_to :account | |
belongs_to :status | |
- validates_with StatusPinValidator | |
+ enum pin_location: { group: 'group', profile: 'profile' }, _suffix: :location | |
+ | |
+ validates_with StatusPinValidator, unless: -> { is_group_pin } | |
+ validates_with GroupStatusPinValidator, if: -> { is_group_pin } | |
+ | |
+ scope :profile_pins, -> { where(pin_location: :profile) } | |
+ scope :group_pins, -> { where(pin_location: :group) } | |
+ | |
+ def is_group_pin | |
+ group_location? | |
+ end | |
end | |
Only in truth-new/opensource/app/models: status_polls.rb | |
Only in truth-new/opensource/app/models: status_reblog_statistic.rb | |
Only in truth-new/opensource/app/models: status_replies.rb | |
Only in truth-new/opensource/app/models: status_replies_v1.rb | |
Only in truth-new/opensource/app/models: status_replies_v2.rb | |
Only in truth-new/opensource/app/models: status_reply_statistic.rb | |
diff -ru truth-old/opensource/app/models/status_stat.rb truth-new/opensource/app/models/status_stat.rb | |
--- truth-old/opensource/app/models/status_stat.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/status_stat.rb 2024-04-01 14:59:13 | |
@@ -29,8 +29,6 @@ | |
[attributes['favourites_count'], 0].max | |
end | |
- private | |
- | |
def reset_parent_cache | |
Rails.cache.delete("statuses/#{status_id}") | |
InvalidateSecondaryCacheService.new.call("InvalidateStatusCacheWorker", status_id) | |
Only in truth-new/opensource/app/models: status_suggestion.rb | |
Only in truth-new/opensource/app/models: statuses | |
Only in truth-new/opensource/app/models: suggestions_carousel.rb | |
diff -ru truth-old/opensource/app/models/tag.rb truth-new/opensource/app/models/tag.rb | |
--- truth-old/opensource/app/models/tag.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/tag.rb 2024-04-01 14:59:13 | |
@@ -8,7 +8,7 @@ | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# usable :boolean | |
-# trendable :boolean | |
+# trendable :boolean default(TRUE), not null | |
# listable :boolean | |
# reviewed_at :datetime | |
# requested_review_at :datetime | |
@@ -18,6 +18,9 @@ | |
# | |
class Tag < ApplicationRecord | |
+ extend Queriable | |
+ include Paginable | |
+ | |
has_and_belongs_to_many :statuses | |
has_and_belongs_to_many :accounts | |
@@ -30,16 +33,20 @@ | |
validates :name, presence: true, format: { with: /\A(#{HASHTAG_NAME_RE})\z/i } | |
validate :validate_name_change, if: -> { !new_record? && name_changed? } | |
+ before_create :unlist_bannable_tags | |
+ | |
scope :reviewed, -> { where.not(reviewed_at: nil) } | |
scope :unreviewed, -> { where(reviewed_at: nil) } | |
scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) } | |
scope :usable, -> { where(usable: [true, nil]) } | |
scope :listable, -> { where(listable: [true, nil]) } | |
- scope :trendable, -> { where(trendable: true).order(last_status_at: :desc) } | |
+ scope :trendable, -> { where(trendable: true).where.not(max_score: nil).order(max_score: :desc, last_status_at: :desc) } | |
+ scope :only_trendable, -> { where(trendable: true).order(max_score: :desc, last_status_at: :desc) } | |
scope :recently_used, ->(account) { joins(:statuses).where(statuses: { id: account.statuses.select(:id).limit(1000) }).group(:id).order(Arel.sql('count(*) desc')) } | |
scope :matches_name, ->(term) { where(arel_table[:name].lower.matches("#{sanitize_sql_like(Tag.normalize(term.downcase))}%", nil, true)) } # Search with case-sensitive to use B-tree index | |
+ scope :search, ->(query) { where('LOWER(tags.name) LIKE :search', search: "%#{sanitize_sql_like(query&.downcase)}%") } | |
- update_index 'tags#tag', :self | |
+ update_index 'tags', :self | |
def contains_prohibited_terms? | |
name_downcase = name.downcase | |
@@ -91,7 +98,7 @@ | |
def history | |
days = [] | |
- 7.times do |i| | |
+ 1.upto(6) do |i| | |
day = i.days.ago.beginning_of_day.to_i | |
days << { | |
@@ -115,15 +122,9 @@ | |
end | |
end | |
- def search_for(term, limit = 5, offset = 0, options = {}) | |
- stripped_term = term.strip | |
- | |
- query = Tag.listable.matches_name(stripped_term) | |
- query = query.merge(matching_name(stripped_term).or(where.not(reviewed_at: nil))) if options[:exclude_unreviewed] | |
- | |
- query.order(Arel.sql('length(name) ASC, name ASC')) | |
- .limit(limit) | |
- .offset(offset) | |
+ # options = [in_search_query text, in_limit smallint, in_offset integer] | |
+ def search_for(*options) | |
+ execute_query('select mastodon_api.search_tags ($1, $2, $3)', options).to_a.first['search_tags'] | |
end | |
def find_normalized(name) | |
@@ -150,6 +151,17 @@ | |
end | |
private | |
+ | |
+ def unlist_bannable_tags | |
+ banned_words = BannedWord.pluck(:word) | |
+ regexp = Regexp.new(banned_words.join('|'), true) | |
+ bannable = regexp === name | |
+ | |
+ if bannable | |
+ self.listable = false | |
+ self.trendable = false | |
+ end | |
+ end | |
def validate_name_change | |
errors.add(:name, I18n.t('tags.does_not_match_previous_name')) unless name_was.mb_chars.casecmp(name.mb_chars).zero? | |
diff -ru truth-old/opensource/app/models/tag_feed.rb truth-new/opensource/app/models/tag_feed.rb | |
--- truth-old/opensource/app/models/tag_feed.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/tag_feed.rb 2024-04-01 14:59:13 | |
@@ -14,6 +14,7 @@ | |
# @option [Boolean] :only_media | |
def initialize(tag, account, options = {}) | |
@tag = tag | |
+ @group_id = options[:group_id] | |
super(account, options) | |
end | |
@@ -23,7 +24,7 @@ | |
# @param [Integer] min_id | |
# @return [Array<Status>] | |
def get(limit, max_id = nil, since_id = nil, min_id = nil) | |
- scope = public_scope | |
+ scope = @group_id ? group_scope : public_scope | |
scope.merge!(tagged_with_any_scope) | |
scope.merge!(tagged_with_all_scope) | |
@@ -52,5 +53,12 @@ | |
def tags_for(names) | |
Tag.matching_name(Array(names).take(LIMIT_PER_MODE)).pluck(:id) if names.present? | |
+ end | |
+ | |
+ def group_scope | |
+ Status.without_reblogs | |
+ .where(group_id: @group_id, reply: false, quote_id: nil) | |
+ .joins(:account) | |
+ .merge(Account.without_suspended.without_silenced.excluded_by_group_account_block(@group_id)) | |
end | |
end | |
Only in truth-old/opensource/app/models: trending.rb | |
Only in truth-new/opensource/app/models: trending_status.rb | |
Only in truth-new/opensource/app/models: trending_status_excluded_expression.rb | |
Only in truth-new/opensource/app/models: trending_status_excluded_status.rb | |
Only in truth-new/opensource/app/models: trending_status_setting.rb | |
diff -ru truth-old/opensource/app/models/trending_tags.rb truth-new/opensource/app/models/trending_tags.rb | |
--- truth-old/opensource/app/models/trending_tags.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/trending_tags.rb 2024-04-01 14:59:13 | |
@@ -13,23 +13,11 @@ | |
class << self | |
include Redisable | |
- def record_use!(tag, account, status: nil, at_time: Time.now.utc) | |
- return unless tag.usable? && !account.silenced? | |
+ def record_use!(tag, account, status: nil, at_time: Time.now.utc); end | |
- # Even if a tag is not allowed to trend, we still need to | |
- # record the stats since they can be displayed in other places | |
- increment_historical_use!(tag.id, at_time) | |
- increment_unique_use!(tag.id, account.id, at_time) | |
- increment_use!(tag.id, at_time) | |
- | |
- # Only update when the tag was last used once every 12 hours | |
- # and only if a status is given (lets use ignore reblogs) | |
- tag.update(last_status_at: at_time) if status.present? && (tag.last_status_at.nil? || (tag.last_status_at < at_time && tag.last_status_at < 12.hours.ago)) | |
- end | |
- | |
def update!(at_time = Time.now.utc) | |
tag_ids = redis.smembers("#{KEY}:used:#{at_time.beginning_of_day.to_i}") + redis.zrange(KEY, 0, -1) | |
- tags = Tag.trendable.where(id: tag_ids.uniq) | |
+ tags = Tag.where(trendable: true).where(id: tag_ids.uniq) | |
# First pass to calculate scores and update the set | |
Only in truth-new/opensource/app/models: trending_tags_result.rb | |
Only in truth-new/opensource/app/models: tv_account.rb | |
Only in truth-new/opensource/app/models: tv_carousel.rb | |
Only in truth-new/opensource/app/models: tv_channel.rb | |
Only in truth-new/opensource/app/models: tv_channel_account.rb | |
Only in truth-new/opensource/app/models: tv_device_session.rb | |
Only in truth-new/opensource/app/models: tv_program.rb | |
Only in truth-new/opensource/app/models: tv_program_status.rb | |
Only in truth-new/opensource/app/models: tv_program_temporary.rb | |
Only in truth-new/opensource/app/models: tv_reminder.rb | |
Only in truth-new/opensource/app/models: tv_status.rb | |
diff -ru truth-old/opensource/app/models/user.rb truth-new/opensource/app/models/user.rb | |
--- truth-old/opensource/app/models/user.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/user.rb 2024-04-05 09:07:34 | |
@@ -3,7 +3,6 @@ | |
# | |
# Table name: users | |
# | |
-# id :bigint(8) not null, primary key | |
# email :string default(""), not null | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
@@ -31,6 +30,7 @@ | |
# otp_backup_codes :string is an Array | |
# filtered_languages :string default([]), not null, is an Array | |
# account_id :bigint(8) not null | |
+# id :bigint(8) not null, primary key | |
# disabled :boolean default(FALSE), not null | |
# moderator :boolean default(FALSE), not null | |
# invite_id :bigint(8) | |
@@ -46,12 +46,16 @@ | |
# waitlist_position :integer | |
# unsubscribe_from_emails :boolean default(FALSE) | |
# ready_to_approve :integer default("not_ready_for_approval") | |
-# unauth_visibility :boolean | |
+# unauth_visibility :boolean default(TRUE), not null | |
+# policy_id :bigint(8) | |
+# sign_up_city_id :integer not null | |
+# sign_up_country_id :integer not null | |
# | |
class User < ApplicationRecord | |
include Settings::Extend | |
include UserRoles | |
+ include EmailHelper | |
# The home and list feeds will be stored in Redis for this amount | |
# of time, and status fan-out to followers will include only people | |
@@ -61,7 +65,15 @@ | |
# RegenerationWorker jobs that need to be run when those people come | |
# to check their feed | |
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze | |
- WAITLIST_PADDING = ENV.fetch('WAITLIST_PADDING', 50000).to_i | |
+ WAITLIST_PADDING = ENV.fetch('WAITLIST_PADDING', 50_000).to_i | |
+ BASE_EMAIL_DOMAINS_VALIDATION = ENV.fetch('BASE_EMAIL_DOMAINS_VALIDATION', false) | |
+ VERIFICATION_INTERVAL = 1.hour.ago.freeze | |
+ INTEGRITY_STATUSES = { | |
+ favourite: 'favourite', | |
+ status: 'status', | |
+ chat_message: 'chat_message', | |
+ reblog: 'reblog', | |
+ }.freeze | |
devise :two_factor_authenticatable, | |
otp_secret_encryption_key: Rails.configuration.x.otp_secret | |
@@ -79,6 +91,9 @@ | |
belongs_to :account, inverse_of: :user | |
belongs_to :invite, counter_cache: :uses, optional: true | |
belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true | |
+ belongs_to :policy, optional: true | |
+ belongs_to :city, class_name: 'City', foreign_key: 'sign_up_city_id', optional: true | |
+ belongs_to :country, class_name: 'Country', foreign_key: 'sign_up_country_id', optional: true | |
accepts_nested_attributes_for :account | |
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner | |
@@ -86,15 +101,16 @@ | |
has_many :invites, inverse_of: :user | |
has_many :markers, inverse_of: :user, dependent: :destroy | |
has_many :webauthn_credentials, dependent: :destroy | |
+ has_many :one_time_challenges, dependent: :destroy | |
+ has_many :password_histories, class_name: 'PasswordHistory' | |
has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy | |
- has_one :trending | |
+ has_one :user_current_information | |
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text } | |
validates :invite_request, presence: true, on: :create, if: :invite_text_required? | |
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? | |
validates_with BlacklistedEmailValidator, on: :create | |
- validates_with EmailMxValidator, if: :validate_email_dns? | |
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create | |
# Those are honeypot/antispam fields | |
@@ -102,8 +118,11 @@ | |
validates_with RegistrationFormTimeValidator, on: :create | |
validates :website, absence: true, on: :create | |
+ validates :password, unique_password: true | |
validates :confirm_password, absence: true, on: :create | |
+ validates_with BaseEmailValidator, on: :create | |
+ | |
scope :recent, -> { order(id: :desc) } | |
scope :pending, -> { where(approved: false) } | |
scope :approved, -> { where(approved: true) } | |
@@ -122,6 +141,8 @@ | |
before_create :skip_confirmation_if_invited | |
after_commit :send_pending_devise_notifications | |
after_update_commit :send_approved_notification | |
+ after_create :create_base_email | |
+ after_save :store_password_history | |
# This avoids a deprecation warning from Rails 5.1 | |
# It seems possible that a future release of devise-two-factor will | |
@@ -130,6 +151,11 @@ | |
has_many :session_activations, dependent: :destroy | |
+ has_one :user_base_email | |
+ | |
+ has_one :user_sms_reverification_required | |
+ scope :with_reverification, -> { eager_load(:user_sms_reverification_required) } | |
+ | |
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, | |
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network, | |
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application, | |
@@ -141,7 +167,7 @@ | |
attr_writer :external, :bypass_invite_request_check | |
enum ready_to_approve: { not_ready_for_approval: 0, ready_by_csv_import: 1, ready_by_sms_verification: 2, sent_one_push_notification: 3, sent_two_push_notifications: 4, sent_three_push_notifications: 5 } | |
- self.ignored_columns = ["reviewed_for_approval"] | |
+ self.ignored_columns = ['reviewed_for_approval'] | |
def confirmed? | |
confirmed_at.present? | |
@@ -185,7 +211,7 @@ | |
end | |
def confirm! | |
- new_user = !confirmed? | |
+ new_user = !confirmed? | |
skip_confirmation! | |
save! | |
@@ -209,12 +235,6 @@ | |
if new_sign_in | |
self.sign_in_count ||= 0 | |
self.sign_in_count += 1 | |
- else | |
- query = if old_current_sign_in.nil? | |
- query.where('current_sign_in_at' => nil) | |
- else | |
- query.where('current_sign_in_at < :time', time: UserTrackingConcern::UPDATE_SIGN_IN_HOURS.hours.ago) | |
- end | |
end | |
unless new_record? | |
@@ -224,6 +244,17 @@ | |
current_sign_in_ip: current_sign_in_ip, | |
sign_in_count: sign_in_count) | |
end | |
+ | |
+ UserCurrentInformation.upsert( | |
+ user_id: id, | |
+ current_sign_in_at: new_current_sign_in, | |
+ current_sign_in_ip: new_current_ip, | |
+ current_city_id: geo(request).city, | |
+ current_country_id: geo(request).country | |
+ ) | |
+ | |
+ EventProvider::EventProvider.new('session.updated', SessionUpdatedEvent, { user_id: id, account_id: account_id, ip_address: new_current_ip, timestamp: new_current_sign_in }).call | |
+ | |
prepare_returning_user! | |
end | |
@@ -232,13 +263,13 @@ | |
end | |
def self.get_user_from_token(user_token) | |
- id, _updated_at_s = EncryptAttrService.decrypt(user_token).split("+=") | |
+ id, _updated_at_s = EncryptAttrService.decrypt(user_token).split('+=') | |
find_by(id: id) | |
end | |
def validate_user_token(user_token) | |
- _id, updated_at_s = EncryptAttrService.decrypt(user_token).split("+=") | |
+ _id, updated_at_s = EncryptAttrService.decrypt(user_token).split('+=') | |
updated_at.to_s == updated_at_s | |
end | |
@@ -251,6 +282,23 @@ | |
sms.present? | |
end | |
+ # remove once all devices have completed the force update | |
+ def integrity_score | |
+ return 0 unless ActiveModel::Type::Boolean.new.cast(ENV.fetch('PLAY_INTEGRITY_ENABLED', true)) # Enable/Disable app integrity for all users | |
+ | |
+ last_status_at = AccountStatusStatistic.find_by(account_id: account.id)&.last_status_at | |
+ first_status_today = last_status_at ? last_status_at < Time.zone.now.midnight : true | |
+ first_status_today ? 1 : 0 | |
+ end | |
+ | |
+ def integrity_status(token, android_client) | |
+ return [] unless android_client | |
+ return [] unless user_sms_reverification_required | |
+ | |
+ integrity_credential = token.integrity_credentials.order(last_verified_at: :desc).first | |
+ integrity_credential&.last_verified_at&.send(:>, VERIFICATION_INTERVAL) ? [] : INTEGRITY_STATUSES.values | |
+ end | |
+ | |
def active_for_authentication? | |
!account.memorial? | |
end | |
@@ -288,7 +336,7 @@ | |
end | |
def two_factor_enabled? | |
- otp_required_for_login? || webauthn_credentials.any? | |
+ otp_required_for_login? | |
end | |
def disable_two_factor! | |
@@ -296,8 +344,6 @@ | |
self.otp_secret = nil | |
otp_backup_codes&.clear | |
- webauthn_credentials.destroy_all if webauthn_enabled? | |
- | |
save! | |
end | |
@@ -305,7 +351,7 @@ | |
return 0 if approved? | |
most_recent_user = User.pending.order(waitlist_position: :desc).first | |
- position = most_recent_user&.waitlist_position || 11342 # this is a magic number means nothing could be anything | |
+ position = most_recent_user&.waitlist_position || 11_342 # this is a magic number means nothing could be anything | |
self.waitlist_position = position + 1 | |
save! | |
@@ -357,7 +403,7 @@ | |
# rubocop:disable Naming/MethodParameterName | |
def token_for_app(a) | |
return nil if a.nil? || a.owner != self | |
- Doorkeeper::AccessToken.find_or_create_by(application_id: a.id, resource_owner_id: id) do |t| | |
+ OauthAccessToken.find_or_create_by(application_id: a.id, resource_owner_id: id) do |t| | |
t.scopes = a.scopes | |
t.expires_in = Doorkeeper.configuration.access_token_expires_in | |
t.use_refresh_token = Doorkeeper.configuration.refresh_token_enabled? | |
@@ -456,6 +502,10 @@ | |
nil | |
end | |
+ def sms_country | |
+ Phonelib.parse(sms).country | |
+ end | |
+ | |
protected | |
def send_devise_notification(notification, *args, **kwargs) | |
@@ -540,7 +590,7 @@ | |
def prepare_returning_user! | |
ActivityTracker.record('activity:logins', id) | |
- regenerate_feed! if needs_feed_update? | |
+ clear_feeds! if needs_feed_update? | |
end | |
def notify_staff_about_pending_account! | |
@@ -554,6 +604,13 @@ | |
RegenerationWorker.perform_async(account_id) if Redis.current.set("account:#{account_id}:regeneration", true, nx: true, ex: 1.day.seconds) | |
end | |
+ def clear_feeds! | |
+ home_feed = HomeFeed.new(account) | |
+ home_feed.clear! | |
+ groups_feed = GroupsFeed.new(account) | |
+ groups_feed.clear! | |
+ end | |
+ | |
def needs_feed_update? | |
last_sign_in_at < ACTIVE_DURATION.ago | |
end | |
@@ -573,16 +630,40 @@ | |
end | |
def hourly_limit_reached? | |
- key = "approved_users_per_hour:#{DateTime.current.strftime("%Y-%m-%d:%H:00")}" | |
+ key = "approved_users_per_hour:#{DateTime.current.strftime('%Y-%m-%d:%H:00')}" | |
return unless (limit_per_hour = ENV['USERS_PER_HOUR'].to_i) > 0 | |
current_limit = Redis.current.scard(key) | |
current_limit.present? && current_limit.to_i >= limit_per_hour | |
end | |
def track_approved_user | |
- key = "approved_users_per_hour:#{DateTime.current.strftime("%Y-%m-%d:%H:00")}" | |
+ key = "approved_users_per_hour:#{DateTime.current.strftime('%Y-%m-%d:%H:00')}" | |
Redis.current.sadd(key, id) | |
Redis.current.expire(key, 65.minutes.seconds) | |
- Prometheus::ApplicationExporter::increment(:approves) | |
+ Prometheus::ApplicationExporter.increment(:approves) | |
+ end | |
+ | |
+ def geo(request) | |
+ @geo_object ||= GeoService.new( | |
+ city_name: request.headers['Geoip-City-Name'], | |
+ country_code: request.headers['Geoip-Country-Code'], | |
+ country_name: request.headers['Geoip-Country-Name'], | |
+ region_name: request.headers['Geoip-Region-Name'], | |
+ region_code: request.headers['Geoip-Region-Code'] | |
+ ) | |
+ end | |
+ | |
+ def create_base_email | |
+ return unless BASE_EMAIL_DOMAINS_VALIDATION | |
+ | |
+ username, domain = email_to_canonical_email_by_username_and_domain(email).values_at(:username, :domain) | |
+ | |
+ return unless BASE_EMAIL_DOMAINS_VALIDATION.split(',').map(&:strip).include? domain | |
+ | |
+ UserBaseEmail.create(user_id: id, email: "#{username}@#{domain}") | |
+ end | |
+ | |
+ def store_password_history | |
+ PasswordHistory.create!(user: self, encrypted_password: encrypted_password) if saved_change_to_encrypted_password? | |
end | |
end | |
Only in truth-new/opensource/app/models: user_base_email.rb | |
Only in truth-new/opensource/app/models: user_current_information.rb | |
Only in truth-new/opensource/app/models: user_sms_reverification_required.rb | |
Only in truth-new/opensource/app/models: users_one_time_challenge.rb | |
diff -ru truth-old/opensource/app/models/web/push_subscription.rb truth-new/opensource/app/models/web/push_subscription.rb | |
--- truth-old/opensource/app/models/web/push_subscription.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/web/push_subscription.rb 2024-04-01 14:59:13 | |
@@ -19,7 +19,7 @@ | |
class Web::PushSubscription < ApplicationRecord | |
belongs_to :user, optional: true | |
- belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', optional: true | |
+ belongs_to :access_token, class_name: 'OauthAccessToken', optional: true | |
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription | |
@@ -74,7 +74,7 @@ | |
class << self | |
def unsubscribe_for(application_id, resource_owner) | |
- access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id) | |
+ access_token_ids = OauthAccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id) | |
where(access_token_id: access_token_ids).delete_all | |
end | |
end | |
@@ -82,7 +82,7 @@ | |
private | |
def find_or_create_access_token | |
- Doorkeeper::AccessToken.find_or_create_for( | |
+ OauthAccessToken.find_or_create_for( | |
application: Doorkeeper::Application.find_by(superapp: true), | |
resource_owner: user_id || session_activation.user_id, | |
scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow push'), | |
diff -ru truth-old/opensource/app/models/webauthn_credential.rb truth-new/opensource/app/models/webauthn_credential.rb | |
--- truth-old/opensource/app/models/webauthn_credential.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/models/webauthn_credential.rb 2024-04-01 14:59:13 | |
@@ -3,20 +3,65 @@ | |
# | |
# Table name: webauthn_credentials | |
# | |
-# id :bigint(8) not null, primary key | |
-# external_id :string not null | |
-# public_key :string not null | |
-# nickname :string not null | |
-# sign_count :bigint(8) default(0), not null | |
-# user_id :bigint(8) | |
-# created_at :datetime not null | |
-# updated_at :datetime not null | |
+# id :bigint(8) not null, primary key | |
+# external_id :string not null | |
+# public_key :string not null | |
+# nickname :string not null | |
+# sign_count :bigint(8) default(0), not null | |
+# user_id :bigint(8) | |
+# created_at :datetime not null | |
+# updated_at :datetime not null | |
+# receipt :text | |
+# fraud_metric :integer | |
+# receipt_updated_at :datetime | |
+# baseline_fraud_metric :integer default(0), not null | |
+# sandbox :boolean default(FALSE), not null | |
# | |
class WebauthnCredential < ApplicationRecord | |
+ extend AppAttestable | |
+ | |
validates :external_id, :public_key, :nickname, :sign_count, presence: true | |
validates :external_id, uniqueness: true | |
validates :nickname, uniqueness: { scope: :user_id } | |
validates :sign_count, | |
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**63 - 1 } | |
+ | |
+ has_one :one_time_challenge | |
+ has_one :registration_webauthn_credential | |
+ has_one :registration, through: :registration_webauthn_credential | |
+ belongs_to :user, optional: true | |
+ has_many :token_credentials, class_name: 'OauthAccessTokens::WebauthnCredential' | |
+ | |
+ class << self | |
+ def decode_receipt(encoded_receipt:, all_fields: false) | |
+ receipt = Base64.strict_decode64(encoded_receipt) | |
+ certificates, payload = verify_and_decode receipt | |
+ fields_hash = extract_field_values(payload) | |
+ | |
+ { | |
+ **user_sensitive_fields(all_fields, certificates, fields_hash), | |
+ receipt_type: fields_hash[6], | |
+ creation_time: fields_hash[12], | |
+ risk_metric: fields_hash[17], | |
+ not_before: fields_hash[19], | |
+ expiration_time: fields_hash[21], | |
+ } | |
+ end | |
+ | |
+ private | |
+ | |
+ def user_sensitive_fields(all_fields, certificates, fields_hash) | |
+ return {} unless all_fields | |
+ | |
+ { | |
+ certificate_chain: certificates, | |
+ app_id: fields_hash[2], | |
+ attested_public_key: fields_hash[3], | |
+ client_hash: fields_hash[4], | |
+ token: fields_hash[5], | |
+ } | |
+ end | |
+ end | |
end | |
+ | |
Only in truth-new/opensource/app/policies: feed_policy.rb | |
Only in truth-new/opensource/app/policies: group_membership_policy.rb | |
Only in truth-new/opensource/app/policies: group_membership_request_policy.rb | |
Only in truth-new/opensource/app/policies: group_policy.rb | |
diff -ru truth-old/opensource/app/policies/poll_policy.rb truth-new/opensource/app/policies/poll_policy.rb | |
--- truth-old/opensource/app/policies/poll_policy.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/policies/poll_policy.rb 2024-04-01 14:59:13 | |
@@ -2,6 +2,6 @@ | |
class PollPolicy < ApplicationPolicy | |
def vote? | |
- StatusPolicy.new(current_account, record.status).show? && !current_account.blocking?(record.account) && !record.account.blocking?(current_account) | |
+ StatusPolicy.new(current_account, record.status).show? && !current_account.blocking?(record.status.account) && !record.status.account.blocking?(current_account) | |
end | |
end | |
diff -ru truth-old/opensource/app/policies/status_policy.rb truth-new/opensource/app/policies/status_policy.rb | |
--- truth-old/opensource/app/policies/status_policy.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/policies/status_policy.rb 2024-04-12 09:09:08 | |
@@ -14,10 +14,14 @@ | |
def show? | |
return false if author.suspended? | |
- if requires_mention? | |
+ if group? | |
+ owned? || public_group? || private_group_member? | |
+ elsif requires_mention? | |
owned? || mention_exists? | |
elsif private? | |
owned? || following_author? || mention_exists? | |
+ elsif tv? | |
+ current_account.tv_enabled? | |
else | |
current_account.nil? || (!author_blocking? && !author_blocking_domain?) | |
end | |
@@ -51,10 +55,18 @@ | |
author.id == current_account&.id | |
end | |
+ def group? | |
+ record.group_visibility? | |
+ end | |
+ | |
def private? | |
record.private_visibility? | |
end | |
+ def tv? | |
+ !!record.tv_program | |
+ end | |
+ | |
def mention_exists? | |
return false if current_account.nil? | |
@@ -87,6 +99,22 @@ | |
return false if current_account.nil? | |
@preloaded_relations[:following] ? @preloaded_relations[:following][author.id] : current_account.following?(author) | |
+ end | |
+ | |
+ def public_group? | |
+ record.group.everyone? | |
+ end | |
+ | |
+ def private_group_member? | |
+ return false unless record.group.members_only? | |
+ | |
+ group_member? | |
+ end | |
+ | |
+ def group_member? | |
+ return false if current_account.nil? || record.group_id.nil? | |
+ | |
+ record.group.members.where(id: current_account&.id).exists? | |
end | |
def author | |
diff -ru truth-old/opensource/app/policies/user_policy.rb truth-new/opensource/app/policies/user_policy.rb | |
--- truth-old/opensource/app/policies/user_policy.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/policies/user_policy.rb 2024-04-01 14:59:13 | |
@@ -49,6 +49,20 @@ | |
admin? && !record.admin? && demoteable? | |
end | |
+ def enable_sms_reverification? | |
+ admin? | |
+ end | |
+ | |
+ def disable_sms_reverification? | |
+ admin? | |
+ end | |
+ | |
+ | |
+ def enable_feature? | |
+ admin? | |
+ end | |
+ | |
+ | |
private | |
def promoteable? | |
diff -ru truth-old/opensource/app/presenters/account_relationships_presenter.rb truth-new/opensource/app/presenters/account_relationships_presenter.rb | |
--- truth-old/opensource/app/presenters/account_relationships_presenter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/presenters/account_relationships_presenter.rb 2024-04-01 14:59:13 | |
@@ -49,36 +49,38 @@ | |
account_note: {}, | |
} | |
- @uncached_account_ids = [] | |
+ @uncached_account_ids = @account_ids.uniq | |
- @account_ids.each do |account_id| | |
- maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}") | |
- | |
- if maps_for_account.is_a?(Hash) | |
- @cached.deep_merge!(maps_for_account) | |
- else | |
- @uncached_account_ids << account_id | |
- end | |
+ cache_ids = @account_ids.map { |account_id| relationship_cache_key(account_id) } | |
+ Rails.cache.read_multi(*cache_ids).each do |key, maps_for_account| | |
+ @cached.deep_merge!(maps_for_account) | |
+ @uncached_account_ids.delete(key.last) | |
end | |
@cached | |
end | |
def cache_uncached! | |
- @uncached_account_ids.each do |account_id| | |
+ to_cache = @uncached_account_ids.to_h do |account_id| | |
maps_for_account = { | |
- following: { account_id => following[account_id] }, | |
- followed_by: { account_id => followed_by[account_id] }, | |
- blocking: { account_id => blocking[account_id] }, | |
- blocked_by: { account_id => blocked_by[account_id] }, | |
- muting: { account_id => muting[account_id] }, | |
- requested: { account_id => requested[account_id] }, | |
+ following: { account_id => following[account_id] }, | |
+ followed_by: { account_id => followed_by[account_id] }, | |
+ blocking: { account_id => blocking[account_id] }, | |
+ blocked_by: { account_id => blocked_by[account_id] }, | |
+ muting: { account_id => muting[account_id] }, | |
+ requested: { account_id => requested[account_id] }, | |
domain_blocking: { account_id => domain_blocking[account_id] }, | |
- endorsed: { account_id => endorsed[account_id] }, | |
- account_note: { account_id => account_note[account_id] }, | |
+ endorsed: { account_id => endorsed[account_id] }, | |
+ account_note: { account_id => account_note[account_id] }, | |
} | |
- Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day) | |
+ [relationship_cache_key(account_id), maps_for_account] | |
end | |
+ | |
+ Rails.cache.write_multi(to_cache, expires_in: 1.day) | |
+ end | |
+ | |
+ def relationship_cache_key(account_id) | |
+ ['relationship', @current_account_id, account_id] | |
end | |
end | |
Only in truth-new/opensource/app/presenters: feed_relationships_presenter.rb | |
Only in truth-new/opensource/app/presenters: group_relationships_presenter.rb | |
diff -ru truth-old/opensource/app/presenters/instance_presenter.rb truth-new/opensource/app/presenters/instance_presenter.rb | |
--- truth-old/opensource/app/presenters/instance_presenter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/presenters/instance_presenter.rb 2024-04-01 14:59:13 | |
@@ -29,7 +29,7 @@ | |
end | |
def status_count | |
- Rails.cache.fetch('local_status_count') { Account.local.joins(:account_stat).sum('account_stats.statuses_count') }.to_i | |
+ Rails.cache.fetch('local_status_count') { Account.local.joins(:account_status).sum('statuses_count') }.to_i | |
end | |
def domain_count | |
diff -ru truth-old/opensource/app/presenters/status_relationships_presenter.rb truth-new/opensource/app/presenters/status_relationships_presenter.rb | |
--- truth-old/opensource/app/presenters/status_relationships_presenter.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/presenters/status_relationships_presenter.rb 2024-04-01 14:59:13 | |
@@ -2,26 +2,32 @@ | |
class StatusRelationshipsPresenter | |
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map, | |
- :bookmarks_map | |
+ :bookmarks_map, :groups_map, :polls_map | |
- def initialize(statuses, current_account_id = nil, **options) | |
+ def initialize(statuses, current_account_id = nil, group_id = nil, **options) | |
if current_account_id.nil? | |
@reblogs_map = {} | |
@favourites_map = {} | |
@bookmarks_map = {} | |
+ @groups_map = {} | |
@mutes_map = {} | |
@pins_map = {} | |
+ @polls_map = {} | |
else | |
statuses = statuses.compact | |
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact | |
conversation_ids = statuses.filter_map(&:conversation_id).uniq | |
pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && %w(public unlisted).include?(s.visibility) } | |
+ pinnable_group_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.group_visibility? } | |
@reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {}) | |
@favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {}) | |
- @bookmarks_map = Status.bookmarks_map(status_ids, current_account_id).merge(options[:bookmarks_map] || {}) | |
+ @bookmarks_map = {} | |
@mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {}) | |
- @pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {}) | |
+ @group_pins_map = Status.pins_map(pinnable_group_status_ids, current_account_id, group_id).merge(options[:pins_map] || {}) | |
+ @pins_map = group_id ? @group_pins_map : Status.pins_map(pinnable_status_ids, current_account_id, group_id).merge(options[:pins_map] || {}) | |
+ @groups_map = Status.groups_map(statuses) | |
+ @polls_map = Status.polls_map(statuses, current_account_id) | |
end | |
end | |
end | |
Only in truth-new/opensource/app/presenters: v2 | |
diff -ru truth-old/opensource/app/serializers/activitypub/actor_serializer.rb truth-new/opensource/app/serializers/activitypub/actor_serializer.rb | |
--- truth-old/opensource/app/serializers/activitypub/actor_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/activitypub/actor_serializer.rb 2024-04-01 14:59:13 | |
@@ -10,7 +10,7 @@ | |
:discoverable, :olm, :suspended | |
attributes :id, :type, :following, :followers, | |
- :inbox, :outbox, :featured, :featured_tags, | |
+ :featured, :featured_tags, | |
:preferred_username, :name, :summary, | |
:url, :manually_approves_followers, | |
:discoverable, :published | |
@@ -27,12 +27,6 @@ | |
class EndpointsSerializer < ActivityPub::Serializer | |
include RoutingHelper | |
- | |
- attributes :shared_inbox | |
- | |
- def shared_inbox | |
- inbox_url | |
- end | |
end | |
has_one :endpoints, serializer: EndpointsSerializer | |
@@ -43,7 +37,7 @@ | |
delegate :suspended?, :instance_actor?, to: :object | |
def id | |
- object.instance_actor? ? instance_actor_url : account_url(object) | |
+ account_url(object) | |
end | |
def type | |
@@ -66,16 +60,8 @@ | |
account_followers_url(object) | |
end | |
- def inbox | |
- object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object) | |
- end | |
- | |
def devices | |
account_collection_url(object, :devices) | |
- end | |
- | |
- def outbox | |
- object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object) | |
end | |
def featured | |
diff -ru truth-old/opensource/app/serializers/manifest_serializer.rb truth-new/opensource/app/serializers/manifest_serializer.rb | |
--- truth-old/opensource/app/serializers/manifest_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/manifest_serializer.rb 2023-05-05 13:42:02 | |
@@ -68,6 +68,48 @@ | |
sizes: '512x512', | |
type: 'image/png', | |
}, | |
+ { | |
+ src: '/icons/icon-maskable-48x48.png', | |
+ sizes: '48x48', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
+ { | |
+ src: '/icons/icon-maskable-72x72.png', | |
+ sizes: '72x72', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
+ { | |
+ src: '/icons/icon-maskable-96x96.png', | |
+ sizes: '96x96', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
+ { | |
+ src: '/icons/icon-maskable-128x128.png', | |
+ sizes: '128x128', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
+ { | |
+ src: '/icons/icon-maskable-192x192.png', | |
+ sizes: '192x192', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
+ { | |
+ src: '/icons/icon-maskable-384x384.png', | |
+ sizes: '384x384', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
+ { | |
+ src: '/icons/icon-maskable-512x512.png', | |
+ sizes: '512x512', | |
+ type: 'image/png', | |
+ purpose: 'maskable', | |
+ }, | |
] | |
end | |
diff -ru truth-old/opensource/app/serializers/mobile/notification_serializer.rb truth-new/opensource/app/serializers/mobile/notification_serializer.rb | |
--- truth-old/opensource/app/serializers/mobile/notification_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/mobile/notification_serializer.rb 2024-04-12 09:09:08 | |
@@ -1,11 +1,14 @@ | |
# frozen_string_literal: true | |
-class Mobile::NotificationSerializer < ActiveModel::Serializer | |
+class Mobile::NotificationSerializer < NotificationSerializer | |
include RoutingHelper | |
include ActionView::Helpers::TextHelper | |
include ActionView::Helpers::SanitizeHelper | |
attributes :token, :category, :platform, :message, :extend | |
+ attribute :title, if: :chat? | |
+ attribute :mutable_content | |
+ attribute :thread_id, if: :chat? | |
def token | |
[current_push_subscription.device_token] | |
@@ -23,29 +26,52 @@ | |
object_type | |
end | |
+ def chat? | |
+ object.type == :chat | |
+ end | |
+ | |
delegate :platform, to: :current_push_subscription | |
def message | |
- params = {name: object.from_account.display_name.presence || object.from_account.username} | |
- if object.count.to_i > 1 | |
- template = "#{object_type}_group" | |
- params[:count_others] = object.count - 1 | |
- params[:actor] = "other" | |
- params[:actor] += "s" if object.count.to_i > 2 | |
- else | |
- template = object_type | |
- end | |
- I18n.t("notification_mailer.#{template}.subject", params) | |
+ chat? ? chat_message : I18n.t("notification_mailer.#{template}.#{notification_mailer_subject}", template_params) | |
end | |
+ def title | |
+ "@#{object.from_account.username}" | |
+ end | |
+ | |
+ def title_with_display_name | |
+ object.from_account.display_name.presence || "@#{object.from_account.username}" | |
+ end | |
+ | |
+ def mutable_content | |
+ true | |
+ end | |
+ | |
+ def thread_id | |
+ chat_message_object['id'] | |
+ end | |
+ | |
def extend | |
url = notification_url(object.type) | |
- if (url.nil? || url.empty?) | |
+ if url.nil? || url.empty? | |
Rails.logger.info("Empty mobile push notification status detected. Object ID: #{object.id}. Object Type: #{object.type}") | |
end | |
- [{"key" => "truthLink", "val" => url}] | |
+ payload = [] | |
+ payload.push({ 'key' => 'truthLink', 'val' => url }) | |
+ payload.push({ 'key' => 'title', 'val' => chat? ? title_with_display_name : 'Truth Social' }) unless android? | |
+ payload.push({ 'key' => 'accountId', 'val' => object.account_id.to_s }) | |
+ payload.push({ 'key' => 'chat', 'val' => extended_chat_fields }) if extended_chat_fields.present? | |
+ payload.push({ 'key' => 'category', 'val' => object_type }) | |
+ | |
+ if android? | |
+ payload.push({ 'key' => 'fromAccountId', 'val' => object.from_account_id.to_s }) | |
+ payload.push({ 'key' => 'title', 'val' => android_title }) | |
+ end | |
+ | |
+ payload | |
end | |
def body | |
@@ -53,18 +79,101 @@ | |
truncate(HTMLEntities.new.decode(str.to_str), length: 140) # Do not encode entities, since this value will not be used in HTML | |
end | |
+ def extended_chat_fields | |
+ return unless chat? | |
+ | |
+ attachments = chat_message_attachments ? { 'media_attachments': chat_message_attachments } : {} | |
+ if android? | |
+ { | |
+ 'title': title_with_display_name, | |
+ 'chat_message_id': chat_message_object['id'], | |
+ 'chat_message_created_at': chat_message_object['created_at'], | |
+ 'from_account_id': chat_message_object['account_id'], | |
+ **attachments, | |
+ } | |
+ else | |
+ attachments | |
+ end | |
+ end | |
+ | |
+ def chat_message_object | |
+ message = ChatMessage.find_message(object.account_id, object.activity.chat_id, object.activity_id) | |
+ ActiveSupport::JSON.decode(message) if message | |
+ end | |
+ | |
+ def chat_message | |
+ chat_message_object['content'] ? strip_tags(chat_message_object['content']) : I18n.t("notification_mailer.chat.sent_message") | |
+ end | |
+ | |
+ def chat_message_attachments | |
+ chat_message_object['media_attachments'] | |
+ .pluck('id', 'type', 'preview_url') | |
+ .map { |p| { id: p[0], type: p[1], preview_url: p[2] } } if chat_message_object['media_attachments'] | |
+ end | |
+ | |
private | |
+ | |
def notification_url(type) | |
- if %i[reblog reblog_group mention mention_group favourite favourite_group].include? type | |
+ if %i(reblog | |
+ reblog_group | |
+ mention | |
+ mention_group | |
+ favourite | |
+ favourite_group | |
+ ).include? type | |
object.target_status.uri | |
- elsif %i[follow follow_group].include? type | |
+ elsif %i( | |
+ follow | |
+ follow_group).include? type | |
ActivityPub::TagManager.instance.url_for(object.from_account) | |
+ elsif %i( | |
+ group_request | |
+ group_approval | |
+ group_promoted | |
+ group_demoted | |
+ group_delete).include? type | |
+ ActivityPub::TagManager.instance.url_for(object.target_group) | |
+ elsif %i( | |
+ group_favourite | |
+ group_favourite_group | |
+ group_mention | |
+ group_mention_group | |
+ group_reblog | |
+ group_reblog_group).include? type | |
+ ActivityPub::TagManager.instance.url_for(object.target_status) | |
+ elsif type == :chat | |
+ ActivityPub::TagManager.instance.url_for_chat_message(object.activity_id) | |
else | |
- "" | |
+ '' | |
end | |
end | |
def object_type | |
object.type.to_s.gsub '_group', '' | |
+ end | |
+ | |
+ def android? | |
+ platform == 2 | |
+ end | |
+ | |
+ def notification_mailer_subject | |
+ android? ? 'subject_android' : 'subject' | |
+ end | |
+ | |
+ def template_params | |
+ @template_params ||= mailer_params | |
+ end | |
+ | |
+ def android_title | |
+ handle = "@#{object.from_account.username}" | |
+ if %i(group_request | |
+ group_approval | |
+ group_promoted | |
+ group_demoted | |
+ group_delete).include? object.type | |
+ template_params[:group] || handle | |
+ else | |
+ handle | |
+ end | |
end | |
end | |
Only in truth-new/opensource/app/serializers: notification_serializer.rb | |
diff -ru truth-old/opensource/app/serializers/oembed_serializer.rb truth-new/opensource/app/serializers/oembed_serializer.rb | |
--- truth-old/opensource/app/serializers/oembed_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/oembed_serializer.rb 2024-04-01 14:59:13 | |
@@ -39,7 +39,7 @@ | |
def html | |
attributes = { | |
src: embed_short_account_status_url(object.account, object), | |
- class: 'mastodon-embed', | |
+ class: "truthsocial-embed#{' truthsocial-video' if has_video}", | |
style: 'max-width: 100%; border: 0', | |
width: width, | |
height: height, | |
@@ -55,5 +55,9 @@ | |
def height | |
instance_options[:height] | |
+ end | |
+ | |
+ def has_video | |
+ instance_options[:has_video] | |
end | |
end | |
diff -ru truth-old/opensource/app/serializers/rest/account_serializer.rb truth-new/opensource/app/serializers/rest/account_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/account_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/account_serializer.rb 2024-04-05 09:07:34 | |
@@ -5,7 +5,8 @@ | |
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at, | |
:note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, | |
- :following_count, :statuses_count, :last_status_at, :verified, :location, :website | |
+ :following_count, :statuses_count, :last_status_at, :verified, :location, :website, :accepting_messages, :chats_onboarded, | |
+ :feeds_onboarded, :tv_onboarded, :show_nonmember_group_statuses, :pleroma, :tv_account, :receive_only_follow_mentions | |
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? | |
@@ -29,10 +30,10 @@ | |
delegate :verified?, to: :object | |
- delegate :location, to: :object | |
+ def website | |
+ object.suspended? ? '' : object.website | |
+ end | |
- delegate :website, to: :object | |
- | |
def acct | |
object.pretty_acct | |
end | |
@@ -42,7 +43,7 @@ | |
end | |
def url | |
- ActivityPub::TagManager.instance.url_for(object) | |
+ object.suspended? ? '' : ActivityPub::TagManager.instance.url_for(object) | |
end | |
def avatar | |
@@ -54,15 +55,23 @@ | |
end | |
def header | |
- object&.header_file_name ? full_asset_url(object.suspended? ? object.header.default_url : object.header_original_url) : '' | |
+ if object&.header_file_name | |
+ full_asset_url(object.suspended? ? object.header.default_url : object.header_original_url) | |
+ else | |
+ '' | |
+ end | |
end | |
def header_static | |
- object&.header_file_name ? full_asset_url(object.suspended? ? object.header.default_url : object.header_static_url) : '' | |
+ if object&.header_file_name | |
+ full_asset_url(object.suspended? ? object.header.default_url : object.header_static_url) | |
+ else | |
+ '' | |
+ end | |
end | |
def created_at | |
- object.created_at.midnight.as_json | |
+ object.created_at.as_json | |
end | |
def last_status_at | |
@@ -101,9 +110,27 @@ | |
object.suspended? | |
end | |
+ def pleroma | |
+ { | |
+ accepts_chat_messages: object.accepting_messages, | |
+ } | |
+ end | |
+ | |
+ def location | |
+ object.suspended? ? '' : object.location | |
+ end | |
+ | |
delegate :suspended?, to: :object | |
def moved_and_not_nested? | |
object.moved? && object.moved_to_account.moved_to_account_id.nil? | |
+ end | |
+ | |
+ def tv_account | |
+ instance_options[:tv_account_lookup] && instance_options[:tv_account_lookup] == true ? !!object.tv_channel_account : false | |
+ end | |
+ | |
+ def chats_onboarded | |
+ true | |
end | |
end | |
Only in truth-new/opensource/app/serializers/rest: ad_metric_serializer.rb | |
diff -ru truth-old/opensource/app/serializers/rest/admin/account_serializer.rb truth-new/opensource/app/serializers/rest/admin/account_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/admin/account_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/admin/account_serializer.rb 2024-04-01 14:59:13 | |
@@ -1,10 +1,10 @@ | |
# frozen_string_literal: true | |
class REST::Admin::AccountSerializer < ActiveModel::Serializer | |
- attributes :id, :username, :domain, :created_at, | |
+ attributes :id, :username, :domain, :created_at, :deleted, | |
:email, :ip, :role, :confirmed, :suspended, | |
:silenced, :disabled, :approved, :locale, | |
- :invite_request, :verified, :location, :website | |
+ :invite_request, :verified, :location, :website, :sms, :sms_reverification_required, :updated_at, :advertiser | |
attribute :created_by_application_id, if: :created_by_application? | |
attribute :invited_by_account_id, if: :invited? | |
@@ -21,10 +21,18 @@ | |
delegate :website, to: :object | |
+ def deleted | |
+ object.deleted? | |
+ end | |
+ | |
def email | |
object.user_email | |
end | |
+ def sms | |
+ object.user_sms | |
+ end | |
+ | |
def ip | |
object.user_current_sign_in_ip.to_s.presence | |
end | |
@@ -79,5 +87,17 @@ | |
def created_by_application? | |
object.user&.created_by_application_id&.present? | |
+ end | |
+ | |
+ def sms_reverification_required | |
+ !!object.user&.user_sms_reverification_required&.user_id | |
+ end | |
+ | |
+ def updated_at | |
+ object.updated_at | |
+ end | |
+ | |
+ def advertiser | |
+ !!object.recent_ads.presence | |
end | |
end | |
Only in truth-new/opensource/app/serializers/rest/admin: chat_message_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest/admin: one_time_challenge_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest/admin: tag_search_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest/admin: tag_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest/admin: webauthn_credential_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: avatars_carousel_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: chat_member_removal_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: chat_message_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: chat_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: chat_silence_serializer.rb | |
diff -ru truth-old/opensource/app/serializers/rest/credential_account_serializer.rb truth-new/opensource/app/serializers/rest/credential_account_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/credential_account_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/credential_account_serializer.rb 2024-04-05 09:09:43 | |
@@ -1,11 +1,12 @@ | |
# frozen_string_literal: true | |
class REST::CredentialAccountSerializer < REST::AccountSerializer | |
- attributes :source, :pleroma | |
+ attributes :source, :pleroma, :features | |
+ SMS_REVERIFICATION_DEADLINE = 90 | |
def source | |
user = object.user | |
- waitlist_enabled = ENV.fetch("WAITLIST_ENABLED", "true") | |
+ waitlist_enabled = ENV.fetch('WAITLIST_ENABLED', 'true') | |
source = { | |
privacy: user.setting_default_privacy, | |
@@ -18,17 +19,45 @@ | |
sms_verified: (user.not_ready_for_approval? || user.ready_by_csv_import? || user.sms_verified?), | |
ready_by_sms_verification: (!user.not_ready_for_approval? && !user.ready_by_csv_import?), | |
follow_requests_count: FollowRequest.where(target_account: object).limit(40).count, | |
+ accepting_messages: object.accepting_messages, | |
+ chats_onboarded: true, | |
+ feeds_onboarded: object.feeds_onboarded, | |
+ tv_onboarded: object.tv_onboarded, | |
+ show_nonmember_group_statuses: object.show_nonmember_group_statuses, | |
+ unauth_visibility: !!user.unauth_visibility, | |
+ integrity: user.integrity_score, | |
+ integrity_status: user.integrity_status(instance_options[:access_token], instance_options[:android_client]), | |
+ sms_reverification_required: !!user.user_sms_reverification_required&.user_id, | |
+ sms: user.sms.present?, | |
+ sms_country: user.sms_country, | |
+ receive_only_follow_mentions: object.receive_only_follow_mentions | |
} | |
- source[:unapproved_position] = user.get_position_in_waitlist_queue if waitlist_enabled == "true" | |
- return source | |
+ source[:unapproved_position] = user.get_position_in_waitlist_queue if waitlist_enabled == 'true' | |
+ source[:sms_last_four_digits] = user.sms.last(4) if user.sms.present? | |
+ source[:sms_reverification_days_left] = sms_reverification_days_left(user) if user.user_sms_reverification_required&.user_id | |
+ source | |
end | |
+ def sms_reverification_days_left(user) | |
+ action_date = Admin::ActionLog.select(:created_at).where(target_type: 'User', target_id: user.id, action: 'enable_sms_reverification').order('created_at DESC').first&.created_at | |
+ return SMS_REVERIFICATION_DEADLINE unless action_date | |
+ [SMS_REVERIFICATION_DEADLINE - ((Time.now - action_date) / 1.day).round, 0].max | |
+ end | |
+ | |
def pleroma | |
{ | |
+ accepts_chat_messages: object.accepting_messages, | |
settings_store: object.settings_store, | |
- is_admin: object.user.admin, | |
- is_moderator: object.user.moderator | |
} | |
+ end | |
+ | |
+ def features | |
+ enabled_features = object.feature_flags.pluck(:name) | |
+ | |
+ ::Configuration::FeatureFlag.all.each_with_object({}) do |feature, hash| | |
+ name = feature.name | |
+ hash[name] = feature.enabled? || feature.account_based? && enabled_features.include?(name) | |
+ end | |
end | |
end | |
Only in truth-new/opensource/app/serializers/rest: feed_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: group_membership_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: group_relationship_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: group_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: group_suggestion_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: groups_avatar_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: groups_carousel_serializer.rb | |
diff -ru truth-old/opensource/app/serializers/rest/instance_serializer.rb truth-new/opensource/app/serializers/rest/instance_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/instance_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/instance_serializer.rb 2024-04-01 14:59:13 | |
@@ -6,12 +6,8 @@ | |
attributes :uri, :title, :short_description, :description, :email, | |
:version, :urls, :thumbnail, :languages, :registrations, | |
:approval_required, :invites_enabled, :configuration, | |
- :feature_quote | |
+ :feature_quote, :rules | |
- has_many :rules, serializer: REST::RuleSerializer | |
- | |
- delegate :rules, to: :instance_presenter | |
- | |
def uri | |
Rails.configuration.x.local_domain | |
end | |
@@ -33,7 +29,8 @@ | |
end | |
def version | |
- "#{Mastodon::Version} (compatible; TruthSocial 1.0.0)" | |
+ is_staging = ActiveModel::Type::Boolean.new.cast(ENV['IS_STAGING']) | |
+ "#{Mastodon::Version} (compatible; TruthSocial 1.0.0#{is_staging ? '+unreleased' : ''})" | |
end | |
def thumbnail | |
@@ -45,20 +42,29 @@ | |
end | |
def configuration | |
+ ads_configuration = JSON.parse(ENV.fetch('ADS_CONFIGURATION', '[{}]')) | |
+ | |
{ | |
statuses: { | |
max_characters: StatusLengthValidator::MAX_CHARS, | |
max_media_attachments: 4, | |
- characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS, | |
+ characters_reserved_per_url: URLPlaceholder::LENGTH, | |
}, | |
+ chats: { | |
+ max_characters: ChatMessage::MAX_CHARS, | |
+ max_messages_per_minute: ChatMessage::MAX_MESSAGES_PER_MIN, | |
+ max_media_attachments: ENV.fetch('MAX_ATTACHMENTS_ALLOWED_PER_MESSAGE', 4).to_i, | |
+ }, | |
+ | |
media_attachments: { | |
- supported_mime_types: MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES, | |
+ supported_mime_types: MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES, | |
image_size_limit: MediaAttachment::IMAGE_LIMIT, | |
image_matrix_limit: Attachmentable::MAX_MATRIX_LIMIT, | |
video_size_limit: MediaAttachment::VIDEO_LIMIT, | |
video_frame_rate_limit: MediaAttachment::MAX_VIDEO_FRAME_RATE, | |
video_matrix_limit: MediaAttachment::MAX_VIDEO_MATRIX_LIMIT, | |
+ video_duration_limit: MediaAttachment::MAX_VIDEO_DURATION_LIMIT, | |
}, | |
polls: { | |
@@ -67,6 +73,22 @@ | |
min_expiration: PollValidator::MIN_EXPIRATION, | |
max_expiration: PollValidator::MAX_EXPIRATION, | |
}, | |
+ | |
+ ads: { | |
+ algorithm: { | |
+ name: ads_configuration[0]&.[]('value'), | |
+ configuration: { | |
+ frequency: ads_configuration[1]&.[]('value').to_i, | |
+ phase_min: ads_configuration[2]&.[]('value').to_f, | |
+ phase_max: ads_configuration[3]&.[]('value').to_f, | |
+ }, | |
+ }, | |
+ }, | |
+ groups: { | |
+ max_characters_name: ENV.fetch('MAX_GROUP_NAME_CHARS', 35).to_i, | |
+ max_characters_description: ENV.fetch('MAX_GROUP_NOTE_CHARS', 160).to_i, | |
+ max_admins_allowed: ENV.fetch('MAX_GROUP_ADMINS_ALLOWED', 10).to_i, | |
+ }, | |
} | |
end | |
@@ -87,7 +109,11 @@ | |
end | |
def feature_quote | |
- false | |
+ true | |
+ end | |
+ | |
+ def rules | |
+ ActiveModelSerializers::SerializableResource.new(Rule.ordered, each_serializer: REST::RuleSerializer).as_json | |
end | |
private | |
diff -ru truth-old/opensource/app/serializers/rest/media_attachment_serializer.rb truth-new/opensource/app/serializers/rest/media_attachment_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/media_attachment_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/media_attachment_serializer.rb 2024-04-01 14:59:13 | |
@@ -5,14 +5,14 @@ | |
attributes :id, :type, :url, :preview_url, :external_video_id, | |
:remote_url, :preview_remote_url, :text_url, :meta, | |
- :description, :blurhash | |
+ :description, :blurhash, :tv | |
def id | |
object.id.to_s | |
end | |
def url | |
- if object.not_processed? | |
+ if object.type != 'video' && object.not_processed? | |
nil | |
elsif object.needs_redownload? | |
media_proxy_url(object.id, :original) | |
@@ -26,8 +26,8 @@ | |
end | |
def preview_url | |
- if object.type == "video" | |
- #TODO: replace the image and upload it to CDN | |
+ if object.type == 'video' | |
+ # TODO: replace the image and upload it to CDN | |
object.external_video_id && object.status.preview_card&.image? ? full_asset_url(object.status.preview_card.image.url(:original)) : full_asset_url('/icons/missing.png') | |
elsif object.needs_redownload? | |
media_proxy_url(object.id, :small) | |
@@ -48,5 +48,14 @@ | |
def meta | |
object.file.meta | |
+ end | |
+ | |
+ def external_video_id | |
+ return nil if object.not_processed? | |
+ object.external_video_id | |
+ end | |
+ | |
+ def tv | |
+ REST::TvProgramSerializer.new(instance_options[:tv_program]) if instance_options && instance_options[:tv_program] | |
end | |
end | |
diff -ru truth-old/opensource/app/serializers/rest/notification_serializer.rb truth-new/opensource/app/serializers/rest/notification_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/notification_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/notification_serializer.rb 2024-04-01 14:59:13 | |
@@ -3,9 +3,10 @@ | |
class REST::NotificationSerializer < ActiveModel::Serializer | |
attributes :id, :type, :total_count, :created_at | |
attribute :total_count, if: -> { object.count.present? } | |
+ attribute :target_chat_message, if: -> { object.type == :chat || object.type == :chat_message_deleted } | |
+ attribute :target_status, key: :status, if: :status_type? | |
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer | |
- belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer | |
def id | |
object.id.to_s | |
@@ -19,7 +20,26 @@ | |
object.count | |
end | |
+ def target_status | |
+ REST::V2::StatusSerializer.new(context: { current_user: instance_options[:current_user] }).serialize(object.target_status) if object.target_status | |
+ end | |
+ | |
def status_type? | |
- [:favourite, :favourite_group, :reblog, :reblog_group, :status, :mention, :mention_group, :poll].include?(object.type) | |
+ [:favourite, :favourite_group, :group_favourite, :group_favourite_group, | |
+ :reblog, :reblog_group, :group_reblog, :group_reblog_group, | |
+ :status, | |
+ :mention, :mention_group, :group_mention, :group_mention_group, | |
+ :poll].include?(object.type) | |
+ end | |
+ | |
+ def target_chat_message | |
+ chat_message = ChatMessage.find_message(object.account_id, object.activity.chat_id, object.activity_id) | |
+ | |
+ if chat_message | |
+ decoded = ActiveSupport::JSON.decode(chat_message) | |
+ chat_message_obj = ChatMessage.new(decoded) | |
+ | |
+ ActiveModelSerializers::SerializableResource.new(chat_message_obj, serializer: REST::ChatMessageSerializer).as_json | |
+ end | |
end | |
end | |
Only in truth-new/opensource/app/serializers/rest: oauth_token_serializer.rb | |
diff -ru truth-old/opensource/app/serializers/rest/preview_card_serializer.rb truth-new/opensource/app/serializers/rest/preview_card_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/preview_card_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/preview_card_serializer.rb 2024-04-01 14:59:13 | |
@@ -8,7 +8,37 @@ | |
:provider_url, :html, :width, :height, | |
:image, :embed_url, :blurhash | |
+ attribute :group, if: -> { instance_options && instance_options[:group] } | |
+ | |
def image | |
object.image? ? full_asset_url(object.image.url(:original)) : nil | |
+ end | |
+ | |
+ def url | |
+ url = object.url | |
+ | |
+ if instance_options && instance_options[:external_links] | |
+ links = instance_options[:external_links].index_by(&:url) | |
+ if (link_id = links[object&.url]&.id) | |
+ url = link_url(link_id, subdomain: 'links') | |
+ end | |
+ end | |
+ url | |
+ end | |
+ | |
+ def provider_name | |
+ url = object.provider_name | |
+ if url.blank? | |
+ url = Addressable::URI.parse(object.url)&.host || '' | |
+ end | |
+ url | |
+ end | |
+ | |
+ def group | |
+ REST::GroupSerializer.new(instance_options[:group]) | |
+ end | |
+ | |
+ def html | |
+ Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED) | |
end | |
end | |
Only in truth-new/opensource/app/serializers/rest: revcontent_ad_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: revcontent_ads_serializer.rb | |
diff -ru truth-old/opensource/app/serializers/rest/rule_serializer.rb truth-new/opensource/app/serializers/rest/rule_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/rule_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/rule_serializer.rb 2024-04-01 14:59:13 | |
@@ -6,4 +6,16 @@ | |
def id | |
object.id.to_s | |
end | |
+ | |
+ def rule_type | |
+ object.rule_type == "rule_type_group" ? :group : object.rule_type | |
+ end | |
+ | |
+ def text | |
+ I18n.t("admin.instances.rules.#{object.name}.text") | |
+ end | |
+ | |
+ def subtext | |
+ I18n.t("admin.instances.rules.#{object.name}.subtext") | |
+ end | |
end | |
diff -ru truth-old/opensource/app/serializers/rest/search_serializer.rb truth-new/opensource/app/serializers/rest/search_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/search_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/search_serializer.rb 2024-04-12 09:09:08 | |
@@ -1,7 +1,14 @@ | |
# frozen_string_literal: true | |
class REST::SearchSerializer < ActiveModel::Serializer | |
- has_many :accounts, serializer: REST::AccountSerializer | |
+ attributes :hashtags, :accounts | |
+ | |
has_many :statuses, serializer: REST::StatusSerializer | |
- has_many :hashtags, serializer: REST::TagSerializer | |
+ delegate :hashtags, to: :object | |
+ has_many :groups, serializer: REST::GroupSerializer | |
+ | |
+ def accounts | |
+ ActiveModel::SerializableResource.new(object.accounts, each_serializer: REST::AccountSerializer, tv_account_lookup: true) | |
+ end | |
+ | |
end | |
diff -ru truth-old/opensource/app/serializers/rest/status_serializer.rb truth-new/opensource/app/serializers/rest/status_serializer.rb | |
--- truth-old/opensource/app/serializers/rest/status_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/rest/status_serializer.rb 2024-04-01 14:59:13 | |
@@ -3,11 +3,12 @@ | |
class REST::StatusSerializer < ActiveModel::Serializer | |
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, | |
:sensitive, :spoiler_text, :visibility, :language, | |
- :uri, :url | |
+ :uri, :url, :sponsored, :tombstone, :tv | |
attribute :replies_count | |
attribute :reblogs_count | |
attribute :favourites_count | |
+ attribute :group_timeline_visible | |
attribute :favourited, if: :current_user? | |
attribute :reblogged, if: :current_user? | |
attribute :muted, if: :current_user? | |
@@ -18,17 +19,19 @@ | |
attribute :text, if: :source_requested? | |
attribute :quote_id, if: -> { object.quote? } | |
+ attribute :metrics, if: -> { object.ad.present? } | |
+ attribute :preview_card, key: :card | |
+ attribute :media_attachments | |
belongs_to :reblog, serializer: REST::StatusSerializer | |
belongs_to :application, if: :show_application? | |
belongs_to :account, serializer: REST::AccountSerializer | |
+ belongs_to :group, serializer: REST::GroupSerializer | |
- has_many :media_attachments, serializer: REST::MediaAttachmentSerializer | |
has_many :ordered_mentions, key: :mentions | |
has_many :tags | |
has_many :emojis, serializer: REST::CustomEmojiSerializer | |
- has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer | |
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer | |
def id | |
@@ -47,6 +50,20 @@ | |
object.quote_id.to_s | |
end | |
+ def metrics | |
+ REST::AdMetricSerializer.new.serialize(object.ad) | |
+ end | |
+ | |
+ def preview_card | |
+ group = if instance_options && instance_options[:relationships] | |
+ instance_options[:relationships].groups_map[object.id] || false | |
+ else | |
+ Status.groups_map([object])[object.id] || false | |
+ end | |
+ | |
+ REST::PreviewCardSerializer.new(object.preview_card, external_links: object.links, group: group) if object.preview_card | |
+ end | |
+ | |
def current_user? | |
if defined?(current_user) | |
!current_user.nil? | |
@@ -81,7 +98,7 @@ | |
end | |
def content | |
- Formatter.instance.format(object) | |
+ Formatter.instance.format(object, { external_links: object.links }) | |
end | |
def url | |
@@ -159,6 +176,24 @@ | |
object.active_mentions.to_a.sort_by(&:id) | |
end | |
+ def sponsored | |
+ !!object.ad | |
+ end | |
+ | |
+ def tombstone | |
+ nil | |
+ end | |
+ | |
+ def media_attachments | |
+ object.media_attachments.map do |attachment| | |
+ REST::MediaAttachmentSerializer.new(attachment, tv_program: object.tv_program) | |
+ end | |
+ end | |
+ | |
+ def tv | |
+ REST::TvProgramSerializer.new(object.tv_program) if object.tv_program | |
+ end | |
+ | |
class ApplicationSerializer < ActiveModel::Serializer | |
attributes :name, :website | |
end | |
@@ -174,6 +209,11 @@ | |
object.account_username | |
end | |
+ def group_timeline_visible | |
+ object.group ? object.group_timeline_visible : true | |
+ end | |
+ | |
+ | |
def url | |
ActivityPub::TagManager.instance.url_for(object.account) | |
end | |
@@ -204,7 +244,7 @@ | |
if instance_options && instance_options[:account_relationships] | |
instance_options[:account_relationships].muting[object.account_id] ? true : false || instance_options[:account_relationships].blocking[object.account_id] || instance_options[:account_relationships].blocked_by[object.account_id] || instance_options[:account_relationships].domain_blocking[object.account_id] || false | |
else | |
- current_user.account.muting?(object.account) || object.account.blocking?(current_user.account) || current_user.account.blocking?(object.account) || current_user.account.domain_blocking?(object.account.domain) | |
+ current_user.account.muting?(object.account) || object.account.blocking?(current_user.account) || current_user.account.blocking?(object.account) || current_user.account.domain_blocking?(object.account.domain) | |
end | |
end | |
end | |
@@ -228,6 +268,6 @@ | |
end | |
class REST::StatusSerializer < ActiveModel::Serializer | |
- belongs_to :quote, serializer: REST::NestedQuoteSerializer | |
- belongs_to :thread, serializer: REST::InReplySerializer, key: :in_reply_to, if: -> { !@instance_options[:exclude_reply_previews] } | |
+ belongs_to :quote, serializer: REST::NestedQuoteSerializer, if: -> { (object&.quote&.visibility != 'self' || (current_user? && current_user.account_id == object&.quote&.account_id)) } | |
+ belongs_to :thread, serializer: REST::InReplySerializer, key: :in_reply_to, if: -> { !@instance_options[:exclude_reply_previews] && (object&.thread&.visibility != 'self' || (current_user? && current_user.account_id == object&.thread&.account_id)) } | |
end | |
Only in truth-new/opensource/app/serializers/rest: suggestions_carousel_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: truth | |
Only in truth-new/opensource/app/serializers/rest: tv_program_serializer.rb | |
Only in truth-new/opensource/app/serializers/rest: v2 | |
diff -ru truth-old/opensource/app/serializers/web/notification_serializer.rb truth-new/opensource/app/serializers/web/notification_serializer.rb | |
--- truth-old/opensource/app/serializers/web/notification_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/web/notification_serializer.rb 2023-05-05 13:42:02 | |
@@ -1,6 +1,6 @@ | |
# frozen_string_literal: true | |
-class Web::NotificationSerializer < ActiveModel::Serializer | |
+class Web::NotificationSerializer < NotificationSerializer | |
include RoutingHelper | |
include ActionView::Helpers::TextHelper | |
include ActionView::Helpers::SanitizeHelper | |
@@ -29,7 +29,7 @@ | |
end | |
def title | |
- I18n.t("notification_mailer.#{object.type}.subject", name: object.from_account.display_name.presence || object.from_account.username) | |
+ I18n.t("notification_mailer.#{template}.subject", mailer_params) | |
end | |
def body | |
diff -ru truth-old/opensource/app/serializers/webfinger_serializer.rb truth-new/opensource/app/serializers/webfinger_serializer.rb | |
--- truth-old/opensource/app/serializers/webfinger_serializer.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/serializers/webfinger_serializer.rb 2024-04-01 14:59:13 | |
@@ -10,25 +10,14 @@ | |
end | |
def aliases | |
- if object.instance_actor? | |
- [instance_actor_url] | |
- else | |
- [short_account_url(object), account_url(object)] | |
- end | |
+ [short_account_url(object), account_url(object)] | |
end | |
def links | |
- if object.instance_actor? | |
- [ | |
- { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: about_more_url(instance_actor: true) }, | |
- { rel: 'self', type: 'application/activity+json', href: instance_actor_url }, | |
- ] | |
- else | |
- [ | |
- { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) }, | |
- { rel: 'self', type: 'application/activity+json', href: account_url(object) }, | |
- { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, | |
- ] | |
- end | |
+ [ | |
+ { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) }, | |
+ { rel: 'self', type: 'application/activity+json', href: account_url(object) }, | |
+ # { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" }, | |
+ ] | |
end | |
end | |
diff -ru truth-old/opensource/app/services/account_search_service.rb truth-new/opensource/app/services/account_search_service.rb | |
--- truth-old/opensource/app/services/account_search_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/account_search_service.rb 2024-04-01 14:59:13 | |
@@ -17,7 +17,8 @@ | |
private | |
def search_service_results | |
- return [] if query.blank? || limit < 1 | |
+ return [] if limit < 1 | |
+ return [] if query.blank? && !options[:followers] | |
[exact_match] + search_results | |
end | |
@@ -30,6 +31,7 @@ | |
match = Account.find_local(query) | |
match = nil if !match.nil? && !account.nil? && options[:following] && !account.following?(match) | |
+ match = nil if options[:followers] && !match&.following?(account) | |
@exact_match = match | |
end | |
@@ -38,7 +40,7 @@ | |
return [] if limit_for_non_exact_results.zero? | |
@search_results ||= begin | |
- results = from_elasticsearch if Chewy.enabled? | |
+ results = from_elasticsearch if Chewy.enabled? && !options[:followers] | |
results ||= from_database | |
results | |
end | |
@@ -57,14 +59,20 @@ | |
end | |
def advanced_search_results | |
- Account.advanced_search_for(query, account, limit_for_non_exact_results, options[:following], offset) | |
+ Account.advanced_search_for(query, account, limit, options[:following], offset) | |
end | |
def follower_search | |
- account.followers.where('LOWER(username) LIKE ?', '%' + query.downcase + '%').limit(20) | |
+ account | |
+ .followers_unordered | |
+ .where('LOWER(username) LIKE :search OR LOWER(display_name) LIKE :search', search: "%#{sanitize_search(query&.downcase)}%") | |
+ .where(accepting_messages: true) | |
+ .limit(limit) | |
+ .offset(offset) | |
+ .order(username: :asc) | |
end | |
- def fields | |
+ def fields | |
if likely_username? | |
%w(acct.edge_ngram acct) | |
elsif likely_display_name? | |
@@ -89,14 +97,23 @@ | |
functions = [reputation_score_function, followers_score_function, time_distance_function] | |
- records = AccountsIndex.query(function_score: { query: fields_query, functions: functions, boost_mode: 'multiply', score_mode: 'multiply' }) | |
+ records = AccountsIndex.query( | |
+ function_score: { | |
+ query: fields_query, | |
+ functions: functions, | |
+ boost_mode: 'multiply', | |
+ score_mode: 'multiply', | |
+ } | |
+ ) | |
.filter(SearchService::PROHIBITED_FILTERS) | |
+ .filter(term: { suspended: false }) | |
+ .filter(exists: { field: 'email' }) | |
.limit(limit_for_non_exact_results) | |
.offset(offset) | |
.objects | |
.compact | |
- ActiveRecord::Associations::Preloader.new.preload(records, :account_stat) | |
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status, :tv_channel_account, :moved_to_account]) | |
records | |
rescue Faraday::ConnectionFailed, Parslet::ParseFailed | |
@@ -168,4 +185,7 @@ | |
@acct_hint | |
end | |
+ def sanitize_search(query) | |
+ ActiveRecord::Base.sanitize_sql_like(query || '') | |
+ end | |
end | |
Only in truth-old/opensource/app/services/activitypub: prepare_followers_synchronization_service.rb | |
Only in truth-old/opensource/app/services/activitypub: process_collection_service.rb | |
diff -ru truth-old/opensource/app/services/activitypub/process_poll_service.rb truth-new/opensource/app/services/activitypub/process_poll_service.rb | |
--- truth-old/opensource/app/services/activitypub/process_poll_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/activitypub/process_poll_service.rb 2024-04-12 09:09:08 | |
@@ -8,7 +8,7 @@ | |
return unless expected_type? | |
- previous_expires_at = poll.expires_at | |
+ #previous_expires_at = poll.expires_at | |
expires_at = if @json['closed'].is_a?(String) | |
@json['closed'] | |
@@ -47,9 +47,9 @@ | |
# If the poll had no expiration date set but now has, and people have voted, | |
# schedule a notification. | |
- if previous_expires_at.nil? && poll.expires_at.present? && poll.votes.exists? | |
- PollExpirationNotifyWorker.perform_at(poll.expires_at + 5.minutes, poll.id) | |
- end | |
+ # if previous_expires_at.nil? && poll.expires_at.present? && poll.votes.exists? | |
+ # PollExpirationNotifyWorker.perform_at(poll.expires_at + 5.minutes, poll.id) | |
+ # end | |
end | |
private | |
Only in truth-new/opensource/app/services: admin_account_search_service.rb | |
Only in truth-new/opensource/app/services: ads_service.rb | |
diff -ru truth-old/opensource/app/services/after_unallow_domain_service.rb truth-new/opensource/app/services/after_unallow_domain_service.rb | |
--- truth-old/opensource/app/services/after_unallow_domain_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/after_unallow_domain_service.rb 2024-04-01 14:59:13 | |
@@ -3,7 +3,13 @@ | |
class AfterUnallowDomainService < BaseService | |
def call(domain) | |
Account.where(domain: domain).find_each do |account| | |
- DeleteAccountService.new.call(account, reserve_username: false) | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'service_unallowed_domain', | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ ) | |
end | |
end | |
end | |
Only in truth-new/opensource/app/services: android_device_check | |
diff -ru truth-old/opensource/app/services/app_sign_up_service.rb truth-new/opensource/app/services/app_sign_up_service.rb | |
--- truth-old/opensource/app/services/app_sign_up_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/app_sign_up_service.rb 2024-04-01 14:59:13 | |
@@ -11,7 +11,7 @@ | |
invite_request_params = { text: params[:reason] } | |
user = User.create!(user_params.merge(created_by_application: app, sign_up_ip: remote_ip, password_confirmation: user_params[:password], account_attributes: account_params, invite_request_attributes: invite_request_params)) | |
- Doorkeeper::AccessToken.create!(application: app, | |
+ OauthAccessToken.create!(application: app, | |
resource_owner_id: user.id, | |
scopes: app.scopes, | |
expires_in: Doorkeeper.configuration.access_token_expires_in, | |
Only in truth-new/opensource/app/services: assertion_service.rb | |
Only in truth-new/opensource/app/services: authorize_membership_service.rb | |
diff -ru truth-old/opensource/app/services/batched_remove_status_service.rb truth-new/opensource/app/services/batched_remove_status_service.rb | |
--- truth-old/opensource/app/services/batched_remove_status_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/batched_remove_status_service.rb 2023-05-05 13:42:02 | |
@@ -31,7 +31,7 @@ | |
# Since we skipped all callbacks, we also need to manually | |
# deindex the statuses | |
- Chewy.strategy.current.update(StatusesIndex::Status, statuses_and_reblogs) if Chewy.enabled? | |
+ Chewy.strategy.current.update(StatusesIndex, statuses_and_reblogs) if Chewy.enabled? | |
return if options[:skip_side_effects] | |
diff -ru truth-old/opensource/app/services/block_domain_service.rb truth-new/opensource/app/services/block_domain_service.rb | |
--- truth-old/opensource/app/services/block_domain_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/block_domain_service.rb 2024-04-01 14:59:13 | |
@@ -37,7 +37,14 @@ | |
blocked_domain_accounts.without_suspended.in_batches.update_all(suspended_at: @domain_block.created_at, suspension_origin: :local) | |
blocked_domain_accounts.where(suspended_at: @domain_block.created_at).reorder(nil).find_each do |account| | |
- DeleteAccountService.new.call(account, reserve_username: true, suspended_at: @domain_block.created_at) | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'service_block_domain', | |
+ reserve_username: true, | |
+ skip_activitypub: true, | |
+ suspended_at: @domain_block.created_at, | |
+ ) | |
end | |
end | |
diff -ru truth-old/opensource/app/services/block_service.rb truth-new/opensource/app/services/block_service.rb | |
--- truth-old/opensource/app/services/block_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/block_service.rb 2024-04-01 14:59:13 | |
@@ -6,12 +6,20 @@ | |
def call(account, target_account) | |
return if account.id == target_account.id | |
- UnfollowService.new.call(account, target_account) if account.following?(target_account) | |
- UnfollowService.new.call(target_account, account) if target_account.following?(account) | |
+ if account.following?(target_account) | |
+ UnfollowService.new.call(account, target_account) | |
+ FollowDelete.where(account_id: account.id).destroy_all | |
+ end | |
+ | |
+ if target_account.following?(account) | |
+ UnfollowService.new.call(target_account, account) | |
+ FollowDelete.where(target_account_id: target_account).destroy_all | |
+ end | |
+ | |
RejectFollowService.new.call(target_account, account) if target_account.requested?(account) | |
block = account.block!(target_account) | |
- | |
+ invalidate_secondary_caches(account, target_account) | |
BlockWorker.perform_async(account.id, target_account.id) | |
create_notification(block) if !target_account.local? && target_account.activitypub? | |
export_prometheus_metric | |
@@ -19,6 +27,11 @@ | |
end | |
private | |
+ | |
+ def invalidate_secondary_caches(account, target_account) | |
+ InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", account.id, target_account.id, target_account.whale?) | |
+ InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", target_account.id, account.id, account.whale?) | |
+ end | |
def create_notification(block) | |
ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url) | |
Only in truth-new/opensource/app/services: canonical_request_service.rb | |
Only in truth-new/opensource/app/services: chat_message_reaction_service.rb | |
Only in truth-new/opensource/app/services: chat_message_service.rb | |
Only in truth-new/opensource/app/services: chat_service.rb | |
Only in truth-new/opensource/app/services/concerns: app_attestable.rb | |
Only in truth-new/opensource/app/services/concerns: challengeable.rb | |
Only in truth-new/opensource/app/services/concerns: group_cachable.rb | |
Only in truth-new/opensource/app/services/concerns: links_parser_concern.rb | |
Only in truth-new/opensource/app/services/concerns: upload_video_concern.rb | |
diff -ru truth-old/opensource/app/services/delete_account_service.rb truth-new/opensource/app/services/delete_account_service.rb | |
--- truth-old/opensource/app/services/delete_account_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/delete_account_service.rb 2024-04-01 14:59:13 | |
@@ -1,8 +1,18 @@ | |
# frozen_string_literal: true | |
+class UnknownDeletionType < StandardError; end | |
+ | |
class DeleteAccountService < BaseService | |
include Payloadable | |
+ # This can be passed to #call as the deleted_by_id to indicate that | |
+ # this account was deleted by an automated process, rather than by | |
+ # a user or moderator. This is then passed to Janus. | |
+ # | |
+ # NOTE: The deletion_type option should be used to give a more specific | |
+ # details on how the account was deleted. | |
+ DELETED_BY_SERVICE = -99 | |
+ | |
ASSOCIATIONS_ON_SUSPEND = %w( | |
account_pins | |
active_relationships | |
@@ -72,9 +82,15 @@ | |
# @option [Boolean] :skip_side_effects Side effects are ActivityPub and streaming API payloads | |
# @option [Boolean] :skip_activitypub Skip sending ActivityPub payloads. Implied by :skip_side_effects | |
# @option [Time] :suspended_at Only applicable when :reserve_username is true | |
- def call(account, **options) | |
+ def call(account, deleted_by_id, **options) | |
@account = account | |
- @options = { reserve_username: true, reserve_email: true }.merge(options) | |
+ @deleted_by_id = deleted_by_id | |
+ defaults = { | |
+ deletion_type: 'unknown', | |
+ reserve_email: true, | |
+ reserve_username: true, | |
+ } | |
+ @options = defaults.merge(options) | |
if @account.local? && @account.user_unconfirmed_or_pending? | |
@options[:reserve_email] = false | |
@@ -87,10 +103,34 @@ | |
distribute_activities! | |
purge_content! | |
fulfill_deletion_request! | |
+ publish_delete_event | |
+ track_account_deletion | |
end | |
private | |
+ def track_account_deletion | |
+ Logs::AccountDeletion.create!( | |
+ account_id: @account.id, | |
+ user_id: @account&.user&.id, | |
+ username: @account.username, | |
+ email: @account&.user_email, | |
+ deleted_at: Time.now.utc, | |
+ account_deletion_type: @options[:deletion_type], | |
+ deleted_by_account_id: @deleted_by_id, | |
+ ) | |
+ if @options[:deletion_type] == 'unknown' | |
+ raise UnknownDeletionType | |
+ end | |
+ rescue UnknownDeletionType => e | |
+ Rails.logger.warn('Track account deletion: Unknown deletion type') | |
+ Rails.logger.warn(e.backtrace.join("\n")) | |
+ rescue ActiveRecord::NotNullViolation => e | |
+ Rails.logger.info("Failed to track account deletion: #{e.message}") | |
+ rescue ActiveRecord::RecordNotUnique => e | |
+ Rails.logger.info("Failed to track account deletion, account_id: #{@account.id}, #{e.message}") | |
+ end | |
+ | |
def distribute_activities! | |
return if skip_activitypub? | |
@@ -173,21 +213,22 @@ | |
end | |
def purge_polls! | |
- @account.polls.reorder(nil).where.not(status_id: reported_status_ids).in_batches.delete_all | |
+ @account.polls.reorder(nil).merge(Status.where.not(id: reported_status_ids)).in_batches.delete_all | |
end | |
def purge_generated_notifications! | |
# By deleting polls and statuses without callbacks, we've left behind | |
# polymorphically associated notifications generated by this account | |
- Notification.where(from_account: @account).in_batches.delete_all | |
+ Notification.where(from_account: @account).in_batches do |batch| | |
+ batch.delete_all | |
+ end | |
end | |
def purge_favourites! | |
@account.favourites.in_batches do |favourites| | |
ids = favourites.pluck(:status_id) | |
- StatusStat.where(status_id: ids).update_all('favourites_count = GREATEST(0, favourites_count - 1)') | |
- Chewy.strategy.current.update(StatusesIndex::Status, ids) if Chewy.enabled? | |
+ Chewy.strategy.current.update(StatusesIndex, ids) if Chewy.enabled? | |
Rails.cache.delete_multi(ids.map { |id| "statuses/#{id}" }) | |
favourites.delete_all | |
end | |
@@ -195,7 +236,7 @@ | |
def purge_bookmarks! | |
@account.bookmarks.in_batches do |bookmarks| | |
- Chewy.strategy.current.update(StatusesIndex::Status, bookmarks.pluck(:status_id)) if Chewy.enabled? | |
+ Chewy.strategy.current.update(StatusesIndex, bookmarks.pluck(:status_id)) if Chewy.enabled? | |
bookmarks.delete_all | |
end | |
end | |
@@ -229,9 +270,6 @@ | |
@account.display_name = '' | |
@account.note = '' | |
@account.fields = [] | |
- @account.statuses_count = 0 | |
- @account.followers_count = 0 | |
- @account.following_count = 0 | |
@account.moved_to_account = nil | |
@account.also_known_as = [] | |
@account.trust_level = :untrusted | |
@@ -302,5 +340,9 @@ | |
def skip_activitypub? | |
@options[:skip_activitypub] | |
+ end | |
+ | |
+ def publish_delete_event | |
+ EventProvider::EventProvider.new('account.deleted', AccountDeletedEvent, {account_id: @account.id, deleted_by_id: @deleted_by_id}).call | |
end | |
end | |
Only in truth-new/opensource/app/services: delete_group_service.rb | |
Only in truth-new/opensource/app/services: destroy_group_service.rb | |
Only in truth-new/opensource/app/services: disabled_user_refollow_service.rb | |
Only in truth-new/opensource/app/services: disabled_user_unfollow_service.rb | |
diff -ru truth-old/opensource/app/services/fan_out_on_write_service.rb truth-new/opensource/app/services/fan_out_on_write_service.rb | |
--- truth-old/opensource/app/services/fan_out_on_write_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/fan_out_on_write_service.rb 2024-04-01 14:59:13 | |
@@ -25,7 +25,7 @@ | |
deliver_to_hashtags(status) | |
- return if status.reply? && status.in_reply_to_account_id != status.account_id | |
+ nil if status.reply? && status.in_reply_to_account_id != status.account_id | |
end | |
@@ -73,8 +73,7 @@ | |
end | |
def render_anonymous_payload(status) | |
- @payload = InlineRenderer.render(status, nil, :status) | |
- @payload = Oj.dump(event: :update, payload: @payload) | |
+ @payload = REST::V2::StatusSerializer.new.serialize_to_json(status) | |
end | |
def deliver_to_hashtags(status) | |
diff -ru truth-old/opensource/app/services/favourite_service.rb truth-new/opensource/app/services/favourite_service.rb | |
--- truth-old/opensource/app/services/favourite_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/favourite_service.rb 2024-04-01 14:59:13 | |
@@ -3,18 +3,32 @@ | |
class FavouriteService < BaseService | |
include Authorization | |
include Payloadable | |
+ include Redisable | |
+ DUPLICATE_FAVOURITE_EXPIRE_AFTER = 7.days.seconds | |
+ | |
# Favourite a status and notify remote user | |
# @param [Account] account | |
# @param [Status] status | |
# @return [Favourite] | |
- def call(account, status) | |
+ def call(account, status, options = {}) | |
authorize_with account, status, :favourite? | |
favourite = Favourite.find_by(account: account, status: status) | |
- return favourite unless favourite.nil? | |
+ unless favourite.nil? | |
+ if options[:user_agent] | |
+ redis_key = "duplicate_favourites:#{DateTime.current.to_date}" | |
+ redis_element_key = options[:user_agent] | |
+ redis.zincrby(redis_key, 1, redis_element_key) | |
+ redis.expire(redis_key, DUPLICATE_FAVOURITE_EXPIRE_AFTER) | |
+ Rails.logger.error "duplicate_favourites: #{favourite.id}, difference: #{Time.now.to_i - favourite.created_at.to_i}, user_agent: #{redis_element_key}" | |
+ end | |
+ | |
+ return favourite | |
+ end | |
+ | |
favourite = Favourite.create!(account: account, status: status) | |
read_from_replica do | |
@@ -31,9 +45,10 @@ | |
def create_notification(favourite) | |
status = favourite.status | |
+ type = status.group ? :group_favourite : :favourite | |
if status.account.local? | |
- NotifyService.new.call(status.account, :favourite, favourite) | |
+ NotifyService.new.call(status.account, type, favourite) | |
elsif status.account.activitypub? | |
ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url) | |
end | |
@@ -41,8 +56,7 @@ | |
def bump_potential_friendship(account, status) | |
ActivityTracker.increment('activity:interactions') | |
- return if account.following?(status.account_id) | |
- PotentialFriendshipTracker.record(account.id, status.account_id, :favourite) | |
+ InteractionsTracker.new(account.id, status.account_id, :favourite, account.following?(status.account_id), status.group).track | |
end | |
def build_json(favourite) | |
diff -ru truth-old/opensource/app/services/fetch_link_card_service.rb truth-new/opensource/app/services/fetch_link_card_service.rb | |
--- truth-old/opensource/app/services/fetch_link_card_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/fetch_link_card_service.rb 2024-04-01 14:59:13 | |
@@ -1,21 +1,12 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/serializers/card_joined_event.rb" | |
class FetchLinkCardService < BaseService | |
- URL_PATTERN = %r{ | |
- (#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]}) # $1 preceeding chars | |
- ( # $2 URL | |
- (https?:\/\/) # $3 Protocol (required) | |
- (#{Twitter::TwitterText::Regex[:valid_domain]}) # $4 Domain(s) | |
- (?::(#{Twitter::TwitterText::Regex[:valid_port_number]}))? # $5 Port number (optional) | |
- (/#{Twitter::TwitterText::Regex[:valid_url_path]}*)? # $6 URL Path and anchor | |
- (\?#{Twitter::TwitterText::Regex[:valid_url_query_chars]}*#{Twitter::TwitterText::Regex[:valid_url_query_ending_chars]})? # $7 Query String | |
- ) | |
- }iox | |
+ include LinksParserConcern | |
- def call(status, url = nil) | |
+ def call(status, url = nil, request_domain = nil) | |
@status = status | |
- @url = url || parse_urls | |
+ @url = url || parse_urls | |
+ @request_domain = request_domain | |
@known_oembed_paths = { | |
"rumble.com": { | |
@@ -23,7 +14,6 @@ | |
format: :json, | |
}, | |
} | |
- | |
return if @url.nil? || @status.preview_cards.any? | |
@url = @url.to_s | |
@@ -32,13 +22,13 @@ | |
parsed_uri = Addressable::URI.parse(full_url.to_s) | |
check_known_short_links(parsed_uri) | |
- Prometheus::ApplicationExporter::increment(:links, {domain: parsed_uri.normalized_host}) | |
+ Prometheus::ApplicationExporter.increment(:links, { domain: parsed_uri.normalized_host }) | |
end | |
RedisLock.acquire(lock_options) do |lock| | |
if lock.acquired? | |
@card = PreviewCard.find_by(url: @url) | |
- process_url if @card.nil? || @card.updated_at <= 2.weeks.ago || @card.missing_image? | |
+ process_url if @card.nil? || @card.updated_at <= 2.weeks.ago || @card.missing_image? && !interactive_ad? | |
else | |
raise Mastodon::RaceConditionError | |
end | |
@@ -48,7 +38,6 @@ | |
attach_card | |
publish_card_joined_event | |
end | |
- | |
rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e | |
Rails.logger.info "Error fetching link #{@url}: #{e}" | |
nil | |
@@ -61,23 +50,19 @@ | |
path = uri.omit(:scheme, :authority, :host).to_s[1..-1] | |
short_links = { | |
- "youtu.be": "https://www.youtube.com/watch?v=#{path.sub('?', '&')}" | |
+ "youtu.be": "https://www.youtube.com/watch?v=#{path.sub('?', '&')}", | |
} | |
@url = short_links[domain.to_sym] if short_links[domain.to_sym] | |
end | |
def publish_card_joined_event | |
- Redis.current.publish( | |
- CardJoinedEvent::EVENT_KEY, | |
- CardJoinedEvent.new(@card).serialize | |
- ) | |
+ EventProvider::EventProvider.new('card.joined', CardJoinedEvent, @card).call | |
end | |
def process_url | |
@card ||= PreviewCard.new(url: @url) | |
- | |
- attempt_oembed || attempt_opengraph | |
+ parsed_url.normalized_host == 'rumble.com' ? (attempt_oembed || attempt_opengraph) : (attempt_group || attempt_opengraph || attempt_oembed) | |
end | |
def html | |
@@ -97,7 +82,7 @@ | |
def attach_card | |
@status.preview_cards << @card | |
Rails.cache.delete(@status) | |
- InvalidateSecondaryCacheService.new.call("InvalidateStatusCacheWorker", @status.id) | |
+ InvalidateSecondaryCacheService.new.call('InvalidateStatusCacheWorker', @status.id) | |
end | |
def parse_urls | |
@@ -108,15 +93,9 @@ | |
links = html.css(':not(.quote-inline) > a') | |
@all_urls = links.filter_map { |a| Addressable::URI.parse(a['href']) unless skip_link?(a) }.filter_map(&:normalize) | |
end | |
- | |
- @all_urls.reject { |uri| bad_url?(uri) }.first | |
+ @all_urls.reject { |uri| bad_url_with_group?(uri) }.first | |
end | |
- def bad_url?(uri) | |
- # Avoid local instance URLs and invalid URLs | |
- uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme) | |
- end | |
- | |
# rubocop:disable Naming/MethodParameterName | |
def mention_link?(a) | |
@status.mentions.any? do |mention| | |
@@ -132,20 +111,25 @@ | |
def attempt_oembed | |
service = FetchOEmbedService.new | |
- url_domain = Addressable::URI.parse(@url).normalized_host | |
+ url_domain = parsed_url.normalized_host | |
cached_endpoint = Rails.cache.read("oembed_endpoint:#{url_domain}") | |
embed = service.call(@url, cached_endpoint: cached_endpoint) unless cached_endpoint.nil? | |
embed ||= service.call(@url, cached_endpoint: @known_oembed_paths[url_domain.to_sym]) if @known_oembed_paths.key?(url_domain.to_sym) | |
- embed ||= service.call(@url, html: html) unless html.nil? | |
+ if !embed && !html.nil? | |
+ service.call(@url, html: html) | |
+ end | |
+ | |
return false if embed.nil? | |
url = Addressable::URI.parse(service.endpoint_url) | |
+ raise Mastodon::UnexpectedResponseError, service.endpoint_url unless embed[:thumbnail_url].present? | |
+ | |
@card.type = embed[:type] | |
@card.title = embed[:title].present? ? CGI.unescapeHTML(embed[:title]) : '' | |
- @card.author_name = embed[:author_name] || '' | |
+ @card.author_name = embed[:author_name] || '' | |
@card.author_url = embed[:author_url].present? ? (url + embed[:author_url]).to_s : '' | |
@card.provider_name = embed[:provider_name] || '' | |
@card.provider_url = embed[:provider_url].present? ? (url + embed[:provider_url]).to_s : '' | |
@@ -202,18 +186,37 @@ | |
@card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || '' | |
@card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || '' | |
- @card.image_remote_url = (Addressable::URI.parse(@url) + meta_property(page, 'og:image')).to_s if meta_property(page, 'og:image') | |
+ @card.image_remote_url = (parsed_url + meta_property(page, 'og:image')).to_s if meta_property(page, 'og:image') | |
return if @card.title.blank? && @card.html.blank? | |
@card.save_with_optional_image! | |
end | |
+ def attempt_group | |
+ return unless group_url?(parsed_url.to_s) | |
+ | |
+ group_slug = extract_group_slug(parsed_url.to_s) | |
+ group = Group.find_by!({ slug: group_slug.to_s }) | |
+ @card.title = group.display_name | |
+ @card.description = group.note | |
+ @card.image_remote_url = full_asset_url(group.header_static_url) if group.header_file_name | |
+ @card.save_with_optional_image! | |
+ end | |
+ | |
def meta_property(page, property) | |
page.at_xpath("//meta[contains(concat(' ', normalize-space(@property), ' '), ' #{property} ')]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value | |
end | |
def lock_options | |
{ redis: Redis.current, key: "fetch:#{@url}", autorelease: 15.minutes.seconds } | |
+ end | |
+ | |
+ def interactive_ad? | |
+ !!@card&.statuses&.last&.ad | |
+ end | |
+ | |
+ def parsed_url | |
+ Addressable::URI.parse(@url) | |
end | |
end | |
diff -ru truth-old/opensource/app/services/fetch_oembed_service.rb truth-new/opensource/app/services/fetch_oembed_service.rb | |
--- truth-old/opensource/app/services/fetch_oembed_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/fetch_oembed_service.rb 2024-04-12 09:09:12 | |
@@ -5,6 +5,17 @@ | |
attr_reader :url, :options, :format, :endpoint_url | |
+ # | |
+ # Calls the service with a given URL and options. | |
+ # If the options hash contains a `:cached_endpoint`, it will parse the cached endpoint. | |
+ # Otherwise, it will discover the endpoint by fetching the HTML of the page and looking | |
+ # for OEmbed links in the head of the document. After the endpoint is determined, it fetches | |
+ # the OEmbed data from the endpoint URL. | |
+ # | |
+ # @param url [String] the URL to fetch OEmbed data from | |
+ # @param options [Hash] a hash of options. Can contain a `:cached_endpoint` key with a cached endpoint to use. | |
+ # @return [Hash, nil] the fetched OEmbed data, or nil if an error occurred | |
+ # | |
def call(url, options = {}) | |
@url = url | |
@options = options | |
diff -ru truth-old/opensource/app/services/follow_service.rb truth-new/opensource/app/services/follow_service.rb | |
--- truth-old/opensource/app/services/follow_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/follow_service.rb 2024-04-01 14:59:13 | |
@@ -14,10 +14,11 @@ | |
# @option [Boolean] :bypass_locked | |
# @option [Boolean] :bypass_limit Allow following past the total follow number | |
# @option [Boolean] :with_rate_limit | |
+ # @option [Boolean] :skip_notification Do not notify followed user | |
def call(source_account, target_account, options = {}) | |
@source_account = source_account | |
@target_account = target_account | |
- @options = { bypass_locked: false, bypass_limit: false, with_rate_limit: false }.merge(options) | |
+ @options = { bypass_locked: false, bypass_limit: false, with_rate_limit: false, skip_notification: false }.merge(options) | |
raise ActiveRecord::RecordNotFound if following_not_possible? | |
raise Mastodon::NotPermittedError if following_not_allowed? | |
@@ -79,12 +80,16 @@ | |
def direct_follow! | |
follow = @source_account.follow!(@target_account, reblogs: @options[:reblogs], notify: @options[:notify], rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]) | |
- LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, :follow) | |
+ LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, :follow) unless @options[:skip_notification] | |
MergeWorker.perform_async(@target_account.id, @source_account.id) | |
redis.del("whale:following:#{@source_account.id}") if @target_account.whale? | |
InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", @source_account.id, @target_account.id, @target_account.whale?) | |
+ | |
+ redis.del("avatars_carousel_list_#{@source_account.id}") if @source_account.following_count.to_i < 10 | |
+ | |
+ InvalidateSecondaryCacheService.new.call("InvalidateAvatarsCarouselCacheWorker", @source_account.id) | |
follow | |
end | |
Only in truth-new/opensource/app/services: geo_service.rb | |
Only in truth-new/opensource/app/services: group_membership_validation_service.rb | |
Only in truth-new/opensource/app/services: group_search_service.rb | |
Only in truth-new/opensource/app/services: inspect_link_service.rb | |
Only in truth-new/opensource/app/services: interactive_ads_service.rb | |
diff -ru truth-old/opensource/app/services/invalidate_secondary_cache_service.rb truth-new/opensource/app/services/invalidate_secondary_cache_service.rb | |
--- truth-old/opensource/app/services/invalidate_secondary_cache_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/invalidate_secondary_cache_service.rb 2024-04-01 14:59:13 | |
@@ -5,7 +5,7 @@ | |
return unless secondary_dcs | |
secondary_dcs.split(',').map(&:strip).each do |dc| | |
- worker_name.constantize.set(queue: dc).perform_in(1.second, *args) | |
+ worker_name.constantize.set(queue: dc).perform_async(*args) | |
end | |
end | |
end | |
Only in truth-new/opensource/app/services: ios_device_check | |
Only in truth-new/opensource/app/services: join_group_service.rb | |
Only in truth-new/opensource/app/services: leave_group_service.rb | |
diff -ru truth-old/opensource/app/services/notify_service.rb truth-new/opensource/app/services/notify_service.rb | |
--- truth-old/opensource/app/services/notify_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/notify_service.rb 2024-04-01 14:59:13 | |
@@ -15,6 +15,7 @@ | |
@activity = activity | |
@type = type | |
@notification = Notification.new(account: @recipient, type: type, activity: @activity) | |
+ | |
return if recipient.user.nil? || blocked? | |
@target_status = target_status | |
@@ -61,6 +62,50 @@ | |
false | |
end | |
+ def blocked_chat? | |
+ false | |
+ end | |
+ | |
+ def blocked_chat_message_deleted? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_mention? | |
+ FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient) | |
+ end | |
+ | |
+ def blocked_group_reblog? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_follow? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_favourite? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_delete? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_approval? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_request? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_promoted? | |
+ false | |
+ end | |
+ | |
+ def blocked_group_demoted? | |
+ false | |
+ end | |
+ | |
def following_sender? | |
return @following_sender if defined?(@following_sender) | |
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account) | |
@@ -94,6 +139,10 @@ | |
message? && target_status.direct_visibility? | |
end | |
+ def chat? | |
+ @notification.type == :chat | |
+ end | |
+ | |
def response_to_recipient? | |
target_status.in_reply_to_account_id == @recipient.id && target_status.thread&.direct_visibility? | |
end | |
@@ -131,6 +180,7 @@ | |
blocked ||= domain_blocking? # Skip for domain blocked accounts | |
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts | |
+ blocked ||= @notification.from_account.blocking?(@recipient) # Skip for blocked accounts - the other direction | |
blocked ||= @recipient.muting_notifications?(@notification.from_account) | |
blocked ||= hellbanned? # Hellban | |
blocked ||= optional_non_follower? # Options | |
Only in truth-new/opensource/app/services: p_tv | |
diff -ru truth-old/opensource/app/services/post_distribution_service.rb truth-new/opensource/app/services/post_distribution_service.rb | |
--- truth-old/opensource/app/services/post_distribution_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/post_distribution_service.rb 2024-04-05 09:07:34 | |
@@ -1,24 +1,36 @@ | |
+# frozen_string_literal: true | |
class PostDistributionService < BaseService | |
include Redisable | |
BAILEY_PERCENTAGE = (ENV['BAILEY_PERCENTAGE'] || "0").to_i | |
- def call(status) | |
- if rand(1..100) <= BAILEY_PERCENTAGE && !status.account.whale? | |
- send_to_bailey(status) | |
+ # Enqueue jobs for both author and followers | |
+ def distribute_to_author(status) | |
+ if rand(1..100) <= BAILEY_PERCENTAGE | |
+ QueueManager.enqueue_status_for_author_distribution(status.id) | |
+ Rails.logger.debug("bailey_debug: enqueuing status #{status.id}") | |
else | |
FanOutOnWriteService.new.call(status) | |
end | |
end | |
- def send_to_bailey(status) | |
- if status.account.silenced? || !status.public_visibility? || status.reblog? | |
- rendered = nil | |
+ def distribute_to_followers(status) | |
+ if rand(1..100) <= BAILEY_PERCENTAGE | |
+ QueueManager.enqueue_status_for_follower_distribution(status.id) | |
+ Rails.logger.debug("bailey_debug: enqueuing status #{status.id}") | |
else | |
- rendered = InlineRenderer.render(status, nil, :status) | |
- rendered = Oj.dump(event: :update, payload: rendered) | |
+ FanOutOnWriteService.new.call(status) | |
end | |
- Redis.current.lpush('elixir:distribution', Oj.dump(job_type: "status_created", status_id: status.id, rendered: rendered)) | |
- Rails.logger.debug("bailey_debug: sending #{rendered.nil? ? 'nil' : 'value'} for rendered for status #{status.id}") | |
end | |
+ | |
+ def distribute_to_author_and_followers(status) | |
+ if rand(1..100) <= BAILEY_PERCENTAGE | |
+ QueueManager.enqueue_status_for_author_distribution(status.id) | |
+ QueueManager.enqueue_status_for_follower_distribution(status.id) | |
+ Rails.logger.debug("bailey_debug: enqueuing status #{status.id}") | |
+ else | |
+ FanOutOnWriteService.new.call(status) | |
+ end | |
+ end | |
+ | |
end | |
diff -ru truth-old/opensource/app/services/post_status_service.rb truth-new/opensource/app/services/post_status_service.rb | |
--- truth-old/opensource/app/services/post_status_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/post_status_service.rb 2024-04-12 09:09:12 | |
@@ -1,5 +1,4 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/serializers/status_created_event.rb" | |
class PostStatusService < BaseService | |
include Redisable | |
@@ -14,6 +13,7 @@ | |
# @option [Status] :thread Optional status to reply to | |
# @option [Boolean] :sensitive | |
# @option [String] :visibility | |
+ # @option [Group] :group Optional group to post to, `visibility` must be set to 'group' in that case | |
# @option [String] :spoiler_text | |
# @option [String] :language | |
# @option [String] :scheduled_at | |
@@ -22,18 +22,26 @@ | |
# @option [Doorkeeper::Application] :application | |
# @option [String] :idempotency Optional idempotency key | |
# @option [Boolean] :with_rate_limit | |
+ # @option [String] :ip_address IP address of where the request originated | |
# @return [Status] | |
def call(account, options = {}) | |
- @account = account | |
- @options = options | |
- @text = @options[:text] || '' | |
- @in_reply_to = @options[:thread] | |
- @quote_id = @options[:quote_id] | |
- @mentions = @options[:mentions] || [] | |
+ @account = account | |
+ @options = options | |
+ @text = @options[:text] || '' | |
+ @in_reply_to = @options[:thread] | |
+ @quote_id = @options[:quote_id] | |
+ @mentions = @options[:mentions] || [] | |
+ @ip_address = @options[:ip_address] || '' | |
+ @group = @options[:group] | |
+ @group_timeline_visible = @options[:group_timeline_visible] | |
+ @group_visibility = @options[:group_visibility] | |
+ @domain = @options[:domain] | |
+ @links_service = process_links_service | |
+ | |
return idempotency_duplicate if idempotency_given? && idempotency_duplicate? | |
- validate_media! | |
+ @media = validate_media! | |
preprocess_attributes! | |
preprocess_quote! | |
@@ -43,24 +51,21 @@ | |
process_status! | |
postprocess_status! | |
bump_potential_friendship! | |
- create_status_event | |
+ publish_status_event | |
export_prometheus_metric | |
end | |
redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given? | |
- send_video_to_upload_worker(@media_ids.first) if video_status? | |
+ send_video_to_upload_worker | |
@status | |
end | |
private | |
- def create_status_event | |
- Redis.current.publish( | |
- StatusCreatedEvent::EVENT_KEY, | |
- StatusCreatedEvent.new(@status).serialize | |
- ) | |
+ def publish_status_event | |
+ EventProvider::EventProvider.new('status.created', StatusCreatedEvent, @status, @ip_address).call unless @group_visibility == :members_only | |
end | |
def status_from_uri(uri) | |
@@ -90,6 +95,8 @@ | |
@quote_id = quote_from_url(md[1])&.id | |
@text.sub!(/RT:\s*\[.*?\]/, '') | |
end | |
+ | |
+ @text = @links_service.resolve_urls(@text) | |
rescue ArgumentError | |
raise ActiveRecord::RecordInvalid | |
end | |
@@ -104,13 +111,13 @@ | |
def process_status! | |
# The following transaction block is needed to wrap the UPDATEs to | |
# the media attachments when the status is created | |
- | |
ApplicationRecord.transaction do | |
@status = @account.statuses.create!(status_attributes) | |
+ ProcessMentionsService.new.call(@status, @mentions, @in_reply_to) | |
end | |
process_hashtags_service.call(@status) | |
- process_mentions_service.call(@status, @mentions) | |
+ @links_service.call(@status) | |
end | |
def schedule_status! | |
@@ -131,45 +138,59 @@ | |
end | |
def postprocess_status! | |
- LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text? || video_status? | |
- post_distribution_service.call(@status) | |
- PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll | |
+ LinkCrawlWorker.perform_async(@status.id, nil, @domain) unless @status.spoiler_text? || video_status? | |
+ PostDistributionService.new.distribute_to_author(@status) | |
+ # PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll | |
end | |
+ # | |
+ # @return [Array] Returns an empty array if there are no media_ids or if media_ids is not an Enumerable. | |
+ # Otherwise, it returns the media attachments associated with the account that have not been assigned a status yet. | |
+ # | |
+ # @raise [Mastodon::ValidationError] If there are more than 4 media attachments or if a poll is present. | |
+ # @raise [Mastodon::ValidationError] If any of the media attachments are not processed yet. | |
+ # | |
def validate_media! | |
- return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) | |
+ return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) | |
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present? | |
+ if @options[:media_ids].size > 4 || @options[:poll].present? | |
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') | |
+ end | |
- @media_ids = @options[:media_ids].take(4).map(&:to_i) | |
- @media = @account.media_attachments.where(status_id: nil).where(id: @media_ids).sort_by {|m| @media_ids.index(m.id)} | |
+ media_ids = @options[:media_ids].map(&:to_i) | |
+ media = @account | |
+ .media_attachments | |
+ .where(status_id: nil) | |
+ .where(id: media_ids) | |
+ .sort_by { |m| media_ids.index(m.id) } | |
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?) | |
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?) | |
+ if media.any? { |m| !m.video? && m.not_processed? } | |
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') | |
+ end | |
+ | |
+ media | |
end | |
def video_upload_enabled? | |
ENV['VIDEO_REMOTE_UPLOAD_ENABLED'] == 'true' | |
end | |
- def send_video_to_upload_worker(media_attachment_id) | |
- VideoUploadWorker.perform_async(media_attachment_id, @status.id) | |
+ def send_video_to_upload_worker | |
+ @media.each do |m| | |
+ UploadVideoStatusWorker.perform_async(m.id, @status.id) if m.video? | |
+ end | |
end | |
def language_from_option(str) | |
ISO_639.find(str)&.alpha2 | |
end | |
- def process_mentions_service | |
- ProcessMentionsService.new | |
- end | |
- | |
def process_hashtags_service | |
ProcessHashtagsService.new | |
end | |
- def post_distribution_service | |
- PostDistributionService.new | |
+ def process_links_service | |
+ ProcessStatusLinksService.new | |
end | |
def scheduled? | |
@@ -201,10 +222,13 @@ | |
end | |
def bump_potential_friendship! | |
- return if [email protected]? || @account.id == @status.in_reply_to_account_id | |
- ActivityTracker.increment('activity:interactions') | |
- return if @account.following?(@status.in_reply_to_account_id) | |
- PotentialFriendshipTracker.record(@account.id, @status.in_reply_to_account_id, :reply) | |
+ if @status.reply? && @account.id != @status.in_reply_to_account_id | |
+ ActivityTracker.increment('activity:interactions') | |
+ InteractionsTracker.new(@account.id, @status.in_reply_to_account_id, :reply, @account.following?(@status.in_reply_to_account_id), @status.group).track | |
+ elsif @status.quote? && @account.id != @status.quote.account_id | |
+ ActivityTracker.increment('activity:interactions') | |
+ InteractionsTracker.new(@account.id, @status.quote.account_id, :quote, @account.following?(@status.quote.account_id), @status.quote.group).track | |
+ end | |
end | |
def status_attributes | |
@@ -212,7 +236,10 @@ | |
text: @text, | |
media_attachments: @media || [], | |
thread: @in_reply_to, | |
- poll_attributes: poll_attributes, | |
+ group: @group, | |
+ group_timeline_visible: @group_timeline_visible || false, | |
+ polls: poll_attributes, | |
+ has_poll: @options[:poll].present?, | |
sensitive: @sensitive, | |
spoiler_text: @options[:spoiler_text] || '', | |
visibility: @visibility, | |
@@ -233,13 +260,14 @@ | |
def poll_attributes | |
return if @options[:poll].blank? | |
- | |
- @options[:poll].merge(account: @account, voters_count: 0) | |
+ @options[:poll][:options_attributes] = @options[:poll].delete(:options).map.with_index { |v, i| { option_number: i, text: v } } | |
+ [Poll.new(@options[:poll])] | |
end | |
def scheduled_options | |
@options.tap do |options_hash| | |
options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id | |
+ options_hash[:group_id] = options_hash.delete(:group)&.id | |
options_hash[:application_id] = options_hash.delete(:application)&.id | |
options_hash[:scheduled_at] = nil | |
options_hash[:idempotency] = nil | |
@@ -249,10 +277,10 @@ | |
def export_prometheus_metric | |
metric_type = @in_reply_to ? :replies : :statuses | |
- Prometheus::ApplicationExporter::increment(metric_type) | |
+ Prometheus::ApplicationExporter.increment(metric_type) | |
end | |
def video_status? | |
- @media.present? && @media.first.video? && video_upload_enabled? | |
+ video_upload_enabled? && @media.any?(&:video?) | |
end | |
end | |
Only in truth-new/opensource/app/services: privatize_media_attachment_service.rb | |
Only in truth-new/opensource/app/services: process_chat_links_service.rb | |
diff -ru truth-old/opensource/app/services/process_mentions_service.rb truth-new/opensource/app/services/process_mentions_service.rb | |
--- truth-old/opensource/app/services/process_mentions_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/process_mentions_service.rb 2024-04-12 09:09:08 | |
@@ -2,50 +2,97 @@ | |
class ProcessMentionsService < BaseService | |
include Payloadable | |
+ include Redisable | |
MAX_MENTIONS = ENV.fetch('MAX_MENTIONS', 15).to_i | |
+ NOTIFICATIONS_TRESHOLD = 100 | |
+ MENTION_MISMATCH_EXPIRE_AFTER = 7.days.seconds | |
# Scan status for mentions and fetch remote mentioned users, create | |
# local mention pointers, send Salmon notifications to mentioned | |
# remote users | |
# @param [Status] status | |
# @param [Enumerable] mentions an array of usernames | |
- def call(status, mentions) | |
+ # @param [Status] thread in_reply_to status | |
+ def call(status, mentions, thread = nil) | |
+ @passed_mentions = mentions | |
@status = status | |
+ @status_mentions = scan_mentions(@status).map(&:downcase) | |
+ @thread = thread | |
+ mention_notifications = [] | |
- mentions = mentions.first(MAX_MENTIONS) if mentions.length > MAX_MENTIONS | |
+ if @status.reply? | |
+ previous_mentioned_usernames = Account.joins(:mentions).where('mentions.status_id = ?', @thread.id).pluck(:username) | |
+ @previously_mentioned = (previous_mentioned_usernames << @thread.account.username).map(&:downcase) | |
+ end | |
+ group = @status.group_visibility?.presence && @status.group | |
+ mentions = mentions.first(MAX_MENTIONS) if mentions.length > MAX_MENTIONS | |
mentioned_accounts = Account.ci_find_by_usernames(mentions) | |
+ accounts_with_mention_preference = mentioned_accounts.where(receive_only_follow_mentions: true) | |
+ if accounts_with_mention_preference.any? | |
+ followers = Follow.where(account: accounts_with_mention_preference, target_account: @status.account).pluck(:account_id) | |
+ end | |
+ | |
mentioned_accounts.each do |acc| | |
next acc if mention_undeliverable?(acc) || acc.suspended? | |
+ reject_missing_status_mention(acc.username.downcase) | |
+ # Since mentions are currently tied to audience and notifications, skip mentions | |
+ # of non-members if private group | |
+ next acc if (group&.members_only? && !group.members&.where(id: acc.id)&.exists?) || skip_new_account_mentions(acc) | |
+ | |
+ next acc if acc.receive_only_follow_mentions && !followers.include?(acc.id) | |
+ | |
new_mention = acc.mentions.new(status: status) | |
- create_notification(new_mention) if new_mention.save | |
+ mention_notifications << new_mention if new_mention.save | |
end | |
+ | |
+ mention_notifications | |
end | |
+ def self.create_notification(status, mention) | |
+ mentioned_account = mention.account | |
+ type = status.group ? :group_mention : :mention | |
+ | |
+ if status.account.followers_count < NOTIFICATIONS_TRESHOLD | |
+ ProcessMentionNotificationsWorker.perform_in(61.seconds, status.id, mention.id, type.to_sym) | |
+ else | |
+ LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name, type.to_sym) | |
+ end | |
+ end | |
+ | |
private | |
def mention_undeliverable?(mentioned_account) | |
mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus?) | |
end | |
- def create_notification(mention) | |
- mentioned_account = mention.account | |
+ def scan_mentions(status) | |
+ status.text.scan(Account::MENTION_RE).map(&:second).map(&:downcase) | |
+ end | |
- if mentioned_account.local? | |
- LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name, :mention) | |
- elsif mentioned_account.activitypub? | |
- ActivityPub::DeliveryWorker.perform_async(activitypub_json, mention.status.account_id, mentioned_account.inbox_url, { synchronize_followers: !mention.status.distributable? }) | |
+ def reject_missing_status_mention(username) | |
+ return if @status_mentions.include? username | |
+ | |
+ if (quote = @status.quote?) | |
+ return if username == quote.account.username.downcase | |
+ elsif @status.reply? | |
+ return if @previously_mentioned.include? username | |
end | |
- end | |
- def activitypub_json | |
- return @activitypub_json if defined?(@activitypub_json) | |
- @activitypub_json = Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_status(@status), ActivityPub::ActivitySerializer, signer: @status.account)) | |
+ Rails.logger.info "mention_mismatch #{@status.account_id} thread_id: #{@thread&.id} reply_text: #{@status.text} passed_mentions: #{@passed_mentions} previously_mentioned: #{@previously_mentioned}" | |
+ | |
+ redis_key = "mention_mismatch:#{DateTime.current.to_date}" | |
+ redis_element_key = @status.account_id | |
+ redis.zincrby(redis_key, 1, redis_element_key) | |
+ redis.expire(redis_key, MENTION_MISMATCH_EXPIRE_AFTER) | |
+ | |
+ raise Mastodon::ValidationError, I18n.t('statuses.errors.mention_mismatch') | |
end | |
- def resolve_account_service | |
- ResolveAccountService.new | |
+ def skip_new_account_mentions(acc) | |
+ return false if (Time.now - @status.account.created_at).round > 7.days | |
+ [email protected]? || (@status.reply? && !@previously_mentioned.include?(acc.username.downcase)) | |
end | |
end | |
Only in truth-new/opensource/app/services: process_status_links_service.rb | |
Only in truth-new/opensource/app/services: publish_media_attachment_service.rb | |
diff -ru truth-old/opensource/app/services/reblog_service.rb truth-new/opensource/app/services/reblog_service.rb | |
--- truth-old/opensource/app/services/reblog_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/reblog_service.rb 2024-04-05 09:07:34 | |
@@ -3,7 +3,10 @@ | |
class ReblogService < BaseService | |
include Authorization | |
include Payloadable | |
+ include Redisable | |
+ DUPLICATE_REBLOG_EXPIRE_AFTER = 7.days.seconds | |
+ | |
# Reblog a status and notify its remote author | |
# @param [Account] account Account to reblog from | |
# @param [Status] reblogged_status Status to be reblogged | |
@@ -18,17 +21,27 @@ | |
reblog = account.statuses.find_by(reblog: reblogged_status) | |
- return reblog unless reblog.nil? | |
+ unless reblog.nil? | |
+ if options[:user_agent] | |
+ redis_key = "duplicate_reblogs:#{DateTime.current.to_date}" | |
+ redis_element_key = options[:user_agent] | |
+ redis.zincrby(redis_key, 1, redis_element_key) | |
+ redis.expire(redis_key, DUPLICATE_REBLOG_EXPIRE_AFTER) | |
+ end | |
+ return reblog | |
+ end | |
+ | |
visibility = if reblogged_status.hidden? | |
reblogged_status.visibility | |
else | |
options[:visibility] || account.user&.setting_default_privacy | |
end | |
- reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit]) | |
+ reblog_params = reblogged_status.group_visibility? ? { group_id: reblogged_status.group.id } : {} | |
+ reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit], **reblog_params) | |
- PostDistributionService.new.call(reblog) | |
+ PostDistributionService.new.distribute_to_author_and_followers(reblog) | |
ActivityPub::DistributionWorker.perform_async(reblog.id) | |
create_notification(reblog) | |
@@ -43,9 +56,10 @@ | |
def create_notification(reblog) | |
reblogged_status = reblog.reblog | |
+ type = reblogged_status.group ? :group_reblog : :reblog | |
if reblogged_status.account.local? | |
- LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, :reblog) | |
+ LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, type) | |
elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account) | |
ActivityPub::DeliveryWorker.perform_async(build_json(reblog), reblog.account_id, reblogged_status.account.inbox_url) | |
end | |
@@ -53,10 +67,7 @@ | |
def bump_potential_friendship(account, reblog) | |
ActivityTracker.increment('activity:interactions') | |
- | |
- return if account.following?(reblog.reblog.account_id) | |
- | |
- PotentialFriendshipTracker.record(account.id, reblog.reblog.account_id, :reblog) | |
+ InteractionsTracker.new(account.id, reblog.reblog.account_id, :reblog, account.following?(reblog.reblog.account_id), reblog.group).track | |
end | |
def record_use(account, reblog) | |
Only in truth-new/opensource/app/services: registration_service.rb | |
Only in truth-new/opensource/app/services: reject_membership_service.rb | |
diff -ru truth-old/opensource/app/services/remove_status_service.rb truth-new/opensource/app/services/remove_status_service.rb | |
--- truth-old/opensource/app/services/remove_status_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/remove_status_service.rb 2024-04-01 14:59:13 | |
@@ -4,7 +4,7 @@ | |
include Redisable | |
include Payloadable | |
- BAILEY_PERCENTAGE = (ENV['BAILEY_PERCENTAGE'] || "0").to_i | |
+ BAILEY_PERCENTAGE = (ENV['BAILEY_PERCENTAGE'] || '0').to_i | |
# Delete a status | |
# @param [Status] status | |
@@ -17,13 +17,18 @@ | |
@status = status | |
@account = status.account | |
@immediate = options.key?(:immediate) ? options[:immediate] : false | |
- @options = options | |
+ @options = options | |
@status.discard | |
+ EventProvider::EventProvider.new('status.removed', StatusRemovedEvent, @status, options[:called_by_id]).call if options[:called_by_id] == @status.account_id | |
+ | |
RedisLock.acquire(lock_options) do |lock| | |
if lock.acquired? | |
+ remove_user_interactions! | |
+ remove_from_timeline_cache | |
+ | |
if rand(1..100) <= BAILEY_PERCENTAGE | |
send_to_bailey | |
else | |
@@ -33,12 +38,7 @@ | |
remove_from_lists | |
- # There is no reason to send out Undo activities when the | |
- # cause is that the original object has been removed, since | |
- # original object being removed implicitly removes reblogs | |
- # of it. The Delete activity of the original is forwarded | |
- # separately. | |
- remove_from_remote_reach if @account.local? && !@options[:original_removed] | |
+ remove_from_group | |
unless @status.reblog? | |
remove_reblogs | |
@@ -56,6 +56,9 @@ | |
notify_user if options[:notify_user] | |
end | |
+ remove_ad_data if @status.ad? | |
+ purge_cache | |
+ | |
@status.destroy! if @immediate | |
else | |
raise Mastodon::RaceConditionError | |
@@ -81,7 +84,6 @@ | |
end | |
end | |
- | |
def remove_from_whale_list | |
FeedManager.instance.remove_from_whale(@status) | |
end | |
@@ -102,19 +104,6 @@ | |
end | |
end | |
- def remove_from_remote_reach | |
- # Followers, relays, people who got mentioned in the status, | |
- # or who reblogged it from someone else might not follow | |
- # the author and wouldn't normally receive the delete | |
- # notification - so here, we explicitly send it to them | |
- | |
- status_reach_finder = StatusReachFinder.new(@status) | |
- | |
- ActivityPub::DeliveryWorker.push_bulk(status_reach_finder.inboxes) do |inbox_url| | |
- [signed_activity_json, @account.id, inbox_url] | |
- end | |
- end | |
- | |
def signed_activity_json | |
@signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account)) | |
end | |
@@ -142,6 +131,12 @@ | |
end | |
end | |
+ def remove_from_group | |
+ return unless @status.group_visibility? | |
+ | |
+ redis.publish("timeline:group:#{@status.group_id}", @payload) | |
+ end | |
+ | |
def remove_media | |
return if @options[:redraft] || !@immediate | |
@@ -153,8 +148,35 @@ | |
end | |
def send_to_bailey | |
- Redis.current.lpush('elixir:distribution', @payload) | |
- Rails.logger.info("bailey_debug: sending for deletion status #{@status.id}") | |
+ # Don't create job. Bailey not cleaning up deletes at the moment. | |
end | |
+ def remove_user_interactions! | |
+ if @status.reply? && @account.id != @status.in_reply_to_account_id | |
+ InteractionsTracker.new(@account.id, @status.in_reply_to_account_id, :reply, @account.following?(@status.in_reply_to_account_id), @status.group).untrack | |
+ elsif @status.quote? && @account.id != @status.quote.account_id | |
+ InteractionsTracker.new(@account.id, @status.quote.account_id, :quote, @account.following?(@status.quote.account_id), @status.quote.group).untrack | |
+ end | |
+ end | |
+ | |
+ def remove_from_timeline_cache | |
+ redis.del("sevro:#{@status.id}") | |
+ end | |
+ | |
+ def remove_ad_data | |
+ @status.preview_cards.each(&:destroy) | |
+ @status.ad&.destroy | |
+ end | |
+end | |
+ | |
+def purge_cache | |
+ purge_status(@status) | |
+ Status.where(in_reply_to_id: @status.id).or(Status.where(quote_id: @status.id)).in_batches.each_record do |reply| | |
+ purge_status(reply) | |
+ end | |
+end | |
+ | |
+def purge_status(status) | |
+ Rails.cache.delete(status) | |
+ InvalidateSecondaryCacheService.new.call('InvalidateStatusCacheWorker', status) | |
end | |
diff -ru truth-old/opensource/app/services/report_service.rb truth-new/opensource/app/services/report_service.rb | |
--- truth-old/opensource/app/services/report_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/report_service.rb 2024-04-01 14:59:13 | |
@@ -1,5 +1,4 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/serializers/report_created_event.rb" | |
class ReportService < BaseService | |
include Payloadable | |
@@ -8,12 +7,13 @@ | |
@source_account = source_account | |
@target_account = target_account | |
@status_ids = options.delete(:status_ids) || [] | |
+ @message_ids = options.delete(:message_ids) || [] | |
@rule_ids = options.delete(:rule_ids) || [] | |
@comment = options.delete(:comment) || '' | |
+ @group_id = options.delete(:group_id) || nil | |
+ @external_ad_id = options.delete(:external_ad_id) || nil | |
@options = options | |
- raise ActiveRecord::RecordNotFound if @target_account.suspended? | |
- | |
create_report! | |
publish_event! | |
#notify_staff! | |
@@ -28,10 +28,14 @@ | |
@report = @source_account.reports.create!( | |
target_account: @target_account, | |
status_ids: @status_ids, | |
+ message_ids: @message_ids, | |
rule_ids: @rule_ids, | |
comment: @comment, | |
uri: @options[:uri], | |
- forwarded: ActiveModel::Type::Boolean.new.cast(@options[:forward]) | |
+ forwarded: ActiveModel::Type::Boolean.new.cast(@options[:forward]), | |
+ rate_limit: true, | |
+ group_id: @group_id, | |
+ external_ad_id: @external_ad_id | |
) | |
end | |
@@ -42,24 +46,45 @@ | |
@report.status_ids << nil | |
end | |
- @report.status_ids.each do |status_id| | |
- attachments = MediaAttachment.where(status_id: status_id) | |
+ if @report.message_ids.empty? | |
+ @report.message_ids << nil | |
+ end | |
+ if @group_id && @report.status_ids[0].nil? | |
+ # reporting a group | |
report_data = OpenStruct.new( | |
@report.attributes.merge( | |
- status_id: status_id, | |
- status_ids: @report.status_ids.compact, | |
report_set_id: set_uuid, | |
- account_username: @report.account.username, | |
- target_account_username: @report.target_account.username, | |
- image_ids: attachments.select { |a| a.image? }.pluck(:id), | |
- video_ids: attachments.select { |a| a.video? }.pluck(:id) | |
+ display_name: @report.account.username, | |
+ owner_id: @target_account.id, | |
+ group_id: @group_id | |
) | |
) | |
- Redis.current.publish( | |
- ReportCreatedEvent::EVENT_KEY, | |
- ReportCreatedEvent.new(report_data).serialize | |
- ) | |
+ EventProvider::EventProvider.new("group_report.created", GroupReportCreatedEvent, report_data).call | |
+ elsif @report.message_ids[0].nil? | |
+ # reporting a status | |
+ @report.status_ids.each do |status_id| | |
+ attachments = status_id ? MediaAttachment.where(status_id: status_id) : [] | |
+ | |
+ report_data = OpenStruct.new( | |
+ @report.attributes.merge( | |
+ status_id: status_id, | |
+ report_set_id: set_uuid, | |
+ image_ids: attachments.select { |a| a.image? }.pluck(:id), | |
+ video_ids: attachments.select { |a| a.video? }.pluck(:id), | |
+ group_id: @group_id | |
+ ) | |
+ ) | |
+ EventProvider::EventProvider.new("report.created", ReportCreatedEvent, report_data).call | |
+ end | |
+ elsif @external_ad_id.nil? | |
+ # reporting a message | |
+ @report.message_ids.each do |message_id| | |
+ report_data = OpenStruct.new( | |
+ @report.attributes.merge(message_id: message_id) | |
+ ) | |
+ EventProvider::EventProvider.new("chat_report.created", ChatMessageReportCreatedEvent, report_data).call | |
+ end | |
end | |
end | |
diff -ru truth-old/opensource/app/services/resolve_account_service.rb truth-new/opensource/app/services/resolve_account_service.rb | |
--- truth-old/opensource/app/services/resolve_account_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/resolve_account_service.rb 2024-04-01 14:59:13 | |
@@ -140,7 +140,7 @@ | |
end | |
def queue_deletion! | |
- AccountDeletionWorker.perform_async(@account.id, reserve_username: false, skip_activitypub: true) | |
+ AccountDeletionWorker.perform_async(@account.id, -99, reserve_username: false, skip_activitypub: true) | |
end | |
def lock_options | |
Only in truth-new/opensource/app/services: revoke_membership_service.rb | |
Only in truth-new/opensource/app/services: rumble | |
diff -ru truth-old/opensource/app/services/search_service.rb truth-new/opensource/app/services/search_service.rb | |
--- truth-old/opensource/app/services/search_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/search_service.rb 2024-04-12 09:09:12 | |
@@ -7,9 +7,11 @@ | |
PROHIBITED_TERMS = ENV.fetch('PROHIBITED_TERMS', '').split(',').freeze | |
PROHIBITED_FILTERS = { bool: { must_not: PROHIBITED_TERMS.map { |term| { multi_match: { type: 'most_fields', query: term } } } } }.freeze | |
+ # https://github.com/franklsf95/ruby-emoji-regex | |
+ EMOJI_REGEX = /[\u{00A9}\u{00AE}\u{203C}\u{2049}\u{2122}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{2328}\u{23CF}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2604}\u{260E}\u{2611}\u{2614}-\u{2615}\u{2618}\u{261D}\u{2620}\u{2622}-\u{2623}\u{2626}\u{262A}\u{262E}-\u{262F}\u{2638}-\u{263A}\u{2640}\u{2642}\u{2648}-\u{2653}\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267F}\u{2692}-\u{2697}\u{2699}\u{269B}-\u{269C}\u{26A0}-\u{26A1}\u{26AA}-\u{26AB}\u{26B0}-\u{26B1}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26C8}\u{26CE}-\u{26CF}\u{26D1}\u{26D3}-\u{26D4}\u{26E9}-\u{26EA}\u{26F0}-\u{26F5}\u{26F7}-\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}\u{2712}\u{2714}\u{2716}\u{271D}\u{2721}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2763}-\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{27BF}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{1F004}\u{1F0CF}\u{1F170}-\u{1F171}\u{1F17E}-\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E6}-\u{1F1FF}\u{1F201}-\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}-\u{1F251}\u{1F300}-\u{1F321}\u{1F324}-\u{1F393}\u{1F396}-\u{1F397}\u{1F399}-\u{1F39B}\u{1F39E}-\u{1F3F0}\u{1F3F3}-\u{1F3F5}\u{1F3F7}-\u{1F4FD}\u{1F4FF}-\u{1F53D}\u{1F549}-\u{1F54E}\u{1F550}-\u{1F567}\u{1F56F}-\u{1F570}\u{1F573}-\u{1F57A}\u{1F587}\u{1F58A}-\u{1F58D}\u{1F590}\u{1F595}-\u{1F596}\u{1F5A4}-\u{1F5A5}\u{1F5A8}\u{1F5B1}-\u{1F5B2}\u{1F5BC}\u{1F5C2}-\u{1F5C4}\u{1F5D1}-\u{1F5D3}\u{1F5DC}-\u{1F5DE}\u{1F5E1}\u{1F5E3}\u{1F5E8}\u{1F5EF}\u{1F5F3}\u{1F5FA}-\u{1F64F}\u{1F680}-\u{1F6C5}\u{1F6CB}-\u{1F6D2}\u{1F6E0}-\u{1F6E5}\u{1F6E9}\u{1F6EB}-\u{1F6EC}\u{1F6F0}\u{1F6F3}-\u{1F6F8}\u{1F910}-\u{1F93A}\u{1F93C}-\u{1F93E}\u{1F940}-\u{1F945}\u{1F947}-\u{1F94C}\u{1F950}-\u{1F96B}\u{1F980}-\u{1F997}\u{1F9C0}\u{1F9D0}-\u{1F9E6}\u{200D}\u{20E3}\u{FE0F}\u{E0020}-\u{E007F}\u{2388}\u{2605}\u{2607}-\u{260D}\u{260F}-\u{2610}\u{2612}\u{2616}-\u{2617}\u{2619}-\u{261C}\u{261E}-\u{261F}\u{2621}\u{2624}-\u{2625}\u{2627}-\u{2629}\u{262B}-\u{262D}\u{2630}-\u{2637}\u{263B}-\u{263F}\u{2641}\u{2643}-\u{2647}\u{2654}-\u{265F}\u{2661}-\u{2662}\u{2664}\u{2667}\u{2669}-\u{267A}\u{267C}-\u{267E}\u{2680}-\u{2691}\u{2698}\u{269A}\u{269D}-\u{269F}\u{26A2}-\u{26A9}\u{26AC}-\u{26AF}\u{26B2}-\u{26BC}\u{26BF}-\u{26C3}\u{26C6}-\u{26C7}\u{26C9}-\u{26CD}\u{26D0}\u{26D2}\u{26D5}-\u{26E8}\u{26EB}-\u{26EF}\u{26F6}\u{26FB}-\u{26FC}\u{26FE}-\u{2701}\u{2703}-\u{2704}\u{270E}\u{2710}-\u{2711}\u{2765}-\u{2767}\u{1F000}-\u{1F003}\u{1F005}-\u{1F0CE}\u{1F0D0}-\u{1F0FF}\u{1F10D}-\u{1F10F}\u{1F12F}\u{1F16C}-\u{1F16F}\u{1F1AD}-\u{1F1E5}\u{1F203}-\u{1F20F}\u{1F23C}-\u{1F23F}\u{1F249}-\u{1F24F}\u{1F252}-\u{1F2FF}\u{1F322}-\u{1F323}\u{1F394}-\u{1F395}\u{1F398}\u{1F39C}-\u{1F39D}\u{1F3F1}-\u{1F3F2}\u{1F3F6}\u{1F4FE}\u{1F53E}-\u{1F548}\u{1F54F}\u{1F568}-\u{1F56E}\u{1F571}-\u{1F572}\u{1F57B}-\u{1F586}\u{1F588}-\u{1F589}\u{1F58E}-\u{1F58F}\u{1F591}-\u{1F594}\u{1F597}-\u{1F5A3}\u{1F5A6}-\u{1F5A7}\u{1F5A9}-\u{1F5B0}\u{1F5B3}-\u{1F5BB}\u{1F5BD}-\u{1F5C1}\u{1F5C5}-\u{1F5D0}\u{1F5D4}-\u{1F5DB}\u{1F5DF}-\u{1F5E0}\u{1F5E2}\u{1F5E4}-\u{1F5E7}\u{1F5E9}-\u{1F5EE}\u{1F5F0}-\u{1F5F2}\u{1F5F4}-\u{1F5F9}\u{1F6C6}-\u{1F6CA}\u{1F6D3}-\u{1F6DF}\u{1F6E6}-\u{1F6E8}\u{1F6EA}\u{1F6ED}-\u{1F6EF}\u{1F6F1}-\u{1F6F2}\u{1F6F9}-\u{1F6FF}\u{1F774}-\u{1F77F}\u{1F7D5}-\u{1F7FF}\u{1F80C}-\u{1F80F}\u{1F848}-\u{1F84F}\u{1F85A}-\u{1F85F}\u{1F888}-\u{1F88F}\u{1F8AE}-\u{1F90F}\u{1F93F}\u{1F94D}-\u{1F94F}\u{1F96C}-\u{1F97F}\u{1F998}-\u{1F9BF}\u{1F9C1}-\u{1F9CF}]/.freeze | |
def call(query, account, limit, options = {}) | |
- @query = query&.strip | |
+ @query = create_query(query) | |
@account = account | |
@options = options | |
@limit = limit.to_i | |
@@ -25,10 +27,15 @@ | |
results[:accounts] = perform_accounts_search! if account_searchable? | |
results[:statuses] = perform_statuses_search! if full_text_searchable? | |
results[:hashtags] = perform_hashtags_search! if hashtag_searchable? | |
+ results[:groups] = perform_groups_search! if group_searchable? | |
end | |
end | |
end | |
+ def create_query(q) | |
+ q&.gsub(EMOJI_REGEX, '')&.strip | |
+ end | |
+ | |
private | |
def perform_accounts_search! | |
@@ -37,11 +44,19 @@ | |
@account, | |
limit: @limit, | |
resolve: @resolve, | |
- offset: @offset | |
+ offset: @offset, | |
) | |
end | |
add_method_tracer :perform_accounts_search!, 'SearchService/perform_accounts_search!' | |
+ def perform_groups_search! | |
+ GroupSearchService.new( | |
+ @query, | |
+ limit: @limit, | |
+ offset: @offset, | |
+ ).call | |
+ end | |
+ | |
def perform_statuses_search! | |
functions = [activity_score_function, time_distance_function] | |
@@ -55,13 +70,13 @@ | |
end | |
results = StatusesIndex | |
- .query(function_score: function_score_query) | |
- .filter(range: { id: id_range }) | |
- .filter(PROHIBITED_FILTERS) | |
- .limit(@limit) | |
- .offset(@offset) | |
- .objects | |
- .compact | |
+ .query(function_score: function_score_query) | |
+ .filter(range: { id: id_range }) | |
+ .filter(PROHIBITED_FILTERS) | |
+ .limit(@limit) | |
+ .offset(@offset) | |
+ .objects | |
+ .compact | |
account_ids = results.map(&:account_id) | |
account_domains = results.map(&:account_domain) | |
@@ -97,17 +112,14 @@ | |
end | |
def perform_hashtags_search! | |
- TagSearchService.new.call( | |
- @query, | |
- limit: @limit, | |
- offset: @offset, | |
- exclude_unreviewed: @options[:exclude_unreviewed] | |
- ) | |
+ result = Tag.search_for(@query, @limit, @offset) | |
+ JSON.parse(result || '[]') | |
end | |
+ | |
add_method_tracer :perform_hashtags_search!, 'SearchService/perform_hashtags_search!' | |
def default_results | |
- { accounts: [], hashtags: [], statuses: [] } | |
+ { accounts: [], hashtags: [], statuses: [], groups: [] } | |
end | |
def url_query? | |
@@ -136,6 +148,10 @@ | |
account_search? && !(@query.start_with?('#') || (@query.include?('@') && @query.include?(' '))) | |
end | |
+ def group_searchable? | |
+ group_search? && !(@query.start_with?('#') || (@query.include?('@') && @query.include?(' '))) | |
+ end | |
+ | |
def hashtag_searchable? | |
hashtag_search? && [email protected]?('@') | |
end | |
@@ -150,6 +166,10 @@ | |
def statuses_search? | |
@options[:type].blank? || @options[:type] == 'statuses' | |
+ end | |
+ | |
+ def group_search? | |
+ @options[:type].blank? || @options[:type] == 'groups' | |
end | |
def relations_map_for_account(account, account_ids, domains) | |
Only in truth-new/opensource/app/services: sk_ad_network_service.rb | |
diff -ru truth-old/opensource/app/services/suspend_account_service.rb truth-new/opensource/app/services/suspend_account_service.rb | |
--- truth-old/opensource/app/services/suspend_account_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/suspend_account_service.rb 2024-04-01 14:59:13 | |
@@ -8,10 +8,9 @@ | |
suspend! | |
reject_remote_follows! | |
- distribute_update_actor! | |
unmerge_from_home_timelines! | |
unmerge_from_list_timelines! | |
- privatize_media_attachments! | |
+ Admin::MediaPrivatizationWorker.perform_async(account.id) | |
end | |
private | |
@@ -60,40 +59,6 @@ | |
def unmerge_from_list_timelines! | |
@account.lists_for_local_distribution.find_each do |list| | |
FeedManager.instance.unmerge_from_list(@account, list) | |
- end | |
- end | |
- | |
- def privatize_media_attachments! | |
- attachment_names = MediaAttachment.attachment_definitions.keys | |
- | |
- @account.media_attachments.find_each do |media_attachment| | |
- attachment_names.each do |attachment_name| | |
- attachment = media_attachment.public_send(attachment_name) | |
- styles = [:original] | attachment.styles.keys | |
- | |
- next if attachment.blank? | |
- | |
- styles.each do |style| | |
- case Paperclip::Attachment.default_options[:storage] | |
- when :s3 | |
- begin | |
- attachment.s3_object(style).acl.put(acl: 'private') | |
- rescue Aws::S3::Errors::NoSuchKey | |
- Rails.logger.warn "Tried to change acl on non-existent key #{attachment.s3_object(style).key}" | |
- end | |
- when :fog | |
- # Not supported | |
- when :filesystem | |
- begin | |
- FileUtils.chmod(0o600 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil? | |
- rescue Errno::ENOENT | |
- Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}" | |
- end | |
- end | |
- | |
- CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled | |
- end | |
- end | |
end | |
end | |
Only in truth-old/opensource/app/services: tag_search_service.rb | |
diff -ru truth-old/opensource/app/services/unblock_service.rb truth-new/opensource/app/services/unblock_service.rb | |
--- truth-old/opensource/app/services/unblock_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/unblock_service.rb 2024-04-01 14:59:13 | |
@@ -7,6 +7,7 @@ | |
return unless account.blocking?(target_account) | |
unblock = account.unblock!(target_account) | |
+ InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", account.id, target_account.id, target_account.whale?) | |
create_notification(unblock) if !target_account.local? && target_account.activitypub? | |
unblock | |
end | |
diff -ru truth-old/opensource/app/services/unfavourite_service.rb truth-new/opensource/app/services/unfavourite_service.rb | |
--- truth-old/opensource/app/services/unfavourite_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/unfavourite_service.rb 2024-04-01 14:59:13 | |
@@ -7,6 +7,7 @@ | |
favourite = Favourite.find_by!(account: account, status: status) | |
favourite.destroy! | |
create_notification(favourite) if !status.account.local? && status.account.activitypub? | |
+ InteractionsTracker.new(account.id, status.account_id, :favourite, account.following?(status.account_id), status.group).untrack | |
favourite | |
end | |
diff -ru truth-old/opensource/app/services/unfollow_service.rb truth-new/opensource/app/services/unfollow_service.rb | |
--- truth-old/opensource/app/services/unfollow_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/unfollow_service.rb 2024-04-01 14:59:13 | |
@@ -34,6 +34,8 @@ | |
InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", @source_account.id, @target_account.id, @target_account.whale?) | |
+ remove_follower_interactions(@source_account.id, @target_account.id) | |
+ | |
follow | |
end | |
@@ -63,5 +65,11 @@ | |
def build_reject_json(follow) | |
Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)) | |
+ end | |
+ | |
+ def remove_follower_interactions(source_account_id, target_account_id) | |
+ InteractionsTracker.new(source_account_id, target_account_id, false, true).remove | |
+ redis.del("avatars_carousel_list_#{source_account_id}") | |
+ InvalidateSecondaryCacheService.new.call("InvalidateAvatarsCarouselCacheWorker", source_account_id) | |
end | |
end | |
diff -ru truth-old/opensource/app/services/unsuspend_account_service.rb truth-new/opensource/app/services/unsuspend_account_service.rb | |
--- truth-old/opensource/app/services/unsuspend_account_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/unsuspend_account_service.rb 2024-04-01 14:59:13 | |
@@ -11,8 +11,7 @@ | |
merge_into_home_timelines! | |
merge_into_list_timelines! | |
- publish_media_attachments! | |
- distribute_update_actor! | |
+ Admin::MediaPublicationWorker.perform_async(account.id) | |
end | |
private | |
@@ -56,40 +55,6 @@ | |
def merge_into_list_timelines! | |
@account.lists_for_local_distribution.find_each do |list| | |
FeedManager.instance.merge_into_list(@account, list) | |
- end | |
- end | |
- | |
- def publish_media_attachments! | |
- attachment_names = MediaAttachment.attachment_definitions.keys | |
- | |
- @account.media_attachments.find_each do |media_attachment| | |
- attachment_names.each do |attachment_name| | |
- attachment = media_attachment.public_send(attachment_name) | |
- styles = [:original] | attachment.styles.keys | |
- | |
- next if attachment.blank? | |
- | |
- styles.each do |style| | |
- case Paperclip::Attachment.default_options[:storage] | |
- when :s3 | |
- begin | |
- attachment.s3_object(style).acl.put(acl: Paperclip::Attachment.default_options[:s3_permissions]) | |
- rescue Aws::S3::Errors::NoSuchKey | |
- Rails.logger.warn "Tried to change acl on non-existent key #{attachment.s3_object(style).key}" | |
- end | |
- when :fog | |
- # Not supported | |
- when :filesystem | |
- begin | |
- FileUtils.chmod(0o666 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil? | |
- rescue Errno::ENOENT | |
- Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}" | |
- end | |
- end | |
- | |
- CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled | |
- end | |
- end | |
end | |
end | |
diff -ru truth-old/opensource/app/services/update_account_service.rb truth-new/opensource/app/services/update_account_service.rb | |
--- truth-old/opensource/app/services/update_account_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/update_account_service.rb 2024-04-01 14:59:13 | |
@@ -1,6 +1,4 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/serializers/account_updated_event.rb" | |
- | |
class UpdateAccountService < BaseService | |
def call(account, params, raise_error: false) | |
was_locked = account.locked | |
@@ -8,6 +6,7 @@ | |
params['settings_store'] = params['pleroma_settings_store'] | |
params.delete('pleroma_settings_store') | |
+ params.delete('chats_onboarded') | |
if !params['settings_store'] | |
params.delete('settings_store') | |
@@ -16,12 +15,15 @@ | |
account.send(update_method, params).tap do |ret| | |
next unless ret | |
+ # Allow resetting header images by passing in empty string | |
+ if params[:header] && params[:header].to_s.empty? | |
+ account.header_file_name = nil | |
+ account.header_content_type = nil | |
+ account.header_file_size = nil | |
+ end | |
+ | |
authorize_all_follow_requests(account) if was_locked && !account.locked | |
check_links(account) | |
- Redis.current.publish( | |
- AccountUpdatedEvent::EVENT_KEY, | |
- AccountUpdatedEvent.new(account, fields_changed(account)).serialize | |
- ) | |
process_hashtags(account) | |
end | |
rescue Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e | |
@@ -45,14 +47,5 @@ | |
def process_hashtags(account) | |
account.tags_as_strings = Extractor.extract_hashtags(account.note) | |
- end | |
- | |
- def fields_changed(account) | |
- updatable_fields = %w(display_name avatar_url header_url website bio location) | |
- changed_fields = account.saved_changes.keys | |
- updated_fields = changed_fields.select { |f| updatable_fields.include?(f) } | |
- updated_fields << "avatar_url" if changed_fields.include?("avatar_file_name") | |
- updated_fields << "header_url" if changed_fields.include?("header_file_name") | |
- updated_fields.map(&:upcase) | |
end | |
end | |
Only in truth-new/opensource/app/services: update_feed_service.rb | |
Only in truth-new/opensource/app/services: update_group_service.rb | |
Only in truth-new/opensource/app/services: upload_video_chat_service.rb | |
Only in truth-old/opensource/app/services: upload_video_service.rb | |
Only in truth-new/opensource/app/services: upload_video_status_service.rb | |
Only in truth-new/opensource/app/services: video_encoding_status_service.rb | |
Only in truth-new/opensource/app/services: video_preview_service.rb | |
diff -ru truth-old/opensource/app/services/vote_service.rb truth-new/opensource/app/services/vote_service.rb | |
--- truth-old/opensource/app/services/vote_service.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/services/vote_service.rb 2024-04-12 09:09:08 | |
@@ -16,11 +16,11 @@ | |
RedisLock.acquire(lock_options) do |lock| | |
if lock.acquired? | |
- already_voted = @poll.votes.where(account: @account).exists? | |
+ already_voted = @poll.voted?(@account) | |
ApplicationRecord.transaction do | |
@choices.each do |choice| | |
- @votes << @poll.votes.create!(account: @account, choice: Integer(choice)) | |
+ @votes << @poll.votes.create!(account: @account, option_number: Integer(choice)) | |
end | |
end | |
else | |
@@ -28,52 +28,18 @@ | |
end | |
end | |
- increment_voters_count! unless already_voted | |
- | |
ActivityTracker.increment('activity:interactions') | |
- | |
- if @poll.account.local? | |
- distribute_poll! | |
- else | |
- deliver_votes! | |
- queue_final_poll_check! | |
- end | |
end | |
private | |
- def distribute_poll! | |
- return if @poll.hide_totals? | |
- ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, @poll.status.id) | |
- end | |
- | |
def queue_final_poll_check! | |
return unless @poll.expires? | |
- PollExpirationNotifyWorker.perform_at(@poll.expires_at + 5.minutes, @poll.id) | |
+ #PollExpirationNotifyWorker.perform_at(@poll.expires_at + 5.minutes, @poll.id) | |
end | |
- def deliver_votes! | |
- @votes.each do |vote| | |
- ActivityPub::DeliveryWorker.perform_async( | |
- build_json(vote), | |
- @account.id, | |
- @poll.account.inbox_url | |
- ) | |
- end | |
- end | |
- | |
def build_json(vote) | |
Oj.dump(serialize_payload(vote, ActivityPub::VoteSerializer)) | |
- end | |
- | |
- def increment_voters_count! | |
- unless @poll.voters_count.nil? | |
- @poll.voters_count = @poll.voters_count + 1 | |
- @poll.save | |
- end | |
- rescue ActiveRecord::StaleObjectError | |
- @poll.reload | |
- retry | |
end | |
def lock_options | |
Only in truth-new/opensource/app/validators: account_feed_validator.rb | |
Only in truth-new/opensource/app/validators: base_email_validator.rb | |
Only in truth-new/opensource/app/validators: feed_validator.rb | |
diff -ru truth-old/opensource/app/validators/follow_limit_validator.rb truth-new/opensource/app/validators/follow_limit_validator.rb | |
--- truth-old/opensource/app/validators/follow_limit_validator.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/validators/follow_limit_validator.rb 2024-04-01 14:59:13 | |
@@ -9,7 +9,7 @@ | |
if limit_reached?(follow.account) | |
follow.errors.add(:base, I18n.t('users.follow_limit_reached', limit: self.class.limit_for_account(follow.account))) | |
- follow.errors.add(:errorCode, 'followLimitReached') | |
+ follow.errors.add(:error_code, 'followLimitReached') | |
end | |
end | |
Only in truth-new/opensource/app/validators: group_status_pin_validator.rb | |
Only in truth-new/opensource/app/validators: max_group_admin_validator.rb | |
Only in truth-new/opensource/app/validators: max_group_tag_validator.rb | |
diff -ru truth-old/opensource/app/validators/note_length_validator.rb truth-new/opensource/app/validators/note_length_validator.rb | |
--- truth-old/opensource/app/validators/note_length_validator.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/validators/note_length_validator.rb 2024-04-01 14:59:13 | |
@@ -14,9 +14,8 @@ | |
def countable_text(value) | |
return '' if value.nil? | |
- value.dup.tap do |new_text| | |
- new_text.gsub!(FetchLinkCardService::URL_PATTERN, StatusLengthValidator::URL_PLACEHOLDER) | |
- new_text.gsub!(Account::MENTION_RE, '@\2') | |
- end | |
+ value | |
+ .gsub(FetchLinkCardService::URL_PATTERN) { |url| URLPlaceholder.generate(url) } | |
+ .gsub(Account::MENTION_RE, '@\2') | |
end | |
end | |
diff -ru truth-old/opensource/app/validators/poll_validator.rb truth-new/opensource/app/validators/poll_validator.rb | |
--- truth-old/opensource/app/validators/poll_validator.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/validators/poll_validator.rb 2024-04-01 14:59:13 | |
@@ -1,9 +1,9 @@ | |
# frozen_string_literal: true | |
class PollValidator < ActiveModel::Validator | |
- MAX_OPTIONS = 4 | |
+ MAX_OPTIONS = 6 | |
MAX_OPTION_CHARS = 50 | |
- MAX_EXPIRATION = 1.month.freeze | |
+ MAX_EXPIRATION = 7.days.freeze | |
MIN_EXPIRATION = 5.minutes.freeze | |
def validate(poll) | |
@@ -11,8 +11,7 @@ | |
poll.errors.add(:options, I18n.t('polls.errors.too_few_options')) unless poll.options.size > 1 | |
poll.errors.add(:options, I18n.t('polls.errors.too_many_options', max: MAX_OPTIONS)) if poll.options.size > MAX_OPTIONS | |
- poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.mb_chars.grapheme_length > MAX_OPTION_CHARS } | |
- poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq.size == poll.options.size | |
+ poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq(&:text).size == poll.options.map(&:text).size | |
poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at.nil? || poll.expires_at - current_time > MAX_EXPIRATION | |
poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && (poll.expires_at - current_time).ceil < MIN_EXPIRATION | |
end | |
Only in truth-new/opensource/app/validators: status_group_validator.rb | |
diff -ru truth-old/opensource/app/validators/status_length_validator.rb truth-new/opensource/app/validators/status_length_validator.rb | |
--- truth-old/opensource/app/validators/status_length_validator.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/validators/status_length_validator.rb 2024-04-01 14:59:13 | |
@@ -1,9 +1,7 @@ | |
# frozen_string_literal: true | |
class StatusLengthValidator < ActiveModel::Validator | |
- MAX_CHARS = 500 | |
- URL_PLACEHOLDER_CHARS = 23 | |
- URL_PLACEHOLDER = "\1#{'x' * URL_PLACEHOLDER_CHARS}" | |
+ MAX_CHARS = 1000 | |
def validate(status) | |
return unless status.local? && !status.reblog? | |
@@ -29,9 +27,8 @@ | |
def countable_text | |
return '' if @status.text.nil? | |
- @status.text.dup.tap do |new_text| | |
- new_text.gsub!(FetchLinkCardService::URL_PATTERN, URL_PLACEHOLDER) | |
- new_text.gsub!(Account::MENTION_RE, '@\2') | |
- end | |
+ @status.text | |
+ .gsub(FetchLinkCardService::URL_PATTERN) { |url| URLPlaceholder.generate(url) } | |
+ .gsub(Account::MENTION_RE, '@\2') | |
end | |
end | |
diff -ru truth-old/opensource/app/validators/status_pin_validator.rb truth-new/opensource/app/validators/status_pin_validator.rb | |
--- truth-old/opensource/app/validators/status_pin_validator.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/validators/status_pin_validator.rb 2024-04-01 14:59:13 | |
@@ -5,6 +5,8 @@ | |
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? | |
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id | |
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) | |
- pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local? | |
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.direct')) if pin.status.direct_visibility? | |
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.group')) if pin.status.group_visibility? | |
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.profile_pins.size > 4 && pin.account.local? | |
end | |
end | |
Only in truth-new/opensource/app/validators: unique_password_validator.rb | |
Only in truth-new/opensource/app/validators: valid_group_name_validator.rb | |
diff -ru truth-old/opensource/app/validators/vote_validator.rb truth-new/opensource/app/validators/vote_validator.rb | |
--- truth-old/opensource/app/validators/vote_validator.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/validators/vote_validator.rb 2024-04-01 14:59:13 | |
@@ -6,7 +6,7 @@ | |
vote.errors.add(:base, I18n.t('polls.errors.invalid_choice')) if invalid_choice?(vote) | |
- if vote.poll.multiple? && vote.poll.votes.where(account: vote.account, choice: vote.choice).exists? | |
+ if vote.poll.multiple? && vote.poll.votes.where(account: vote.account, option_number: vote.option_number).exists? | |
vote.errors.add(:base, I18n.t('polls.errors.already_voted')) | |
elsif !vote.poll.multiple? && vote.poll.votes.where(account: vote.account).exists? | |
vote.errors.add(:base, I18n.t('polls.errors.already_voted')) | |
@@ -16,6 +16,6 @@ | |
private | |
def invalid_choice?(vote) | |
- vote.choice.negative? || vote.choice >= vote.poll.options.size | |
+ vote.option_number.negative? || vote.option_number >= vote.poll.options.size | |
end | |
end | |
diff -ru truth-old/opensource/app/views/admin/reports/_status.html.haml truth-new/opensource/app/views/admin/reports/_status.html.haml | |
--- truth-old/opensource/app/views/admin/reports/_status.html.haml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/admin/reports/_status.html.haml 2024-04-01 14:59:13 | |
@@ -1,3 +1,5 @@ | |
+- show_author ||= false | |
+ | |
.batch-table__row | |
%label.batch-table__row__select.batch-checkbox | |
= f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id | |
@@ -33,7 +35,14 @@ | |
= t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account)) | |
- else | |
= fa_visibility_icon(status) | |
- = t("statuses.visibilities.#{status.visibility}") | |
+ - if status.group_visibility? | |
+ - if status.group.present? | |
+ = link_to admin_group_path(status.group.id), class: 'detailed-status__group' do | |
+ = t("statuses.visibilities.#{status.visibility}") | |
+ - else | |
+ = t("statuses.visibilities.#{status.visibility}") | |
+ - else | |
+ = t("statuses.visibilities.#{status.visibility}") | |
- if status.proper.sensitive? | |
· | |
= fa_icon('eye-slash fw') | |
Only in truth-old/opensource/app/views/admin: trending_truths | |
Only in truth-new/opensource/app/views: api | |
Only in truth-new/opensource/app/views/application: _group_card.html.haml | |
diff -ru truth-old/opensource/app/views/layouts/application.html.haml truth-new/opensource/app/views/layouts/application.html.haml | |
--- truth-old/opensource/app/views/layouts/application.html.haml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/layouts/application.html.haml 2024-04-01 14:59:13 | |
@@ -21,7 +21,6 @@ | |
%link{ rel: 'apple-touch-icon', sizes: '180x180', href: '/icons/icon-180x180.png' }/ | |
%link{ rel: 'apple-touch-icon', sizes: '192x192', href: '/icons/icon-192x192.png' }/ | |
%link{ rel: 'apple-touch-icon', sizes: '512x512', href: '/icons/icon-512x512.png' }/ | |
- %link{ rel: 'mask-icon', href: '/mask-icon.svg', color: '#2B90D9' }/ | |
%link{ rel: 'manifest', href: '/manifest.json' }/ | |
%meta{ name: 'msapplication-config', content: '/browserconfig.xml' }/ | |
%meta{ name: 'theme-color', content: '#282c37' }/ | |
Only in truth-new/opensource/app/views/layouts: docs.html.haml | |
Only in truth-new/opensource/app/views: link | |
diff -ru truth-old/opensource/app/views/notification_mailer/user_approved.html.erb truth-new/opensource/app/views/notification_mailer/user_approved.html.erb | |
--- truth-old/opensource/app/views/notification_mailer/user_approved.html.erb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/notification_mailer/user_approved.html.erb 2024-04-01 14:59:13 | |
@@ -40,7 +40,24 @@ | |
</p> | |
<p class="text-gray-600 mb-0"> | |
- <%= t 'notification_mailer.user_approved.extra_step' %> | |
+ <%= t( | |
+ 'notification_mailer.user_approved.extra_step', | |
+ help_center_link: link_to( | |
+ t('notification_mailer.user_approved.help_center'), | |
+ 'https://help.truthsocial.com', | |
+ class: 'text-primary-600', | |
+ target: '_blank' | |
+ ), | |
+ mailto_support: link_to( | |
+ '[email protected]', | |
+ 'mailto:[email protected]', | |
+ class: 'text-primary-600', | |
+ ) | |
+ ).html_safe %> | |
+ </p> | |
+ | |
+ <p class="text-gray-600 mb-0"> | |
+ <%= t("notification_mailer.user_approved.signoff") %> | |
</p> | |
<table class="w-full" role="separator" cellpadding="0" cellspacing="0"> | |
diff -ru truth-old/opensource/app/views/notification_mailer/user_approved.text.erb truth-new/opensource/app/views/notification_mailer/user_approved.text.erb | |
--- truth-old/opensource/app/views/notification_mailer/user_approved.text.erb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/notification_mailer/user_approved.text.erb 2024-04-01 14:59:13 | |
@@ -7,7 +7,15 @@ | |
<%= t 'notification_mailer.user_approved.explanation' %> | |
-<%= t 'notification_mailer.user_approved.extra_step' %> | |
+<%= t( | |
+ 'notification_mailer.user_approved.extra_step', | |
+ help_center_link: t('notification_mailer.user_approved.help_center'), | |
+ mailto_support: '[email protected]' | |
+) %> | |
+ | |
+=> https://help.truthsocial.com | |
+ | |
+<%= t("notification_mailer.user_approved.signoff") %> | |
--- | |
diff -ru truth-old/opensource/app/views/statuses/_simple_status.html.haml truth-new/opensource/app/views/statuses/_simple_status.html.haml | |
--- truth-old/opensource/app/views/statuses/_simple_status.html.haml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/statuses/_simple_status.html.haml 2024-04-01 14:59:13 | |
@@ -58,6 +58,8 @@ | |
= link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do | |
- if status.distributable? | |
= fa_icon 'retweet fw' | |
+ - elsif status.group_visibility? | |
+ = fa_icon 'users fw' | |
- elsif status.private_visibility? || status.limited_visibility? | |
= fa_icon 'lock fw' | |
- else | |
diff -ru truth-old/opensource/app/views/user_mailer/status_removed.html.erb truth-new/opensource/app/views/user_mailer/status_removed.html.erb | |
--- truth-old/opensource/app/views/user_mailer/status_removed.html.erb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/user_mailer/status_removed.html.erb 2024-04-01 14:59:13 | |
@@ -54,7 +54,7 @@ | |
<table cellpadding="0" cellspacing="0" border="0" role="presentation"> | |
<tr> | |
<td class="leading-full"> | |
- <a href="https://help.truthsocial.com/community/guidelines" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700"> | |
+ <a href="https://help.truthsocial.com/community-guidelines-page" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700"> | |
<!--[if mso]><i style="letter-spacing: 24px; mso-font-width: -100%; mso-text-raise:30px;"> </i><![endif]--> | |
<span style="mso-text-raise: 16px;"> | |
<%= t 'user_mailer.warning.review_server_policies' %> | |
diff -ru truth-old/opensource/app/views/user_mailer/warning.html.erb truth-new/opensource/app/views/user_mailer/warning.html.erb | |
--- truth-old/opensource/app/views/user_mailer/warning.html.erb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/views/user_mailer/warning.html.erb 2024-04-01 14:59:13 | |
@@ -74,7 +74,7 @@ | |
<table cellpadding="0" cellspacing="0" border="0" role="presentation"> | |
<tr> | |
<td class="leading-full"> | |
- <a href="https://help.truthsocial.com/community/guidelines" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700"> | |
+ <a href="https://help.truthsocial.com/community-guidelines-page" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700"> | |
<!--[if mso]><i style="letter-spacing: 24px; mso-font-width: -100%; mso-text-raise:30px;"> </i><![endif]--> | |
<span style="mso-text-raise: 16px;"> | |
<%= t 'user_mailer.warning.review_server_policies' %> | |
diff -ru truth-old/opensource/app/workers/account_deletion_worker.rb truth-new/opensource/app/workers/account_deletion_worker.rb | |
--- truth-old/opensource/app/workers/account_deletion_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/account_deletion_worker.rb 2024-04-01 14:59:13 | |
@@ -5,10 +5,18 @@ | |
sidekiq_options queue: 'pull', lock: :until_executed | |
- def perform(account_id, options = {}) | |
+ def perform(account_id, deleted_by_id, options = {}) | |
reserve_username = options.with_indifferent_access.fetch(:reserve_username, true) | |
skip_activitypub = options.with_indifferent_access.fetch(:skip_activitypub, false) | |
- DeleteAccountService.new.call(Account.find(account_id), reserve_username: reserve_username, skip_activitypub: skip_activitypub, reserve_email: false) | |
+ deletion_type = options.with_indifferent_access.fetch(:deletion_type, 'unknown') | |
+ DeleteAccountService.new.call( | |
+ Account.find(account_id), | |
+ deleted_by_id, | |
+ deletion_type: deletion_type, | |
+ reserve_email: false, | |
+ reserve_username: reserve_username, | |
+ skip_activitypub: skip_activitypub, | |
+ ) | |
rescue ActiveRecord::RecordNotFound | |
true | |
end | |
diff -ru truth-old/opensource/app/workers/activitypub/delivery_worker.rb truth-new/opensource/app/workers/activitypub/delivery_worker.rb | |
--- truth-old/opensource/app/workers/activitypub/delivery_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/activitypub/delivery_worker.rb 2024-04-01 14:59:13 | |
@@ -39,12 +39,7 @@ | |
Request.new(:post, @inbox_url, body: @json, http_client: http_client).tap do |request| | |
request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with]) | |
request.add_headers(HEADERS) | |
- request.add_headers({ 'Collection-Synchronization' => synchronization_header }) if ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] != 'true' && @options[:synchronize_followers] | |
end | |
- end | |
- | |
- def synchronization_header | |
- "collectionId=\"#{account_followers_url(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(inbox_url_prefix)}\", url=\"#{account_followers_synchronization_url(@source_account)}\"" | |
end | |
def inbox_url_prefix | |
Only in truth-old/opensource/app/workers/activitypub: processing_worker.rb | |
diff -ru truth-old/opensource/app/workers/admin/account_deletion_worker.rb truth-new/opensource/app/workers/admin/account_deletion_worker.rb | |
--- truth-old/opensource/app/workers/admin/account_deletion_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/admin/account_deletion_worker.rb 2024-04-01 14:59:13 | |
@@ -5,8 +5,15 @@ | |
sidekiq_options queue: 'pull' | |
- def perform(account_id) | |
- DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true) | |
+ def perform(account_id, deleted_by_id) | |
+ DeleteAccountService.new.call( | |
+ Account.find(account_id), | |
+ deleted_by_id, | |
+ deletion_type: 'worker_admin_account_deletion', | |
+ reserve_username: true, | |
+ reserve_email: true, | |
+ skip_activitypub: true, | |
+ ) | |
rescue ActiveRecord::RecordNotFound | |
true | |
end | |
Only in truth-new/opensource/app/workers/admin: ad_worker.rb | |
Only in truth-new/opensource/app/workers/admin: group_deletion_worker.rb | |
Only in truth-new/opensource/app/workers/admin: media_privatization_worker.rb | |
Only in truth-new/opensource/app/workers/admin: media_publication_worker.rb | |
Only in truth-new/opensource/app/workers/concerns: image_migration.rb | |
Only in truth-new/opensource/app/workers/concerns: track_ad_impressions_concern.rb | |
Only in truth-new/opensource/app/workers: disabled_user_refollow_worker.rb | |
Only in truth-new/opensource/app/workers: disabled_user_unfollow_worker.rb | |
diff -ru truth-old/opensource/app/workers/distribution_worker.rb truth-new/opensource/app/workers/distribution_worker.rb | |
--- truth-old/opensource/app/workers/distribution_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/distribution_worker.rb 2024-04-05 09:07:34 | |
@@ -25,13 +25,8 @@ | |
end | |
def send_to_bailey(status_id) | |
- if @status.account.silenced? || [email protected]_visibility? || @status.reblog? | |
- rendered = nil | |
- else | |
- rendered = InlineRenderer.render(@status, nil, :status) | |
- rendered = Oj.dump(event: :update, payload: rendered) | |
- end | |
- Redis.current.lpush('elixir:distribution', Oj.dump(status_id: status_id, rendered: rendered)) | |
- Rails.logger.debug("bailey_debug: sending #{rendered.nil? ? 'nil' : 'value'} for rendered for status #{status_id}") | |
+ QueueManager.enqueue_status_for_author_distribution(status_id) | |
+ QueueManager.enqueue_status_for_follower_distribution(status_id) | |
+ Rails.logger.debug("bailey_debug: enqueuing status #{status_id}") | |
end | |
end | |
Only in truth-new/opensource/app/workers: group_acceptance_notify_worker.rb | |
Only in truth-new/opensource/app/workers: group_deletion_notify_worker.rb | |
Only in truth-new/opensource/app/workers: group_deletion_worker.rb | |
Only in truth-new/opensource/app/workers: group_request_notify_worker.rb | |
Only in truth-new/opensource/app/workers: group_role_change_notify_worker.rb | |
Only in truth-new/opensource/app/workers: images | |
Only in truth-new/opensource/app/workers: inspect_link_worker.rb | |
Only in truth-new/opensource/app/workers: invalidate_account_statuses_worker.rb | |
Only in truth-new/opensource/app/workers: invalidate_ads_accounts_cache_worker.rb | |
Only in truth-new/opensource/app/workers: invalidate_ads_accounts_worker.rb | |
Only in truth-new/opensource/app/workers: invalidate_avatars_carousel_cache_worker.rb | |
Only in truth-new/opensource/app/workers: invalidate_descendants_cache_worker.rb | |
diff -ru truth-old/opensource/app/workers/invalidate_follow_cache_worker.rb truth-new/opensource/app/workers/invalidate_follow_cache_worker.rb | |
--- truth-old/opensource/app/workers/invalidate_follow_cache_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/invalidate_follow_cache_worker.rb 2024-04-01 14:59:13 | |
@@ -5,8 +5,8 @@ | |
include Redisable | |
def perform(source_account_id, target_account_id, whale) | |
- Rails.cache.delete("relationship:#{source_account_id}:#{target_account_id}") | |
- Rails.cache.delete("relationship:#{target_account_id}:#{source_account_id}") | |
+ Rails.cache.delete("relationship/#{source_account_id}/#{target_account_id}") | |
+ Rails.cache.delete("relationship/#{target_account_id}/#{source_account_id}") | |
redis.del("whale:following:#{source_account_id}") if whale | |
end | |
end | |
Only in truth-new/opensource/app/workers: invalidate_group_relationship_cache_worker.rb | |
Only in truth-new/opensource/app/workers: ios_device_check | |
diff -ru truth-old/opensource/app/workers/link_crawl_worker.rb truth-new/opensource/app/workers/link_crawl_worker.rb | |
--- truth-old/opensource/app/workers/link_crawl_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/link_crawl_worker.rb 2024-04-01 14:59:13 | |
@@ -5,8 +5,8 @@ | |
sidekiq_options queue: 'pull', retry: 5 | |
- def perform(status_id, url = nil) | |
- FetchLinkCardService.new.call(Status.find(status_id), url) | |
+ def perform(status_id, url = nil, domain = nil) | |
+ FetchLinkCardService.new.call(Status.find(status_id), url, domain) | |
rescue ActiveRecord::RecordNotFound | |
true | |
end | |
diff -ru truth-old/opensource/app/workers/local_notification_worker.rb truth-new/opensource/app/workers/local_notification_worker.rb | |
--- truth-old/opensource/app/workers/local_notification_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/local_notification_worker.rb 2024-04-01 14:59:13 | |
@@ -3,6 +3,8 @@ | |
class LocalNotificationWorker | |
include Sidekiq::Worker | |
+ sidekiq_options retry: 1 | |
+ | |
def perform(receiver_account_id, activity_id = nil, activity_class_name = nil, type = nil) | |
if activity_id.nil? && activity_class_name.nil? | |
activity = Mention.find(receiver_account_id) | |
@@ -13,7 +15,5 @@ | |
end | |
NotifyService.new.call(receiver, type || activity_class_name.underscore, activity) | |
- rescue ActiveRecord::RecordNotFound | |
- true | |
end | |
end | |
Only in truth-new/opensource/app/workers/mobile: channel_notification_queueing_worker.rb | |
Only in truth-new/opensource/app/workers/mobile: channel_notification_worker.rb | |
Only in truth-new/opensource/app/workers/mobile: marketing_notification_queueing_worker.rb | |
diff -ru truth-old/opensource/app/workers/mobile/marketing_notification_worker.rb truth-new/opensource/app/workers/mobile/marketing_notification_worker.rb | |
--- truth-old/opensource/app/workers/mobile/marketing_notification_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/mobile/marketing_notification_worker.rb 2024-04-01 14:59:13 | |
@@ -7,40 +7,59 @@ | |
TTL = 8.hours.to_s | |
URGENCY = 'normal' | |
+ IOS_PLATFORM = 1 | |
+ ANDROID_PLATFORM = 2 | |
- def self.queue_notifications(user_ids:, message:, url:) | |
- self.push_bulk(user_ids) do |user_id| | |
- [user_id, message, url] | |
+ def self.queue_notifications(message:, url:, mark_id: nil) | |
+ tester_platform = IOS_PLATFORM | |
+ tester_user_ids = if mark_id.present? | |
+ ENV.fetch('MARK_ID_PUSH_NOTIFICATIONS_USER_IDS', '') | |
+ .split(',') | |
+ .map(&:to_i) | |
+ .reject(&:zero?) | |
+ end | |
+ | |
+ if tester_user_ids.present? | |
+ tester_tokens = MarketingPushSubscription | |
+ .select(:device_token) | |
+ .distinct | |
+ .joins('inner join users on users.id = web_push_subscriptions.user_id') | |
+ .where(users: { approved: true, disabled: false, id: tester_user_ids }, platform: tester_platform) | |
+ .where.not(device_token: nil) | |
+ .to_a | |
+ .pluck(:device_token) | |
+ perform_async(tester_tokens, message, url, tester_platform, mark_id) | |
end | |
+ | |
+ [IOS_PLATFORM, ANDROID_PLATFORM].each do |platform| | |
+ tokens = MarketingPushSubscription | |
+ .select(:device_token) | |
+ .distinct | |
+ .joins('inner join users on users.id = web_push_subscriptions.user_id') | |
+ .where(users: { approved: true, disabled: false }, platform: platform) | |
+ .where.not(device_token: nil) | |
+ tokens = tokens.where.not(users: { id: tester_user_ids }) if platform == tester_platform && tester_user_ids.present? | |
+ tokens.find_in_batches(batch_size: 2_000) do |batch| | |
+ perform_async(batch.pluck(:device_token), message, url, platform, nil) | |
+ end | |
+ end | |
end | |
- def perform(user_id, message, url) | |
- subscriptions = Web::PushSubscription.where(user_id: user_id) | |
- endpoint = ENV.fetch('MOBILE_NOTIFICATION_ENDPOINT') | |
+ def perform(tokens, message, url, platform, mark_id) | |
+ endpoint = ENV.fetch('MOBILE_NOTIFICATION_ENDPOINT') | |
+ extend = [{ key: 'truthLink', val: url }, { key: 'category', val: 'marketing' }] | |
+ extend.push({ key: 'markId', val: mark_id }) if mark_id.present? | |
+ msg = { token: tokens, category: 'invite', platform: platform, message: message, extend: extend } | |
+ msg[:mutable_content] = true if platform == IOS_PLATFORM | |
+ body = { 'notifications' => [msg] }.to_json | |
- # return unless endpoint.present? && subscriptions.any? && message && url | |
+ return unless endpoint.present? && tokens.any? && message && url | |
- subscriptions.each do |subscription| | |
- payload = push_notification_json(subscription.device_token, message, url) | |
- request_pool.with(Addressable::URI.parse(endpoint).normalized_site) do |http_client| | |
- request = Request.new(:post, endpoint, body: payload, http_client: http_client) | |
- | |
- request.add_headers( | |
- 'Content-Type' => 'application/octet-stream' | |
- ) | |
- | |
- request.perform do |response| | |
- # If the server responds with an error in the 4xx range | |
- # that isn't about rate-limiting or timeouts, we can | |
- # assume that the subscription is invalid or expired | |
- # and must be removed | |
- | |
- if (400..499).cover?(response.code) && ![408, 429].include?(response.code) | |
- subscription.destroy! | |
- elsif !(200...300).cover?(response.code) | |
- raise Mastodon::UnexpectedResponseError, response | |
- end | |
- end | |
+ request_pool.with(Addressable::URI.parse(endpoint).normalized_site) do |http_client| | |
+ request = Request.new(:post, endpoint, body: body, http_client: http_client) | |
+ request.add_headers('Content-Type' => 'application/octet-stream') | |
+ request.perform do |response| | |
+ raise Mastodon::UnexpectedResponseError, response unless (200...300).cover?(response.code) | |
end | |
end | |
rescue ActiveRecord::RecordNotFound | |
@@ -49,11 +68,12 @@ | |
private | |
- def push_notification_json(token, message, url) | |
- { 'notifications' => [{token: [token], category: 'invite', platform: 1, message: message, extend: [{key: 'truthLink', val: url}]}] }.to_json | |
- end | |
- | |
def request_pool | |
RequestPool.current | |
end | |
+end | |
+ | |
+class MarketingPushSubscription < ApplicationRecord | |
+ self.table_name = 'web_push_subscriptions' | |
+ self.primary_key = :device_token | |
end | |
diff -ru truth-old/opensource/app/workers/mobile/push_notification_worker.rb truth-new/opensource/app/workers/mobile/push_notification_worker.rb | |
--- truth-old/opensource/app/workers/mobile/push_notification_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/mobile/push_notification_worker.rb 2024-04-12 09:09:08 | |
@@ -18,6 +18,7 @@ | |
return unless @endpoint.present? && @notification.activity.present? && @subscription.pushable?(@notification) | |
payload = push_notification_json | |
+ | |
request_pool.with(Addressable::URI.parse(@endpoint).normalized_site) do |http_client| | |
request = Request.new(:post, @endpoint, body: payload, http_client: http_client) | |
@@ -31,9 +32,7 @@ | |
# assume that the subscription is invalid or expired | |
# and must be removed | |
- if (400..499).cover?(response.code) && ![408, 429].include?(response.code) | |
- @subscription.destroy! | |
- elsif !(200...300).cover?(response.code) | |
+ if !(200...300).cover?(response.code) | |
raise Mastodon::UnexpectedResponseError, response | |
end | |
end | |
diff -ru truth-old/opensource/app/workers/poll_expiration_notify_worker.rb truth-new/opensource/app/workers/poll_expiration_notify_worker.rb | |
--- truth-old/opensource/app/workers/poll_expiration_notify_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/poll_expiration_notify_worker.rb 2024-04-01 15:02:56 | |
@@ -10,12 +10,11 @@ | |
# Notify poll owner and remote voters | |
if poll.local? | |
- ActivityPub::DistributePollUpdateWorker.perform_async(poll.status.id) | |
NotifyService.new.call(poll.account, :poll, poll) | |
end | |
# Notify local voters | |
- poll.votes.includes(:account).group(:account_id).select(:account_id).map(&:account).select(&:local?).each do |account| | |
+ PollVote.where(poll_id: poll_id).includes(:account).group(:account_id).select(:account_id).map(&:account).select(&:local?).each do |account| | |
NotifyService.new.call(account, :poll, poll) | |
end | |
rescue ActiveRecord::RecordNotFound | |
Only in truth-new/opensource/app/workers: process_mention_notifications_worker.rb | |
Only in truth-new/opensource/app/workers: push_chat_message_worker.rb | |
Only in truth-new/opensource/app/workers: reblog_removal_worker.rb | |
Only in truth-new/opensource/app/workers/scheduler: device_verification_cleanup_worker.rb | |
diff -ru truth-old/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb truth-new/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb | |
--- truth-old/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb 2024-04-01 14:59:13 | |
@@ -6,7 +6,7 @@ | |
sidekiq_options retry: 0 | |
def perform | |
- Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all | |
+ OauthAccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all | |
Doorkeeper::AccessGrant.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all | |
SystemKey.expired.delete_all | |
end | |
Only in truth-old/opensource/app/workers/scheduler: pghero_scheduler.rb | |
Only in truth-new/opensource/app/workers/scheduler: refresh_receipt_scheduler.rb | |
Only in truth-new/opensource/app/workers/scheduler: tv_create_program_records_scheduler.rb | |
Only in truth-new/opensource/app/workers/scheduler: tv_refetch_channels_list_scheduler.rb | |
diff -ru truth-old/opensource/app/workers/scheduler/user_cleanup_scheduler.rb truth-new/opensource/app/workers/scheduler/user_cleanup_scheduler.rb | |
--- truth-old/opensource/app/workers/scheduler/user_cleanup_scheduler.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/scheduler/user_cleanup_scheduler.rb 2024-04-01 14:59:13 | |
@@ -8,6 +8,7 @@ | |
def perform | |
clean_unconfirmed_accounts! | |
clean_suspended_accounts! | |
+ # clean_suspended_groups! # Revisit once we have a deletion strategy | |
end | |
private | |
@@ -20,8 +21,14 @@ | |
end | |
def clean_suspended_accounts! | |
- AccountDeletionRequest.where('created_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).reorder(nil).find_each do |deletion_request| | |
- Admin::AccountDeletionWorker.perform_async(deletion_request.account_id) | |
+ AccountDeletionRequest.where('created_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).where(Account.where('account_deletion_requests.account_id = accounts.id and accounts.suspended_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).arel.exists).reorder(nil).find_each do |deletion_request| | |
+ Admin::AccountDeletionWorker.perform_async(deletion_request.account_id, -99) | |
end | |
end | |
+ | |
+ # def clean_suspended_groups! | |
+ # GroupDeletionRequest.where('created_at <= ?', GroupDeletionRequest::DELAY_TO_DELETION.ago).reorder(nil).find_each do |deletion_request| | |
+ # Admin::GroupDeletionWorker.perform_async(deletion_request.group_id) | |
+ # end | |
+ # end | |
end | |
Only in truth-new/opensource/app/workers: status_moderated_event_worker.rb | |
Only in truth-new/opensource/app/workers: sync_avatars_seen_accounts_worker.rb | |
Only in truth-new/opensource/app/workers: sync_groups_seen_worker.rb | |
Only in truth-new/opensource/app/workers: sync_seen_feeds_worker.rb | |
Only in truth-new/opensource/app/workers: track_revcontent_ad_impressions_worker.rb | |
Only in truth-new/opensource/app/workers: track_rumble_ad_impressions_worker.rb | |
Only in truth-new/opensource/app/workers: tv_accounts_create_worker.rb | |
Only in truth-new/opensource/app/workers: tv_accounts_login_worker.rb | |
Only in truth-new/opensource/app/workers: tv_create_tv_program_status_worker.rb | |
Only in truth-new/opensource/app/workers: tv_program_reminder_notification_worker.rb | |
Only in truth-new/opensource/app/workers: upload_video_chat_worker.rb | |
Only in truth-new/opensource/app/workers: upload_video_status_worker.rb | |
Only in truth-new/opensource/app/workers: video_polling_worker.rb | |
Only in truth-new/opensource/app/workers: video_preview_worker.rb | |
Only in truth-old/opensource/app/workers: video_upload_worker.rb | |
diff -ru truth-old/opensource/app/workers/web/push_notification_worker.rb truth-new/opensource/app/workers/web/push_notification_worker.rb | |
--- truth-old/opensource/app/workers/web/push_notification_worker.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app/workers/web/push_notification_worker.rb 2024-04-01 14:59:13 | |
@@ -3,7 +3,7 @@ | |
class Web::PushNotificationWorker | |
include Sidekiq::Worker | |
- sidekiq_options queue: 'push', retry: 5 | |
+ sidekiq_options queue: 'push', retry: false | |
TTL = 48.hours.to_s | |
URGENCY = 'normal' | |
diff -ru truth-old/opensource/app.json truth-new/opensource/app.json | |
--- truth-old/opensource/app.json 2022-06-08 09:15:38 | |
+++ truth-new/opensource/app.json 2024-04-01 14:59:13 | |
@@ -93,10 +93,16 @@ | |
} | |
], | |
"scripts": { | |
- "postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed" | |
+ "postdeploy": "bundle exec rails db:schema:load && bundle exec rails db:seed" | |
}, | |
"addons": [ | |
"heroku-postgresql", | |
"heroku-redis" | |
- ] | |
+ ], | |
+ "dokku": { | |
+ "plugins": [ | |
+ "postgres", | |
+ "redis" | |
+ ] | |
+ } | |
} | |
Only in truth-new/opensource/bin: ci-pre-deploy | |
Only in truth-new/opensource/bin: rmq_status_moderated_event_worker | |
Only in truth-new/opensource/bin: sql-injection-check | |
diff -ru truth-old/opensource/brakeman-output.json truth-new/opensource/brakeman-output.json | |
--- truth-old/opensource/brakeman-output.json 2022-06-08 09:18:29 | |
+++ truth-new/opensource/brakeman-output.json 2024-04-01 14:59:13 | |
@@ -1,11 +1,11 @@ | |
{ | |
"scan_info": { | |
- "app_path": "/builds/tmediatech/social-v1", | |
+ "app_path": "/Users/markmorales/Code/social-v1_groups", | |
"rails_version": "6.1.3.2", | |
"security_warnings": 0, | |
- "start_time": "2022-06-08 13:18:11 +0000", | |
- "end_time": "2022-06-08 13:18:29 +0000", | |
- "duration": 18.243194352, | |
+ "start_time": "2023-07-17 16:03:50 -0500", | |
+ "end_time": "2023-07-17 16:04:07 -0500", | |
+ "duration": 17.111335, | |
"checks_performed": [ | |
"BasicAuth", | |
"BasicAuthTimingAttack", | |
@@ -80,9 +80,9 @@ | |
"XMLDoS", | |
"YAMLParsing" | |
], | |
- "number_of_controllers": 211, | |
- "number_of_models": 122, | |
- "number_of_templates": 202, | |
+ "number_of_controllers": 286, | |
+ "number_of_models": 173, | |
+ "number_of_templates": 205, | |
"ruby_version": "2.7.2", | |
"brakeman_version": "5.0.1" | |
}, | |
@@ -97,7 +97,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/report.rb", | |
- "line": 118, | |
+ "line": 121, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "Admin::ActionLog.from(\"(#{[Admin::ActionLog.where(:target_type => \"Report\", :target_id => id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Account\", :target_id => target_account_id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)].map do\n \"(#{query.to_sql})\"\n end.join(\" UNION ALL \")}) AS admin_action_logs\")", | |
"render_path": null, | |
@@ -110,13 +110,32 @@ | |
"confidence": "High" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "0a8e1b95121cc27c9eccfa472297b9c738b55e8511a902dfb0cd6c0861cf4a29", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/groups_controller.rb", | |
+ "line": 186, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:role, :account_ids => ([]), :tags => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::GroupsController", | |
+ "method": "resource_params" | |
+ }, | |
+ "user_input": ":role", | |
+ "confidence": "Medium" | |
+ }, | |
+ { | |
"warning_type": "SQL Injection", | |
"warning_code": 0, | |
"fingerprint": "19df3740b8d02a9fe0eb52c939b4b87d3a2a591162a6adfa8d64e9c26aeebe6d", | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/status.rb", | |
- "line": 107, | |
+ "line": 131, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "result.joins(\"INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")", | |
"render_path": null, | |
@@ -148,6 +167,25 @@ | |
"confidence": "High" | |
}, | |
{ | |
+ "warning_type": "SQL Injection", | |
+ "warning_code": 0, | |
+ "fingerprint": "3373360caca1c1ecad29d946fe605fb60d1ae163d68685c01faee2e86b7ef160", | |
+ "check_name": "SQL", | |
+ "message": "Possible SQL injection", | |
+ "file": "app/models/status.rb", | |
+ "line": 503, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
+ "code": "select(\"*\").from(\"(select s.* from statuses s\\n inner join conversations c on c.id = s.conversation_id\\n inner join conversation_mutes cm on cm.conversation_id = c.id\\n where cm.account_id = #{account_id} and in_reply_to_id is null) as statuses\")", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Status", | |
+ "method": "muted_conversations_for_account" | |
+ }, | |
+ "user_input": "account_id", | |
+ "confidence": "Weak" | |
+ }, | |
+ { | |
"warning_type": "Redirect", | |
"warning_code": 18, | |
"fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7", | |
@@ -173,7 +211,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/account.rb", | |
- "line": 542, | |
+ "line": 611, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "find_by_sql([\" WITH first_degree AS (\\n SELECT target_account_id\\n FROM follows\\n WHERE account_id = ?\\n UNION ALL\\n SELECT ?\\n )\\n SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?)\\n WHERE accounts.id IN (SELECT * FROM first_degree)\\n AND #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, account.id, limit, offset])", | |
"render_path": null, | |
@@ -192,7 +230,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/status.rb", | |
- "line": 112, | |
+ "line": 136, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "result.joins(\"LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")", | |
"render_path": null, | |
@@ -230,7 +268,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/account.rb", | |
- "line": 511, | |
+ "line": 580, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, limit, offset])", | |
"render_path": null, | |
@@ -262,6 +300,25 @@ | |
"confidence": "Medium" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/notifications_controller.rb", | |
+ "line": 82, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::NotificationsController", | |
+ "method": "browserable_params" | |
+ }, | |
+ "user_input": ":account_id", | |
+ "confidence": "High" | |
+ }, | |
+ { | |
"warning_type": "Redirect", | |
"warning_code": 18, | |
"fingerprint": "ba568ac09683f98740f663f3d850c31785900215992e8c090497d359a2563d50", | |
@@ -300,13 +357,32 @@ | |
"confidence": "High" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "d6003f47b2ac2aa2a8c7e56d8a78d9490999ec6c6e0b82c353575084f8bd47cd", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/reports_controller.rb", | |
+ "line": 84, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:account_id, :comment, :forward, :group_id, :external_ad_url, :external_ad_media_url, :external_ad_description, :status_ids => ([]), :rule_ids => ([]), :message_ids => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::ReportsController", | |
+ "method": "report_params" | |
+ }, | |
+ "user_input": ":account_id", | |
+ "confidence": "High" | |
+ }, | |
+ { | |
"warning_type": "SQL Injection", | |
"warning_code": 0, | |
"fingerprint": "e21d8fee7a5805761679877ca35ed1029c64c45ef3b4012a30262623e1ba8bb9", | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/account.rb", | |
- "line": 558, | |
+ "line": 627, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?)\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, limit, offset])", | |
"render_path": null, | |
@@ -317,31 +393,12 @@ | |
}, | |
"user_input": "textsearch", | |
"confidence": "Medium" | |
- }, | |
- { | |
- "warning_type": "Mass Assignment", | |
- "warning_code": 105, | |
- "fingerprint": "efa3a1b56f8c87aabd679156a2d5fd8185d469562361fb9ca8e5b478ed909906", | |
- "check_name": "PermitAttributes", | |
- "message": "Potentially dangerous key allowed for mass assignment", | |
- "file": "app/controllers/api/v1/reports_controller.rb", | |
- "line": 45, | |
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
- "code": "params.permit(:account_id, :comment, :forward, :status_ids => ([]), :rule_ids => ([]))", | |
- "render_path": null, | |
- "location": { | |
- "type": "method", | |
- "class": "Api::V1::ReportsController", | |
- "method": "report_params" | |
- }, | |
- "user_input": ":account_id", | |
- "confidence": "High" | |
} | |
], | |
"errors": [ | |
], | |
"obsolete": [ | |
- | |
+ "a9c222f356d8b4b673445f266f86e6bfce87df4e97a25ac36407367ee1269bf4" | |
] | |
} | |
\ No newline at end of file | |
diff -ru truth-old/opensource/config/application.rb truth-new/opensource/config/application.rb | |
--- truth-old/opensource/config/application.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/application.rb 2024-04-01 14:59:13 | |
@@ -33,6 +33,9 @@ | |
require_relative '../lib/active_record/database_tasks_extensions' | |
require_relative '../lib/active_record/batches' | |
require_relative '../lib/s3/seahorse_extensions' | |
+require_relative '../lib/sidekiq_unique_jobs/manager_extensions' | |
+require_relative '../lib/http/request_extensions' | |
+ | |
require 'prometheus_exporter/client' | |
Dotenv::Railtie.load | |
@@ -155,10 +158,12 @@ | |
Doorkeeper::AuthorizationsController.layout 'modal' | |
Doorkeeper::AuthorizedApplicationsController.layout 'admin' | |
Doorkeeper::Application.send :include, ApplicationExtension | |
- Doorkeeper::AccessToken.send :include, AccessTokenExtension | |
+ OauthAccessToken.send :include, AccessTokenExtension | |
Devise::FailureApp.send :include, AbstractController::Callbacks | |
Devise::FailureApp.send :include, HttpAcceptLanguage::EasyAccess | |
Devise::FailureApp.send :include, Localized | |
end | |
+ | |
+ config.active_record.schema_format = :sql | |
end | |
end | |
diff -ru truth-old/opensource/config/brakeman.ignore truth-new/opensource/config/brakeman.ignore | |
--- truth-old/opensource/config/brakeman.ignore 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/brakeman.ignore 2024-04-01 14:59:13 | |
@@ -7,7 +7,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/report.rb", | |
- "line": 118, | |
+ "line": 121, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "Admin::ActionLog.from(\"(#{[Admin::ActionLog.where(:target_type => \"Report\", :target_id => id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Account\", :target_id => target_account_id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)].map do\n \"(#{query.to_sql})\"\n end.join(\" UNION ALL \")}) AS admin_action_logs\")", | |
"render_path": null, | |
@@ -21,13 +21,33 @@ | |
"note": "" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "0a8e1b95121cc27c9eccfa472297b9c738b55e8511a902dfb0cd6c0861cf4a29", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/groups_controller.rb", | |
+ "line": 186, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:role, :account_ids => ([]), :tags => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::GroupsController", | |
+ "method": "resource_params" | |
+ }, | |
+ "user_input": ":role", | |
+ "confidence": "Medium", | |
+ "note": "" | |
+ }, | |
+ { | |
"warning_type": "SQL Injection", | |
"warning_code": 0, | |
"fingerprint": "19df3740b8d02a9fe0eb52c939b4b87d3a2a591162a6adfa8d64e9c26aeebe6d", | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/status.rb", | |
- "line": 103, | |
+ "line": 131, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "result.joins(\"INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")", | |
"render_path": null, | |
@@ -41,6 +61,46 @@ | |
"note": "" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "224a66f0b51e79901fae28810d09f30d6cd5a9b45eef4699ecb95dff9bd4aa95", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v2/search_controller.rb", | |
+ "line": 28, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :q, :resolve, :exclude_unreviewed, :limit)", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V2::SearchController", | |
+ "method": "search_params" | |
+ }, | |
+ "user_input": ":account_id", | |
+ "confidence": "High", | |
+ "note": "" | |
+ }, | |
+ { | |
+ "warning_type": "SQL Injection", | |
+ "warning_code": 0, | |
+ "fingerprint": "3373360caca1c1ecad29d946fe605fb60d1ae163d68685c01faee2e86b7ef160", | |
+ "check_name": "SQL", | |
+ "message": "Possible SQL injection", | |
+ "file": "app/models/status.rb", | |
+ "line": 503, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
+ "code": "select(\"*\").from(\"(select s.* from statuses s\\n inner join conversations c on c.id = s.conversation_id\\n inner join conversation_mutes cm on cm.conversation_id = c.id\\n where cm.account_id = #{account_id} and in_reply_to_id is null) as statuses\")", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Status", | |
+ "method": "muted_conversations_for_account" | |
+ }, | |
+ "user_input": "account_id", | |
+ "confidence": "Weak", | |
+ "note": "" | |
+ }, | |
+ { | |
"warning_type": "Redirect", | |
"warning_code": 18, | |
"fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7", | |
@@ -67,7 +127,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/account.rb", | |
- "line": 525, | |
+ "line": 611, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "find_by_sql([\" WITH first_degree AS (\\n SELECT target_account_id\\n FROM follows\\n WHERE account_id = ?\\n UNION ALL\\n SELECT ?\\n )\\n SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?)\\n WHERE accounts.id IN (SELECT * FROM first_degree)\\n AND #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, account.id, limit, offset])", | |
"render_path": null, | |
@@ -87,7 +147,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/status.rb", | |
- "line": 108, | |
+ "line": 136, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "result.joins(\"LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")", | |
"render_path": null, | |
@@ -103,25 +163,6 @@ | |
{ | |
"warning_type": "Mass Assignment", | |
"warning_code": 105, | |
- "fingerprint": "224a66f0b51e79901fae28810d09f30d6cd5a9b45eef4699ecb95dff9bd4aa95", | |
- "check_name": "PermitAttributes", | |
- "message": "Potentially dangerous key allowed for mass assignment", | |
- "file": "app/controllers/api/v2/search_controller.rb", | |
- "line": 28, | |
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
- "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :q, :resolve, :exclude_unreviewed, :limit)", | |
- "render_path": null, | |
- "location": { | |
- "type": "method", | |
- "class": "Api::V2::SearchController", | |
- "method": "search_params" | |
- }, | |
- "user_input": ":account_id", | |
- "confidence": "High" | |
- }, | |
- { | |
- "warning_type": "Mass Assignment", | |
- "warning_code": 105, | |
"fingerprint": "874be88fedf4c680926845e9a588d3197765a6ccbfdd76466b44cc00151c612e", | |
"check_name": "PermitAttributes", | |
"message": "Potentially dangerous key allowed for mass assignment", | |
@@ -146,7 +187,7 @@ | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/account.rb", | |
- "line": 494, | |
+ "line": 580, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, limit, offset])", | |
"render_path": null, | |
@@ -180,6 +221,46 @@ | |
"note": "" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "a9c222f356d8b4b673445f266f86e6bfce87df4e97a25ac36407367ee1269bf4", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/reports_controller.rb", | |
+ "line": 70, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:account_id, :comment, :forward, :group_id, :status_ids => ([]), :rule_ids => ([]), :message_ids => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::ReportsController", | |
+ "method": "report_params" | |
+ }, | |
+ "user_input": ":account_id", | |
+ "confidence": "High", | |
+ "note": "" | |
+ }, | |
+ { | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/notifications_controller.rb", | |
+ "line": 82, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::NotificationsController", | |
+ "method": "browserable_params" | |
+ }, | |
+ "user_input": ":account_id", | |
+ "confidence": "High", | |
+ "note": "" | |
+ }, | |
+ { | |
"warning_type": "Redirect", | |
"warning_code": 18, | |
"fingerprint": "ba568ac09683f98740f663f3d850c31785900215992e8c090497d359a2563d50", | |
@@ -220,13 +301,33 @@ | |
"note": "" | |
}, | |
{ | |
+ "warning_type": "Mass Assignment", | |
+ "warning_code": 105, | |
+ "fingerprint": "d6003f47b2ac2aa2a8c7e56d8a78d9490999ec6c6e0b82c353575084f8bd47cd", | |
+ "check_name": "PermitAttributes", | |
+ "message": "Potentially dangerous key allowed for mass assignment", | |
+ "file": "app/controllers/api/v1/reports_controller.rb", | |
+ "line": 84, | |
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
+ "code": "params.permit(:account_id, :comment, :forward, :group_id, :external_ad_url, :external_ad_media_url, :external_ad_description, :status_ids => ([]), :rule_ids => ([]), :message_ids => ([]))", | |
+ "render_path": null, | |
+ "location": { | |
+ "type": "method", | |
+ "class": "Api::V1::ReportsController", | |
+ "method": "report_params" | |
+ }, | |
+ "user_input": ":account_id", | |
+ "confidence": "High", | |
+ "note": "" | |
+ }, | |
+ { | |
"warning_type": "SQL Injection", | |
"warning_code": 0, | |
"fingerprint": "e21d8fee7a5805761679877ca35ed1029c64c45ef3b4012a30262623e1ba8bb9", | |
"check_name": "SQL", | |
"message": "Possible SQL injection", | |
"file": "app/models/account.rb", | |
- "line": 541, | |
+ "line": 627, | |
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", | |
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?)\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, limit, offset])", | |
"render_path": null, | |
@@ -238,28 +339,8 @@ | |
"user_input": "textsearch", | |
"confidence": "Medium", | |
"note": "" | |
- }, | |
- { | |
- "warning_type": "Mass Assignment", | |
- "warning_code": 105, | |
- "fingerprint": "efa3a1b56f8c87aabd679156a2d5fd8185d469562361fb9ca8e5b478ed909906", | |
- "check_name": "PermitAttributes", | |
- "message": "Potentially dangerous key allowed for mass assignment", | |
- "file": "app/controllers/api/v1/reports_controller.rb", | |
- "line": 45, | |
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", | |
- "code": "params.permit(:account_id, :comment, :forward, :status_ids => ([]), :rule_ids => ([]))", | |
- "render_path": null, | |
- "location": { | |
- "type": "method", | |
- "class": "Api::V1::ReportsController", | |
- "method": "report_params" | |
- }, | |
- "user_input": ":account_id", | |
- "confidence": "High", | |
- "note": "" | |
} | |
], | |
- "updated": "2022-02-03 12:54:45 -0500", | |
+ "updated": "2023-07-17 16:08:14 -0500", | |
"brakeman_version": "5.0.1" | |
} | |
diff -ru truth-old/opensource/config/database.yml truth-new/opensource/config/database.yml | |
--- truth-old/opensource/config/database.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/database.yml 2024-04-01 14:59:13 | |
@@ -35,14 +35,22 @@ | |
connections: | |
- role: master | |
blacklist_duration: 0 | |
- url: postgresql://<%= ENV['DB_USER'] || 'truth' %>:<%= ENV['DB_PASS'] || '' %>@<%= ENV['DB_HOST'] || 'localhost' %>:<%= ENV['DB_PORT'] || 5432 %>/<%= ENV['DB_NAME'] || 'truth_production' %> | |
+ host: <%= ENV['DB_HOST'] || 'localhost' %> | |
+ port: <%= ENV['DB_PORT'] || 5432 %> | |
+ database: <%= ENV['DB_NAME'] || 'truth_production' %> | |
+ username: <%= ENV['DB_USER'] || 'truth' %> | |
+ password: <%= ENV['DB_PASS'] || '' %> | |
- role: slave | |
blacklist_duration: 0 | |
- url: postgresql://<%= ENV['DB_USER'] || 'truth' %>:<%= ENV['DB_PASS'] || '' %>@<%= ENV['DB_HOST_REPLICA'] || 'localhost' %>:<%= ENV['DB_PORT_REPLICA'] || 5433 %>/<%= ENV['DB_NAME'] || 'truth_production' %> | |
+ host: <%= ENV['DB_HOST_REPLICA'] || 'localhost' %> | |
+ port: <%= ENV['DB_PORT_REPLICA'] || 5433 %> | |
+ database: <%= ENV['DB_NAME_REPLICA'] || 'truth_production_ro' %> | |
+ username: <%= ENV['DB_USER_REPLICA'] || 'truth' %> | |
+ password: <%= ENV['DB_PASS_REPLICA'] || '' %> | |
<% else %> | |
+ host: <%= ENV['DB_HOST'] || 'localhost' %> | |
+ port: <%= ENV['DB_PORT'] || 5432 %> | |
database: <%= ENV['DB_NAME'] || 'truth_production' %> | |
username: <%= ENV['DB_USER'] || 'truth' %> | |
password: <%= ENV['DB_PASS'] || '' %> | |
- host: <%= ENV['DB_HOST'] || 'localhost' %> | |
- port: <%= ENV['DB_PORT'] || 5432 %> | |
<% end %> | |
diff -ru truth-old/opensource/config/environment.rb truth-new/opensource/config/environment.rb | |
--- truth-old/opensource/config/environment.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/environment.rb 2024-04-01 14:59:13 | |
@@ -4,4 +4,7 @@ | |
# Initialize the Rails application. | |
Rails.application.initialize! | |
+# Require all proto events & schemas | |
+Dir[File.expand_path("./lib/proto/**/*.rb")].each { |f| require f } | |
+ | |
ActiveRecord::SchemaDumper.ignore_tables = ['deprecated_preview_cards'] | |
diff -ru truth-old/opensource/config/environments/development.rb truth-new/opensource/config/environments/development.rb | |
--- truth-old/opensource/config/environments/development.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/environments/development.rb 2024-04-01 14:59:13 | |
@@ -105,6 +105,7 @@ | |
end | |
config.x.otp_secret = ENV.fetch('OTP_SECRET', '1fc2b87989afa6351912abeebe31ffc5c476ead9bf8b3d74cbc4a302c7b69a45b40b1bbef3506ddad73e942e15ed5ca4b402bf9a66423626051104f4b5f05109') | |
+ config.hosts << "#{Rails.configuration.x.use_https ? 'https' : 'http' }://#{Rails.configuration.x.web_domain}" | |
end | |
ActiveRecordQueryTrace.enabled = ENV['QUERY_TRACE_ENABLED'] == 'true' | |
diff -ru truth-old/opensource/config/environments/production.rb truth-new/opensource/config/environments/production.rb | |
--- truth-old/opensource/config/environments/production.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/environments/production.rb 2024-04-01 14:59:13 | |
@@ -47,7 +47,7 @@ | |
config.force_ssl = true | |
config.ssl_options = { | |
redirect: { | |
- exclude: -> request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') } | |
+ exclude: -> request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') || request.remote_ip === '127.0.0.1'} | |
} | |
} | |
Only in truth-new/opensource/config: imagemagick | |
diff -ru truth-old/opensource/config/initializers/chewy.rb truth-new/opensource/config/initializers/chewy.rb | |
--- truth-old/opensource/config/initializers/chewy.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/chewy.rb 2024-04-01 14:59:13 | |
@@ -1,17 +1,28 @@ | |
-enabled = ENV['ES_ENABLED'] == 'true' | |
-host = ENV.fetch('ES_HOST') { 'localhost' } | |
-port = ENV.fetch('ES_PORT') { 9200 } | |
+enabled = ENV['ES_ENABLED'] == 'true' | |
+indexing_enabled = ENV.fetch('ES_INDEXING_ENABLED') { ENV['ES_ENABLED'] } == 'true' | |
+host = ENV.fetch('ES_HOST') { 'localhost' } | |
+port = ENV.fetch('ES_PORT') { 9200 } | |
fallback_prefix = ENV.fetch('REDIS_NAMESPACE') { nil } | |
-prefix = ENV.fetch('ES_PREFIX') { fallback_prefix } | |
+prefix = ENV.fetch('ES_PREFIX') { fallback_prefix } | |
+username = ENV.fetch('ES_USER') { nil } | |
+password = ENV.fetch('ES_PASSWORD') { nil } | |
Chewy.settings = { | |
- host: "#{host}:#{port}", | |
+ host: host, | |
+ port: port, | |
prefix: prefix, | |
enabled: enabled, | |
+ indexing_enabled: indexing_enabled, | |
journal: false, | |
sidekiq: { queue: 'chewy' }, | |
} | |
+if username && password | |
+ Chewy.settings[:user] = username | |
+ Chewy.settings[:password] = password | |
+end | |
+ | |
+ | |
# We use our own async strategy even outside the request-response | |
# cycle, which takes care of checking if ElasticSearch is enabled | |
# or not. However, mind that for the Rails console, the :urgent | |
@@ -25,6 +36,10 @@ | |
def enabled? | |
settings[:enabled] | |
end | |
+ | |
+ def indexing_enabled? | |
+ settings[:indexing_enabled] | |
+ end | |
end | |
end | |
@@ -33,23 +48,3 @@ | |
# Mastodon is run with hidden services enabled, because | |
# ElasticSearch is *not* supposed to be accessed through a proxy | |
Faraday.ignore_env_proxy = true | |
- | |
-# Elasticsearch 7.x workaround | |
-Elasticsearch::Transport::Client.prepend Module.new { | |
- def search(arguments = {}) | |
- arguments[:rest_total_hits_as_int] = true | |
- super arguments | |
- end | |
-} | |
- | |
-Elasticsearch::API::Indices::IndicesClient.prepend Module.new { | |
- def create(arguments = {}) | |
- arguments[:include_type_name] = true | |
- super arguments | |
- end | |
- | |
- def put_mapping(arguments = {}) | |
- arguments[:include_type_name] = true | |
- super arguments | |
- end | |
-} | |
diff -ru truth-old/opensource/config/initializers/content_security_policy.rb truth-new/opensource/config/initializers/content_security_policy.rb | |
--- truth-old/opensource/config/initializers/content_security_policy.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/content_security_policy.rb 2024-04-01 14:59:13 | |
@@ -19,8 +19,8 @@ | |
#TODO: refactor the maintenance of these URLs | |
segment_base_url = 'https://cdn.segment.com' | |
segment_api_url = 'https://api.segment.io' | |
-segment_script_hashes = ["'sha256-Kru1cRFDRjvkSX3GJVOzPMlesOJPlwl8Yf/vyxi7wnc='", | |
- "'sha256-SkDGcKd1lxidykiwp0MQl3em4R4qTUyDCyVbFr52Qdo='", | |
+segment_script_hashes = ["'sha256-Kru1cRFDRjvkSX3GJVOzPMlesOJPlwl8Yf/vyxi7wnc='", | |
+ "'sha256-SkDGcKd1lxidykiwp0MQl3em4R4qTUyDCyVbFr52Qdo='", | |
"'sha256-CZKu4Ofm+PztnJbExQzfZGKk50F7ttkRpdQxduN4lCA='" | |
] | |
@@ -59,14 +59,3 @@ | |
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } | |
Rails.application.config.content_security_policy_nonce_directives = %w(style-src) | |
- | |
-Rails.application.reloader.to_prepare do | |
- PgHero::HomeController.content_security_policy do |p| | |
- p.script_src :self, :unsafe_inline, assets_host | |
- p.style_src :self, :unsafe_inline, assets_host | |
- end | |
- | |
- PgHero::HomeController.after_action do | |
- request.content_security_policy_nonce_generator = nil | |
- end | |
-end | |
diff -ru truth-old/opensource/config/initializers/cors.rb truth-new/opensource/config/initializers/cors.rb | |
--- truth-old/opensource/config/initializers/cors.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/cors.rb 2024-04-01 14:59:13 | |
@@ -25,7 +25,16 @@ | |
headers: :any, | |
methods: [:post, :put, :delete, :get, :patch, :options], | |
credentials: false, | |
- expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] | |
+ expose: [ | |
+ 'Link', | |
+ 'X-RateLimit-Reset', | |
+ 'X-RateLimit-Limit', | |
+ 'X-RateLimit-Remaining', | |
+ 'X-Request-Id', | |
+ 'X-Unread-Messages-Count', | |
+ 'X-Total-Count', | |
+ 'X-Truth-Ad-Indexes' | |
+ ] | |
resource '/oauth/token', | |
headers: :any, | |
methods: [:post], | |
diff -ru truth-old/opensource/config/initializers/doorkeeper.rb truth-new/opensource/config/initializers/doorkeeper.rb | |
--- truth-old/opensource/config/initializers/doorkeeper.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/doorkeeper.rb 2024-04-01 14:59:13 | |
@@ -2,6 +2,8 @@ | |
# Change the ORM that doorkeeper will use (needs plugins) | |
orm :active_record | |
+ access_token_class "OauthAccessToken" | |
+ | |
# This block will be called to check whether the resource owner is authenticated or not. | |
resource_owner_authenticator do | |
current_user || redirect_to(new_user_session_url) | |
@@ -11,22 +13,28 @@ | |
username = request.params[:username] | |
if (username.present?) | |
- user = if username.include?("@") | |
+ username.strip! | |
+ user = if username =~ URI::MailTo::EMAIL_REGEXP | |
# Emails on the user table are all stored in lowercase, so this should allow us to login with case-insensitive email | |
User.find_by(email: username.downcase) | |
- else | |
+ elsif username.first == '@' | |
# Unlike emails usernames are stored as they are entered so we need to query case insensitive | |
+ Account.ci_find_by_username(username[1..-1])&.user | |
+ else | |
Account.ci_find_by_username(username)&.user | |
end | |
user = nil unless user&.valid_password?(request.params[:password]) | |
elsif request.params[:mfa_token].present? | |
- user = User.get_user_from_token(request.params[:mfa_token]) | |
+ mfa_token = params[:mfa_token].strip | |
+ code = params[:code].strip | |
+ user = User.get_user_from_token(mfa_token) | |
+ | |
if user.present? | |
- user = nil unless user.validate_user_token(request.params[:mfa_token]) | |
- user = nil unless (user.validate_and_consume_otp!(request.params[:code]) || | |
- user.invalidate_otp_backup_code!(request.params[:code])) | |
+ user = nil unless user.validate_user_token(mfa_token) | |
+ user = nil unless (user.validate_and_consume_otp!(code) || | |
+ user.invalidate_otp_backup_code!(code)) | |
end | |
else | |
user = nil | |
@@ -123,7 +131,8 @@ | |
:'admin:write', | |
:'admin:write:accounts', | |
:'admin:write:reports', | |
- :crypto | |
+ :crypto, | |
+ :ads | |
# Change the way client credentials are retrieved from the request object. | |
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then | |
diff -ru truth-old/opensource/config/initializers/filter_parameter_logging.rb truth-new/opensource/config/initializers/filter_parameter_logging.rb | |
--- truth-old/opensource/config/initializers/filter_parameter_logging.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/filter_parameter_logging.rb 2024-04-01 14:59:13 | |
@@ -1,4 +1,4 @@ | |
# Be sure to restart your server when you modify this file. | |
# Configure sensitive parameters which will be filtered from the log file. | |
-Rails.application.config.filter_parameters += [:password, :sms, :private_key, :public_key, :otp_attempt] | |
+Rails.application.config.filter_parameters += [:password, :sms, :private_key, :public_key, :otp_attempt, :token] | |
diff -ru truth-old/opensource/config/initializers/makara.rb truth-new/opensource/config/initializers/makara.rb | |
--- truth-old/opensource/config/initializers/makara.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/makara.rb 2024-04-01 14:59:13 | |
@@ -13,4 +13,4 @@ | |
end | |
end | |
-Object.send :include, ReadFromReplica | |
\ No newline at end of file | |
+Object.send :include, ReadFromReplica | |
diff -ru truth-old/opensource/config/initializers/paperclip.rb truth-new/opensource/config/initializers/paperclip.rb | |
--- truth-old/opensource/config/initializers/paperclip.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/paperclip.rb 2024-04-01 14:59:13 | |
@@ -11,7 +11,7 @@ | |
end | |
end | |
-Paperclip.interpolates :prefix_path do |attachment, style| | |
+Paperclip.interpolates :prefix_path do |attachment, _style| | |
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local? | |
'cache' + File::SEPARATOR | |
else | |
@@ -19,7 +19,7 @@ | |
end | |
end | |
-Paperclip.interpolates :prefix_url do |attachment, style| | |
+Paperclip.interpolates :prefix_url do |attachment, _style| | |
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local? | |
'cache/' | |
else | |
@@ -61,8 +61,8 @@ | |
s3_options: { | |
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' }, | |
- http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT'){ '5' }.to_i, | |
- http_read_timeout: ENV.fetch('S3_READ_TIMEOUT'){ '5' }.to_i, | |
+ http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT') { '5' }.to_i, | |
+ http_read_timeout: ENV.fetch('S3_READ_TIMEOUT') { '5' }.to_i, | |
http_idle_timeout: 5, | |
retry_limit: 0, | |
http_proxy: nil, | |
@@ -72,7 +72,7 @@ | |
if ENV.has_key?('S3_ENDPOINT') | |
Paperclip::Attachment.default_options[:s3_options].merge!( | |
endpoint: ENV['S3_ENDPOINT'], | |
- force_path_style: ENV['S3_OVERRIDE_PATH_STYLE'] != 'true', | |
+ force_path_style: ENV['S3_OVERRIDE_PATH_STYLE'] != 'true' | |
) | |
Paperclip::Attachment.default_options[:url] = ':s3_path_url' | |
@@ -109,7 +109,7 @@ | |
Paperclip::Attachment.default_options.merge!( | |
storage: :filesystem, | |
path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':prefix_path:class', ':attachment', ':id_partition', ':style', ':filename'), | |
- url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename', | |
+ url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename' | |
) | |
end | |
@@ -126,4 +126,11 @@ | |
class NetworkingError < StandardError; end | |
end | |
end | |
+end | |
+ | |
+# Set our ImageMagick security policy, but allow admins to override it | |
+ENV['MAGICK_CONFIGURE_PATH'] = begin | |
+ imagemagick_config_paths = ENV.fetch('MAGICK_CONFIGURE_PATH', '').split(File::PATH_SEPARATOR) | |
+ imagemagick_config_paths << Rails.root.join('config', 'imagemagick').expand_path.to_s | |
+ imagemagick_config_paths.join(File::PATH_SEPARATOR) | |
end | |
diff -ru truth-old/opensource/config/initializers/rack_attack.rb truth-new/opensource/config/initializers/rack_attack.rb | |
--- truth-old/opensource/config/initializers/rack_attack.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/rack_attack.rb 2024-04-01 14:59:13 | |
@@ -7,6 +7,10 @@ | |
def authenticated_token | |
return @token if defined?(@token) | |
+ if Rails.env.production? && path.start_with?('/api/v1/accounts/verify_credentials') | |
+ ActiveRecord::Base.connection.stick_to_master!(false) | |
+ end | |
+ | |
@token = Doorkeeper::OAuth::Token.authenticate( | |
Doorkeeper::Grape::AuthorizationDecorator.new(self), | |
*Doorkeeper.configuration.access_token_methods | |
@@ -14,7 +18,7 @@ | |
end | |
def remote_ip | |
- @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s | |
+ @remote_ip ||= (@env['action_dispatch.remote_ip'] || ip).to_s | |
end | |
def authenticated_user_id | |
@@ -40,10 +44,9 @@ | |
def private_address?(address) | |
PrivateAddressCheck.private_address?(IPAddr.new(address)) | |
end | |
- | |
end | |
- unless ENV["SKIP_IP_RATE_LIMITING"] == "true" | |
+ unless ENV['SKIP_IP_RATE_LIMITING'] == 'true' | |
Rack::Attack.safelist('allow from private') do |req| | |
req.private_address?(req.remote_ip) | |
end | |
@@ -97,20 +100,19 @@ | |
throttle('throttle_password_resets/email', limit: 5, period: 30.minutes) do |req| | |
(req.params.dig('user', 'email').presence if req.post? && req.path == '/auth/password') || | |
- (req.params.dig('email').presence if req.post? && req.path == '/api/pleroma/change_password') || | |
- (req.params.dig('email').presence if req.post? && req.path == '/api/v1/truth/password_reset/request') | |
- | |
+ (req.params.dig('email').presence if req.post? && req.path == '/api/pleroma/change_password') || | |
+ (req.params.dig('email').presence if req.post? && req.path == '/api/v1/truth/password_reset/request') | |
end | |
throttle('throttle_email_confirmations/ip', limit: 25, period: 5.minutes) do |req| | |
- req.remote_ip if ((req.post? && %w(/auth/confirmation /api/v1/emails/confirmations).include?(req.path)) || | |
- (req.get? && req.path.include?('api/v1/truth/email/confirm'))) | |
+ req.remote_ip if (req.post? && %w(/auth/confirmation /api/v1/emails/confirmations).include?(req.path)) || | |
+ (req.get? && req.path.include?('api/v1/truth/email/confirm')) | |
end | |
throttle('throttle_email_confirmations/email', limit: 5, period: 30.minutes) do |req| | |
if req.post? && req.path == '/auth/confirmation' | |
req.params.dig('user', 'email').presence | |
- elsif ((req.post? && req.path == '/api/v1/emails/confirmations') || (req.get? && req.path == '/api/v1/truth/email/confirm')) | |
+ elsif (req.post? && req.path == '/api/v1/emails/confirmations') || (req.get? && req.path == '/api/v1/truth/email/confirm') | |
req.authenticated_user_id | |
end | |
end | |
@@ -121,8 +123,23 @@ | |
throttle('throttle_login_attempts/email', limit: 25, period: 1.hour) do |req| | |
(req.session[:attempt_user_id] || req.params.dig('user', 'email').presence if req.post? && req.path == '/auth/sign_in') || | |
- (req.params.dig('username').presence if req.post? && req.path == '/oauth/token') || | |
- (req.params.dig('mfa_token').presence if req.post? && req.path == '/oauth/mfa/challenge') | |
+ (req.params.dig('username').presence if req.post? && req.path == '/oauth/token') || | |
+ (req.params.dig('mfa_token').presence if req.post? && req.path == '/oauth/mfa/challenge') | |
+ end | |
+ | |
+ API_CHAT_MESSAGE_REGEX = /\A\/api\/v1\/pleroma\/chats\/[\d]+\/messages/.freeze | |
+ API_CHAT_MESSAGE_REACTION_REGEX = /\A\/api\/v1\/pleroma\/chats\/[\d]+\/messages\/[\d]+\/reactions/.freeze | |
+ | |
+ throttle('throttle_chat_messages', limit: ChatMessage::MAX_MESSAGES_PER_MIN, period: 1.minute) do |req| | |
+ req.remote_ip if req.post? && req.path.match?(API_CHAT_MESSAGE_REGEX) | |
+ end | |
+ | |
+ throttle('throttle_chat_message_reactions', limit: ChatMessageReaction::MAX_MESSAGES_PER_MIN, period: 1.minute) do |req| | |
+ req.remote_ip if req.post? && req.path.match?(API_CHAT_MESSAGE_REACTION_REGEX) | |
+ end | |
+ | |
+ throttle('throttle_app_attest_attestations', limit: 11, period: 1.second) do |req| | |
+ req.remote_ip if req.path == '/api/v1/truth/ios_device_check/rate_limit' | |
end | |
self.throttled_response = lambda do |env| | |
@@ -139,4 +156,4 @@ | |
[429, headers, [{ error: I18n.t('errors.429') }.to_json]] | |
end | |
end | |
-end | |
\ No newline at end of file | |
+end | |
diff -ru truth-old/opensource/config/initializers/rack_attack_logging.rb truth-new/opensource/config/initializers/rack_attack_logging.rb | |
--- truth-old/opensource/config/initializers/rack_attack_logging.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/rack_attack_logging.rb 2023-05-05 13:42:02 | |
@@ -5,4 +5,7 @@ | |
Rails.logger.info("Rate limit hit by rack-attack (#{req.env['rack.attack.match_type']} #{req.env['rack.attack.matched']}): #{req.ip} #{req.request_method} #{req.fullpath} #{req.authenticated_user_id}") | |
+ redis_key = "rate_limit:#{DateTime.current.to_date}" | |
+ redis_element_key = "#{req.authenticated_user_id}-#{req.ip}" | |
+ Redis.current.zincrby(redis_key, 1, redis_element_key) | |
end | |
Only in truth-new/opensource/config/initializers: rmq_consumer.rb | |
Only in truth-old/opensource/config/initializers: statsd.rb | |
diff -ru truth-old/opensource/config/initializers/webauthn.rb truth-new/opensource/config/initializers/webauthn.rb | |
--- truth-old/opensource/config/initializers/webauthn.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/initializers/webauthn.rb 2024-04-01 14:59:13 | |
@@ -20,5 +20,22 @@ | |
# In this case the default would be "auth.example.com", but you can set it to | |
# the suffix "example.com" | |
# | |
- # config.rp_id = "example.com" | |
+ config.rp_id = ENV.fetch('SERVER_RP_ID', "truth.social") | |
+ config.silent_authentication = true | |
+end | |
+ | |
+module WebAuthn | |
+ module AttestationStatement | |
+ def self.from(format, statement) | |
+ new_format = format == "apple-appattest" ? "apple" : format | |
+ | |
+ klass = FORMAT_TO_CLASS[new_format] | |
+ | |
+ if klass | |
+ klass.new(statement) | |
+ else | |
+ raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'") | |
+ end | |
+ end | |
+ end | |
end | |
diff -ru truth-old/opensource/config/locales/en.yml truth-new/opensource/config/locales/en.yml | |
--- truth-old/opensource/config/locales/en.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/locales/en.yml 2024-04-05 09:17:47 | |
@@ -55,6 +55,8 @@ | |
other: users | |
user_count_before: Home to | |
what_is_mastodon: What is Truth? | |
+ ads: | |
+ why_copy: We show ads for products and services we think our users might like. | |
accounts: | |
choices_html: "%{name}'s choices:" | |
endorsements_hint: You can endorse people you follow from the web interface, and they will show up here. | |
@@ -126,7 +128,6 @@ | |
disable_two_factor_authentication: Disable 2FA | |
disabled: Frozen | |
display_name: Display name | |
- location: Location | |
website: Website | |
domain: Domain | |
edit: Edit | |
@@ -256,6 +257,7 @@ | |
enable_user: Enable User | |
memorialize_account: Memorialize Account | |
promote_user: Promote User | |
+ remove_avatar_group: Remove Group Avatar | |
remove_avatar_user: Remove Avatar | |
reopen_report: Reopen Report | |
reset_password_user: Reset Password | |
@@ -263,10 +265,12 @@ | |
sensitive_account: Mark the media in your account as sensitive | |
silence_account: Silence Account | |
suspend_account: Suspend Account | |
+ suspend_group: Suspend Group | |
unassigned_report: Unassign Report | |
unsensitive_account: Unmark the media in your account as sensitive | |
unsilence_account: Unsilence Account | |
unsuspend_account: Unsuspend Account | |
+ unsuspend_group: Unsuspend Group | |
update_announcement: Update Announcement | |
update_custom_emoji: Update Custom Emoji | |
update_domain_block: Update Domain Block | |
@@ -299,6 +303,7 @@ | |
enable_user_html: "%{name} enabled login for user %{target}" | |
memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page" | |
promote_user_html: "%{name} promoted user %{target}" | |
+ remove_avatar_group_html: "%{name} removed the avatar for group %{target}" | |
remove_avatar_user_html: "%{name} removed %{target}'s avatar" | |
reopen_report_html: "%{name} reopened report %{target}" | |
reset_password_user_html: "%{name} reset password of user %{target}" | |
@@ -306,10 +311,12 @@ | |
sensitive_account_html: "%{name} marked %{target}'s media as sensitive" | |
silence_account_html: "%{name} silenced %{target}'s account" | |
suspend_account_html: "%{name} suspended %{target}'s account" | |
+ suspend_group_html: "%{name} suspended the group %{target}" | |
unassigned_report_html: "%{name} unassigned report %{target}" | |
unsensitive_account_html: "%{name} unmarked %{target}'s media as sensitive" | |
unsilence_account_html: "%{name} unsilenced %{target}'s account" | |
unsuspend_account_html: "%{name} unsuspended %{target}'s account" | |
+ unsuspend_group_html: "%{name} unsuspended the group %{target}" | |
update_announcement_html: "%{name} updated announcement %{target}" | |
update_custom_emoji_html: "%{name} updated emoji %{target}" | |
update_domain_block_html: "%{name} updated domain block for %{target}" | |
@@ -463,12 +470,20 @@ | |
suppressed: Suppressed | |
title: Follow recommendations | |
unsuppress: Restore follow recommendation | |
- trending_truths: | |
- description_html: "<strong>Follow recommendations help new users quickly find interesting content</strong>. When a user has not interacted with others enough to form personalized follow recommendations, these accounts are recommended instead. They are re-calculated on a daily basis from a mix of accounts with the highest recent engagements and highest local follower counts for a given language." | |
- status: Status | |
- trending: Trending | |
- all: All | |
- title: Trending truths | |
+ group_memberships: | |
+ title: Group members | |
+ group_statuses: | |
+ back_to_group: Back to group page | |
+ title: Group posts | |
+ groups: | |
+ group: Group | |
+ removed_avatar_msg: Successfully removed this group's avatar image | |
+ removed_header_msg: Successfully removed this group's header image | |
+ suspended_msg: Successfully suspended this group | |
+ suspension_irreversible: The data of this group has been irreversibly deleted. You can unsuspend the group to make it usable but it will not recover any data it previously had. | |
+ suspension_reversible_hint_html: The group has been suspended, and the data will be fully removed on %{date}. Until then, the group can be restored without any ill effects. If you wish to remove all of the group's data immediately, you can do so below. | |
+ title: Groups | |
+ unsuspended_msg: Successfully unsuspended this group | |
instances: | |
back_to_all: All | |
back_to_limited: Limited | |
@@ -499,6 +514,87 @@ | |
title: Moderation | |
private_comment: Private comment | |
public_comment: Public comment | |
+ rules: | |
+ content_illegal_activity: | |
+ text: Illegal activity and behavior | |
+ subtext: Content that depicts illegal or criminal acts, threats of violence. | |
+ content_intellectual_infringement: | |
+ text: Intellectual property infringement | |
+ subtext: Impersonating another account or business, infringing on intellectual property | |
+ rights. | |
+ content_sensitive_content: | |
+ text: Sensitive Content | |
+ subtext: Depictions of violence, gore, nudity. | |
+ content_underage_content: | |
+ text: Underage content | |
+ subtext: Sexually explicit content involving underage children. | |
+ content_prostitution: | |
+ text: Prostitution | |
+ subtext: Solicitation or advertising for illegal sexual activity or sex for hire. | |
+ content_privacy_violations: | |
+ text: Privacy violations | |
+ subtext: Violate or post content that violates a person's privacy rights. | |
+ content_illegal_sales: | |
+ text: Illegal sales | |
+ subtext: 'Sale of or promotion of illegal drugs, counterfeit services and goods, | |
+ or illegal products and services. ' | |
+ content_doxxing: | |
+ text: Doxxing | |
+ subtext: Sharing or threatening to share the private information of an individual | |
+ without their consent or breach of privacy rights of others. | |
+ content_spam: | |
+ text: Spam | |
+ subtext: Fraudulent or malicious content or links, inauthentic engagement, repetitive | |
+ replies, ReTruths, or direct messages. | |
+ content_dislike: | |
+ text: I don't like this content | |
+ subtext: I dislike this content and/or this user is a troll. | |
+ account_illegal_activity: | |
+ text: Illegal activity and behavior | |
+ subtext: Content that depicts illegal or criminal acts, threats of violence. | |
+ account_sensitive_content: | |
+ text: Sensitive Content | |
+ subtext: Depictions of violence, gore, nudity. | |
+ account_spam: | |
+ text: SPAM | |
+ subtext: Fraudulent content, phishing, or content that is spam or a bot. | |
+ account_fake_account: | |
+ text: Fake Account | |
+ subtext: Improper use of username/account to masquerade as someone else or impersonate another person or entity that is not a parody. | |
+ account_intellectual_property: | |
+ text: Intellectual Property | |
+ subtext: Account name or entity infringes on intellectual property rights without | |
+ authorization. | |
+ account_underage_content: | |
+ text: Underage content | |
+ subtext: Sexually explicit content involving underage children. | |
+ account_prostitution: | |
+ text: Prostitution | |
+ subtext: Solicitation or advertising for illegal sexual activity or sex for hire. | |
+ account_privacy_violations: | |
+ text: Privacy violations | |
+ subtext: Violate or post content that violates a person's privacy rights. | |
+ account_troll: | |
+ text: Troll | |
+ subtext: This user is a troll and/or I dislike their content. | |
+ group_illegal_activity: | |
+ text: Illegal Activity and Behavior | |
+ subtext: 'This group is participating, encouraging or depicting illegal or criminal acts such as: threats, violence, incitement of violence, doxxing or harrassment.' | |
+ group_spam: | |
+ text: Spam | |
+ subtext: Fraudulent content, phishing or content that is spam or a bot. | |
+ group_impersonation: | |
+ text: Impersonation | |
+ subtext: This group is claiming to represent a person/entity that they aren't authorized to represent. | |
+ group_underage_content: | |
+ text: Underage Content | |
+ subtext: This group has posted sexually explicit content involving underage children. | |
+ group_prostitution: | |
+ text: Prostitution | |
+ subtext: This group is soliciting or advertising for illegal sexual activity or sex for hire. | |
+ group_privacy_violations: | |
+ text: Privacy Violations | |
+ subtext: This group has violated mine or someone else's privacy rights, including doxxing. | |
title: Federation | |
total_blocked_by_us: Blocked by us | |
total_followed_by_them: Followed by them | |
@@ -840,6 +936,14 @@ | |
hint_html: "<strong>Tip:</strong> We won't ask you for your password again for the next hour." | |
invalid_password: Invalid password | |
prompt: Confirm password to continue | |
+ chats: | |
+ errors: | |
+ creator_started: Cannot accept this chat since you started it | |
+ blocked_constraints: Cannot perform request due to blocked constraints | |
+ unfollowed_and_left_chat_by_user: This user has left the chat and no longer follows you | |
+ message_creation: An error occurred while creating message | |
+ channel_reaction: Reactions are not supported for channels | |
+ marketing_reply: Truth Social cannot receive direct messages. To contact us, email [email protected]! | |
crypto: | |
errors: | |
invalid_key: is not a valid Ed25519 or Curve25519 key | |
@@ -900,6 +1004,22 @@ | |
title: This page is not correct | |
'503': The page could not be served due to a temporary server failure. | |
noscript_html: To use the Truth web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Truth for your platform. | |
+ api: | |
+ '401': This method requires an authenticated user | |
+ '403': This action is not allowed | |
+ '404': Record not found | |
+ '503': There was a temporary problem serving your request, please try again | |
+ assertion: Unable to verify assertion | |
+ attestation: Unable to verify attestation | |
+ data_fetch: Remote data could not be fetched | |
+ duplicate: Duplicate record | |
+ login_disabled: Your login is currently disabled | |
+ login_pending: Your login is currently pending approval | |
+ sms_reverification_pending: Your account is currently pending SMS verification | |
+ missing_email: Your login is missing a confirmed e-mail address | |
+ ssl: Remote SSL certificate could not be verified | |
+ outside_scopes: This action is outside the authorized scopes | |
+ unauthorized: Not authorized | |
existing_username_validator: | |
not_found: could not find a local user with that username | |
not_found_multiple: could not find %{usernames} | |
@@ -923,6 +1043,10 @@ | |
errors: | |
limit: You have already featured the maximum amount of hashtags | |
hint_html: "<strong>What are featured hashtags?</strong> They are displayed prominently on your public profile and allow people to browse your public posts specifically under those hashtags. They are a great tool for keeping track of creative works or long-term projects." | |
+ feeds: | |
+ errors: | |
+ feed_creation_limit: Feed Limit Reached. You've reached the maximum number of custom feeds. Delete a feed to add another. | |
+ too_many_pinned: Feed Pinned Limit Reached. You've reached the maximum number of pinned feeds. Unpin a feed to pin another. | |
filters: | |
contexts: | |
account: Profiles | |
@@ -957,6 +1081,22 @@ | |
validation_errors: | |
one: Something isn't quite right yet! Please review the error below | |
other: Something isn't quite right yet! Please review %{count} errors below | |
+ groups: | |
+ errors: | |
+ unsupported_remote_role: Moderation roles are not supported for remote users yet | |
+ invalid_name: Please remove invalid characters | |
+ group_taken: This group name is taken | |
+ too_many_tags: 'Validation failed: Tags cannot exceed 3' | |
+ group_creation_limit: You've reached the group creation threshold | |
+ group_membership_limit: You've reached the group membership threshold | |
+ invalid_group_tag: 'Validation failed: Tag is invalid' | |
+ group_deleted: This group no longer exists | |
+ pending_request_conflict: Group owner or admin has already taken action on this request | |
+ too_many_admins: You can assign up to %{count} admins for the group at this time. | |
+ members: | |
+ one: Member | |
+ other: Members | |
+ members_of: Members of group %{name} | |
html_validator: | |
invalid_markup: 'contains invalid HTML markup: %{error}' | |
identity_proofs: | |
@@ -1023,6 +1163,31 @@ | |
invite_pending: Invite pending | |
invite_redemed: Invite accepted | |
title: Invite people | |
+ links: | |
+ title: | |
+ default: "Are you sure you want to leave Truth Social?" | |
+ blocked: "The Site Ahead Contains Malware" | |
+ missing: "Page Not Found" | |
+ warning: "Warning: Potential Security Risk Ahead" | |
+ summary: | |
+ default: | | |
+ This link is taking you to a site outside of Truth Social | |
+ blocked: | | |
+ Attackers currently on %{url} might attempt to install dangerous programs on your device | |
+ that steal or delete your information (for example, photos, passwords, messages and | |
+ credit cards.) | |
+ missing: | | |
+ Sorry, the link you requested does not exist. | |
+ warning: | | |
+ Truth Social detected a potential security threat and did not continue to | |
+ %{url}. If you visit this site, attackers | |
+ could try to steal information like your passwords, emails, or | |
+ credit card details. | |
+ actions: | |
+ continue: Accept the risk and continue | |
+ go_back: Back to Truth Social | |
+ go_back_recommended: Back to Truth Social (recommended) | |
+ go_to_site: Go to Site | |
lists: | |
errors: | |
limit: You have reached the maximum amount of lists | |
@@ -1069,6 +1234,10 @@ | |
carry_mutes_over_text: This user moved from %{acct}, which you had muted. | |
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:' | |
notification_mailer: | |
+ chat: | |
+ subject: "New message from %{name}" | |
+ subject_android: "sent you a message" | |
+ sent_message: "Sent you a message" | |
digest: | |
action: View all notifications | |
body: Here is a brief summary of the messages you missed since your last visit on %{since} | |
@@ -1079,46 +1248,112 @@ | |
subject: | |
one: "1 new notification since your last visit \U0001F418" | |
other: "%{count} new notifications since your last visit \U0001F418" | |
+ subject_android: | |
+ one: "1 new notification since your last visit \U0001F418" | |
+ other: "%{count} new notifications since your last visit \U0001F418" | |
title: In your absence... | |
favourite: | |
body: 'Your post was liked by %{name}:' | |
- subject: "%{name} liked your post" | |
+ subject: "%{name} liked your Truth" | |
+ subject_android: "liked your Truth" | |
title: New favorite | |
favourite_group: | |
subject: "%{name} + %{count_others} %{actor} liked your Truth" | |
+ subject_android: "%{name} and %{count_others} %{actor} liked your Truth" | |
follow: | |
body: "%{name} is now following you!" | |
subject: "%{name} is now following you" | |
+ subject_android: "is now following you" | |
title: New follower | |
+ group_favourite: | |
+ body: "%{name} liked your Truth in %{group}" | |
+ subject: "%{name} liked your Truth in %{group}" | |
+ subject_android: "liked your Truth in %{group}" | |
+ title: New favorite | |
+ group_favourite_group: | |
+ subject: "%{name} + %{count_others} %{actor} liked your Truth in %{group}" | |
+ subject_android: "%{name} and %{count_others} %{actor} liked your Truth in %{group}" | |
follow_group: | |
subject: "%{name} + %{count_others} %{actor} followed you" | |
+ subject_android: "%{name} and %{count_others} %{actor} followed you" | |
follow_request: | |
action: Manage follow requests | |
body: "%{name} has requested to follow you" | |
subject: 'Pending follower: %{name}' | |
+ subject_android: 'requested to follow you' | |
title: New follow request | |
mention: | |
action: Reply | |
- body: 'You were mentioned by %{name} in:' | |
+ body: 'You were mentioned by %{name}' | |
subject: You were mentioned by %{name} | |
+ subject_android: "mentioned you in a Truth" | |
title: New mention | |
mention_group: | |
subject: "%{name} + %{count_others} %{actor} mentioned you" | |
+ subject_android: "%{name} and %{count_others} %{actor} mentioned you" | |
+ group_mention: | |
+ action: Reply | |
+ body: 'You were mentioned by %{name} in %{group}' | |
+ subject: 'You were mentioned by %{name} in %{group}' | |
+ subject_android: 'mentioned you in %{group}' | |
+ title: New mention | |
+ group_mention_group: | |
+ subject: "%{name} + %{count_others} %{actor} mentioned you" | |
+ subject_android: "%{name} and %{count_others} %{actor} mentioned you" | |
poll: | |
subject: A poll by %{name} has ended | |
+ subject_android: 'concluded a poll' | |
reblog: | |
body: 'Your post was ReTruthed by %{name}:' | |
- subject: "%{name} ReTruthed your post" | |
+ subject: "%{name} ReTruthed your Truth" | |
+ subject_android: "ReTruthed your Truth" | |
title: New ReTruth | |
reblog_group: | |
subject: "%{name} + %{count_others} %{actor} ReTruthed your Truth" | |
+ subject_android: "%{name} and %{count_others} %{actor} ReTruthed your Truth" | |
+ group_delete: | |
+ body: "%{group} has been deleted" | |
+ subject: "%{group} has been deleted" | |
+ subject_android: "has been deleted" | |
+ title: Group Deleted | |
+ group_approval: | |
+ body: "%{group} approved your request" | |
+ subject: "%{group} approved your request" | |
+ subject_android: "approved your request" | |
+ title: Accepted to group | |
+ group_reblog: | |
+ body: "%{name} ReTruthed your Truth from %{group}" | |
+ subject: "%{name} ReTruthed your Truth from %{group}" | |
+ subject_android: "ReTruthed your Truth from %{group}" | |
+ title: New ReTruth | |
+ group_reblog_group: | |
+ subject: "%{name} + %{count_others} %{actor} ReTruthed your group Truth" | |
+ subject_android: "%{name} and %{count_others} %{actor} ReTruthed your group Truth" | |
+ group_request: | |
+ body: "You have a new member request for %{group}" | |
+ subject: "You have a new member request for %{group}" | |
+ subject_android: "you have a new member request" | |
+ title: New member request | |
+ group_promoted: | |
+ body: You are now an admin for %{group} | |
+ subject: You are now an admin for %{group} | |
+ subject_android: you are now an admin | |
+ title: You're an admin | |
+ group_demoted: | |
+ body: You are no longer an admin for %{group} | |
+ subject: You are no longer an admin for %{group} | |
+ subject_android: you are no longer admin | |
+ title: You're no longer an admin | |
status: | |
subject: "%{name} just posted" | |
+ subject_android: "just posted" | |
user_approved: | |
edit_profile_action: Go to profile | |
edit_profile_step: Lastly, make sure to customize your profile by uploading an avatar, header, changing your display name and more. If you’d like to review new followers before they’re allowed to follow you, you can lock your account. | |
- explanation: We are very excited to welcome you to our Truth Seeking community. We believe in free expression and encourage all viewpoints as we do not discriminate against political ideology. | |
- extra_step: Also, a friendly reminder, we are a new platform, and we are still fixing many bugs in our technology. We promise we are working as hard as we can to make things better as fast as possible! Thanks! | |
+ explanation: Let us be the first to welcome you to Truth Social! We're thrilled you've joined our growing free speech community and support our goal to keep the Internet open for everyone. | |
+ extra_step: If you ever have a question about how to use the app, please visit our %{help_center_link} or feel free to email our Support Team at %{mailto_support}. Happy Truthing! | |
+ signoff: The Truth Social Team | |
+ help_center: Help Center | |
final_action: Start posting | |
final_step: 'Start posting! Even without followers your public posts may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.' | |
full_handle: Your full handle | |
@@ -1126,16 +1361,16 @@ | |
review_preferences_action: Change preferences | |
review_preferences_step: Make sure to set your preferences, such as which emails you'd like to receive, or what privacy level you’d like your posts to default to. If you don’t have motion sickness, you could choose to enable GIF autoplay. | |
subject: "Your wait is over! Tap here to start using Truth Social." | |
- web: | |
- subject: "Welcome to Truth" | |
+ subject_android: "Your wait is over! Tap here to start using Truth Social." | |
tip_federated_timeline: The federated timeline is a firehose view of the Truth network. But it only includes people your neighbours are subscribed to, so it's not complete. | |
tip_following: You follow your server's admin(s) by default. To find more interesting people, check the local and federated timelines. | |
tip_local_timeline: The local timeline is a firehose view of people on %{instance}. These are your immediate neighbours! | |
tip_mobile_webapp: If your mobile browser offers you to add Truth to your homescreen, you can receive push notifications. It acts like a native app in many ways! | |
tips: Tips | |
- title: Welcome to Truth Social, %{name}! | |
+ title: Welcome to Truth Social, @%{name}! | |
verify_sms_prompt: | |
subject: Your account is now active on Truth Social! Upgrade to the latest version and join us now! | |
+ subject_android: Your account is now active on Truth Social! Upgrade to the latest version and join us now! | |
notifications: | |
email_events: Events for e-mail notifications | |
email_events_hint: 'Select events that you want to receive notifications for:' | |
@@ -1159,6 +1394,7 @@ | |
manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:' | |
setup: Set up | |
wrong_code: The entered code was invalid! | |
+ invalid_code: "The code doesn't match. Please try again." | |
pagination: | |
newer: Newer | |
next: Next | |
@@ -1303,11 +1539,20 @@ | |
one: 'contained a disallowed hashtag: %{tags}' | |
other: 'contained the disallowed hashtags: %{tags}' | |
errors: | |
- in_reply_not_found: The post you are trying to reply to does not appear to exist. | |
+ not_permitted_to_post: You cannot post to this group. | |
+ in_reply_not_found: The Truth you are trying to reply to does not appear to exist. | |
+ mention_mismatch: Unable to post your Truth at this time. | |
+ group_errors: | |
+ invalid_group_id: Invalid group identifier. | |
+ invalid_membership: Only members may post to this group. | |
+ invalid_reply: The post being replied to is not in this group. | |
+ invalid_visibility: You cannot post to this group. | |
language_detection: Automatically detect language | |
open_in_web: Open in web | |
over_character_limit: character limit of %{max} exceeded | |
pin_errors: | |
+ group: Group posts cannot be pinned | |
+ group_ownership: Only group owners can pin posts | |
limit: You have already pinned the maximum number of posts | |
ownership: Someone else's post cannot be pinned | |
private: Non-public posts cannot be pinned | |
@@ -1328,6 +1573,7 @@ | |
title: '%{name}: "%{quote}"' | |
visibilities: | |
direct: Direct | |
+ group: Group | |
private: Followers-only | |
private_long: Only show to followers | |
public: Public | |
@@ -1458,12 +1704,12 @@ | |
subject: Please confirm attempted sign in | |
title: Sign in attempt | |
status_removed: | |
- explanation: You have a Truth that has been removed for violating Truth Social community guidelines. | |
- title: Your Truth has been removed | |
- subject: Your Truth has been removed | |
+ explanation: We use artificial intelligence (AI) to assist our hardworking moderators, and some Truths are flagged for deletion or marked “sensitive” by AI. While the AI we use is very good, it is not error-proof. Assisted by technology, our moderators use their best judgment to ensure compliance with our Terms of Service. Please give our team time to review your Truth to determine whether it violates our Terms of Service. After a thorough review, we will reinstate the Truth or uphold its removal. | |
+ title: Your Truth was flagged for review | |
+ subject: Your Truth was flagged for review | |
warning: | |
explanation: | |
- ban_html: After careful review we have decided to delete your account permanently due to Truth social community guidelines violations. If you feel we have made an error, you can file an appeal with %{email}. | |
+ ban_html: Your account has been banned for violating our Terms of Service. Indefinite bans are a rare and severe sanction, and are generally reserved for the most egregious violations of our Terms of Service. If you feel our decision to ban your account was unjust, immoral, or downright wrong, we encourage you to submit an appeal. Please send an email to %{email}. In the subject line, please write "@Appeal," along with your username. Our team will review your ban appeal at our earliest convenience. After careful review, we will reverse or uphold the ban. | |
disable_html: You can no longer login to your account or use it in any other way, but your profile and other data remains intact. | |
sensitive_html: Your uploaded media files and linked media will be treated as sensitive. | |
silence_html: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various public listings. However, others may still manually follow you. | |
@@ -1502,6 +1748,8 @@ | |
invalid_otp_token: Invalid two-factor code | |
invalid_sign_in_token: Invalid security code | |
otp_lost_help_html: If you lost access to both, you may get in touch with %{email} | |
+ previously_used_password: Please use a password you have not previously used. | |
+ password_mismatch: Password and password confirmation do not match. | |
seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available. | |
signed_in_as: 'Signed in as:' | |
suspicious_sign_in_confirmation: You appear to not have logged in from this device before, and you haven't logged in for a while, so we're sending a security code to your e-mail address to confirm that it's you. | |
@@ -1525,3 +1773,7 @@ | |
not_supported: This browser doesn't support security keys | |
otp_required: To use security keys please enable two-factor authentication first. | |
registered_on: Registered on %{date} | |
+ activerecord: | |
+ attributes: | |
+ group: | |
+ slug: 'Display name' | |
diff -ru truth-old/opensource/config/locales/es-AR.yml truth-new/opensource/config/locales/es-AR.yml | |
--- truth-old/opensource/config/locales/es-AR.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/locales/es-AR.yml 2024-04-01 14:59:13 | |
@@ -942,6 +942,9 @@ | |
validation_errors: | |
one: "¡Falta algo! Por favor, revisá el error abajo" | |
other: "¡Falta algo! Por favor, revisá los %{count} errores abajo" | |
+ groups: | |
+ errors: | |
+ too_many_admins: Puede asignar hasta %{count} administradores para el grupo en este momento. | |
html_validator: | |
invalid_markup: 'contiene markup HTML no válido: %{error}' | |
identity_proofs: | |
@@ -1457,6 +1460,8 @@ | |
invalid_otp_token: Código de dos factores no válido | |
invalid_sign_in_token: Código de seguridad no válido | |
otp_lost_help_html: Si perdiste al acceso a ambos, podés ponerte en contacto con %{email} | |
+ previously_used_password: Por favor use una contraseña que no haya usado previamente. | |
+ password_mismatch: La contraseña y la confirmación de contraseña no son iguales. | |
seamless_external_login: Iniciaste sesión desde un servicio externo, así que la configuración de contraseña y correo electrónico no están disponibles. | |
signed_in_as: 'Iniciaste sesión como:' | |
suspicious_sign_in_confirmation: Parece que no iniciaste sesión desde este dispositivo antes, y no iniciaste sesión durante un tiempo, así que te estamos enviando un código de seguridad a tu dirección de correo electrónico para confirmar que sos vos. | |
diff -ru truth-old/opensource/config/locales/es-MX.yml truth-new/opensource/config/locales/es-MX.yml | |
--- truth-old/opensource/config/locales/es-MX.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/locales/es-MX.yml 2024-04-01 14:59:13 | |
@@ -942,6 +942,9 @@ | |
validation_errors: | |
one: "¡Algo no está bien! Por favor, revisa el error" | |
other: "¡Algo no está bien! Por favor, revise %{count} errores más abajo" | |
+ groups: | |
+ errors: | |
+ too_many_admins: Puede asignar hasta %{count} administradores para el grupo en este momento. | |
html_validator: | |
invalid_markup: 'contiene código HTML no válido: %{error}' | |
identity_proofs: | |
@@ -1457,6 +1460,8 @@ | |
invalid_otp_token: Código de dos factores incorrecto | |
invalid_sign_in_token: Código de seguridad no válido | |
otp_lost_help_html: Si perdiste al acceso a ambos, puedes ponerte en contancto con %{email} | |
+ previously_used_password: Por favor use una contraseña que no haya usado previamente. | |
+ password_mismatch: La contraseña y la confirmación de contraseña no son iguales. | |
seamless_external_login: Has iniciado sesión desde un servicio externo, así que los ajustes de contraseña y correo no están disponibles. | |
signed_in_as: 'Sesión iniciada como:' | |
suspicious_sign_in_confirmation: Parece que no has iniciado sesión desde este dispositivo antes, y no has iniciado sesión durante un tiempo, así que estamos enviando un código de seguridad a tu dirección de correo electrónico para confirmar que eres tú. | |
diff -ru truth-old/opensource/config/locales/es.yml truth-new/opensource/config/locales/es.yml | |
--- truth-old/opensource/config/locales/es.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/locales/es.yml 2024-04-01 14:59:13 | |
@@ -942,6 +942,9 @@ | |
validation_errors: | |
one: "¡Algo no está bien! Por favor, revisa el error" | |
other: "¡Algo no está bien! Por favor, revise %{count} errores más abajo" | |
+ groups: | |
+ errors: | |
+ too_many_admins: Puede asignar hasta %{count} administradores para el grupo en este momento. | |
html_validator: | |
invalid_markup: 'contiene código HTML no válido: %{error}' | |
identity_proofs: | |
@@ -1457,6 +1460,8 @@ | |
invalid_otp_token: Código de dos factores incorrecto | |
invalid_sign_in_token: Código de seguridad no válido | |
otp_lost_help_html: Si perdiste al acceso a ambos, puedes ponerte en contancto con %{email} | |
+ previously_used_password: Por favor use una contraseña que no haya usado previamente. | |
+ password_mismatch: La contraseña y la confirmación de contraseña no son iguales. | |
seamless_external_login: Has iniciado sesión desde un servicio externo, así que los ajustes de contraseña y correo no están disponibles. | |
signed_in_as: 'Sesión iniciada como:' | |
suspicious_sign_in_confirmation: Parece que no has iniciado sesión desde este dispositivo antes, y no has iniciado sesión durante un tiempo, así que estamos enviando un código de seguridad a tu dirección de correo electrónico para confirmar que eres tú. | |
diff -ru truth-old/opensource/config/locales/pt-BR.yml truth-new/opensource/config/locales/pt-BR.yml | |
--- truth-old/opensource/config/locales/pt-BR.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/locales/pt-BR.yml 2024-04-05 09:17:47 | |
@@ -53,6 +53,8 @@ | |
other: usuários | |
user_count_before: Casa de | |
what_is_mastodon: O que é Mastodon? | |
+ ads: | |
+ why_copy: Mostramos anúncios de produtos e serviços que achamos que nossos usuários podem gostar. | |
accounts: | |
choices_html: 'Sugestões de %{name}:' | |
endorsements_hint: Você pode sugerir pessoas que você segue, elas aparecerão aqui. | |
@@ -641,6 +643,8 @@ | |
edit_preset: Editar o aviso pré-definido | |
title: Gerenciar os avisos pré-definidos | |
admin_mailer: | |
+ account_invitation: | |
+ subject: Você foi convidado a entrar no Truth Social! | |
new_pending_account: | |
body: Os detalhes da nova conta estão abaixo. Você pode aprovar ou vetar. | |
subject: Nova conta para revisão em %{instance} (%{username}) | |
@@ -698,7 +702,7 @@ | |
didnt_get_confirmation: Não recebeu instruções de confirmação? | |
dont_have_your_security_key: Não está com a sua chave de segurança? | |
forgot_password: Esqueceu a sua senha? | |
- invalid_reset_password_token: Código de alteração de senha é inválido ou expirou. Por favor, solicite um novo. | |
+ invalid_reset_password_token: O token de redefinição de senha é inválido ou expirou. Solicite um novo token. | |
link_to_otp: Digite um código de duas etapas do seu telefone ou um código de recuperação | |
link_to_webauth: Use seu dispositivo de chave de segurança | |
login: Entrar | |
@@ -742,6 +746,11 @@ | |
hint_html: "<strong>Dica:</strong> Não pediremos novamente sua senha pela próxima hora." | |
invalid_password: Senha inválida | |
prompt: Confirme sua senha para continuar | |
+ chats: | |
+ errors: | |
+ creator_started: Não foi possível aceitar esse chat já que você o iniciou | |
+ blocked_constraints: Não foi possível executar a solicitação devido a restrições bloqueadas | |
+ unfollowed_and_left_chat_by_user: This user has left the chat and no longer follows you | |
crypto: | |
errors: | |
invalid_key: não é uma chave Ed25519 ou Curve25519 válida | |
@@ -762,11 +771,11 @@ | |
x_months: "%{count}m" | |
x_seconds: "%{count}seg" | |
deletes: | |
- challenge_not_passed: As informações que você inseriu não estão corretas | |
+ challenge_not_passed: A informação que você inseriu não está correta | |
confirm_password: Digite a sua senha atual para verificar a sua identidade | |
confirm_username: Digite seu nome de usuário para confirmar o procedimento | |
proceed: Excluir conta | |
- success_msg: A sua conta foi excluída com sucesso | |
+ success_msg: Sua conta foi excluída com sucesso | |
warning: | |
before: 'Antes de prosseguir, por favor leia com cuidado:' | |
caches: Conteúdo que foi armazenado em cache por outras instâncias pode continuar a existir | |
@@ -793,12 +802,27 @@ | |
'422': | |
content: Falha na verificação de segurança. Você está bloqueando cookies? | |
title: Falha na verificação de segurança | |
- '429': Muitas solicitações | |
+ '429': Excesso de solicitações | |
'500': | |
content: Desculpe, algo deu errado por aqui. | |
title: Esta página não está certa | |
'503': A página não pôde ser carregada devido a uma falha temporária do servidor. | |
noscript_html: Para usar o aplicativo web do Mastodon, por favor ative o JavaScript. Ou, se quiser, experimente um dos <a href="%{apps_path}">aplicativos nativos</a> para o Mastodon em sua plataforma. | |
+ api: | |
+ '401': Esse método requer um usuário autenticado | |
+ '403': Essa ação não é permitida | |
+ '404': Registro não encontrado | |
+ '503': Ocorreu um problema temporário ao atender sua solicitação. Tente novamente. | |
+ assertion: Não foi possível verificar sua afirmação | |
+ attestation: Não foi possível verificar seu atestado | |
+ data_fetch: Não foi possível resgatar os dados remotos | |
+ duplicate: Registro duplicado | |
+ login_disabled: No momento seu login está desativado | |
+ login_pending: No momento seu login está aguardando aprovação | |
+ missing_email: Está faltando um endereço de e-mail confirmado para o seu login | |
+ ssl: Não foi possível verificar o Certificado SSL remoto | |
+ outside_scopes: Essa ação está fora do escopo autorizado | |
+ unauthorized: Não autorizado | |
existing_username_validator: | |
not_found: não foi possível encontrar um usuário local com esse nome de usuário | |
not_found_multiple: não foi possível encontrar %{usernames} | |
@@ -846,7 +870,7 @@ | |
trending_now: Em alta no momento | |
generic: | |
all: Tudo | |
- changes_saved_msg: Alterações foram salvas com sucesso! | |
+ changes_saved_msg: Alterações salvas com sucesso! | |
copy: Copiar | |
delete: Excluir | |
no_batch_actions_available: Nenhuma ação em lote disponível nesta página | |
@@ -855,6 +879,10 @@ | |
validation_errors: | |
one: Algo errado não está certo! Por favor, analise o erro abaixo | |
other: Algo errado não está certo! Por favor, analise os %{count} erros abaixo | |
+ groups: | |
+ errors: | |
+ pending_request_conflict: O proprietário ou administrador do grupo já tomou medidas sobre esta solicitação. | |
+ too_many_admins: Você pode atribuir até %{count} administradores para o grupo. | |
html_validator: | |
invalid_markup: 'contém HTML inválido: %{error}' | |
identity_proofs: | |
@@ -920,9 +948,9 @@ | |
limit: Você atingiu o máximo de listas | |
media_attachments: | |
validations: | |
- images_and_video: Não foi possível anexar um vídeo a um toot que já contém imagens | |
- not_ready: Não é possível anexar arquivos que não terminaram de ser processados. Tente novamente daqui a pouco! | |
- too_many: Não foi possível anexar mais de 4 imagens | |
+ images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens | |
+ not_ready: Não é possível anexar arquivos cujo processamento ainda não foi concluído. Tente novamente daqui a pouco! | |
+ too_many: Não é possível anexar mais de 4 arquivos | |
migrations: | |
acct: Mudou-se para | |
cancel: Cancelar redirecionamento | |
@@ -961,39 +989,133 @@ | |
carry_mutes_over_text: Este usuário mudou de %{acct}, que você havia silenciado. | |
copy_account_note_text: 'Este usuário saiu de %{acct}, aqui estão suas notas anteriores sobre ele:' | |
notification_mailer: | |
+ chat: | |
+ subject: "Nova mensagem de %{name}" | |
+ subject_android: "lhe enviou uma mensagem" | |
+ sent_message: "Nova mensagem" | |
digest: | |
action: Ver todas as notificações | |
- body: Aqui está um breve resumo das mensagens que você perdeu desde o seu último acesso em %{since} | |
- mention: "%{name} te mencionou em:" | |
+ body: Aqui está um breve resumo das mensagens que você não viu desde seu último acesso em %{since} | |
+ mention: "%{name} mencionou você em:" | |
new_followers_summary: | |
- one: Você tem um novo seguidor! Uia! | |
- other: Você tem %{count} novos seguidores! AÊÊÊ! | |
+ one: Além disso, enquanto estava fora você ganhou um novo seguidor! Oba! | |
+ other: Além disso, enquanto estava fora você ganhou %{count} novos seguidores! Incrível! | |
subject: | |
- one: "Uma nova notificação desde o seu último acesso \U0001F418" | |
- other: "%{count} novas notificações desde o seu último acesso \U0001F418" | |
- title: Enquanto você estava ausente... | |
+ one: "1 nova notificação desde seu último acesso \U0001F418" | |
+ other: "%{count} novas notificações desde seu último acesso \U0001F418" | |
+ subject_android: | |
+ one: "1 nova notificação desde seu último acesso \U0001F418" | |
+ other: "%{count} novas notificações desde seu último acesso \U0001F418" | |
+ title: Na sua ausência... | |
favourite: | |
- body: "%{name} favoritou seu toot:" | |
- subject: "%{name} favoritou seu toot" | |
+ body: "Sua postagem foi curtida por %{name}:" | |
+ subject: "%{name} curtiu sua postagem" | |
+ subject_android: "gostou da sua Truth" | |
title: Novo favorito | |
+ favourite_group: | |
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth" | |
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth" | |
+ group_favourite: | |
+ body: "Sua postagem foi curtida por %{name}:" | |
+ subject: "%{name} curtiu sua postagem" | |
+ subject_android: "gostou da sua Truth em %{group}" | |
+ title: Novo favorito | |
+ group_favourite_group: | |
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth" | |
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth em %{group}" | |
follow: | |
- body: "%{name} te seguiu!" | |
- subject: "%{name} te seguiu" | |
+ body: "%{name} está seguindo você!" | |
+ subject: "%{name} está seguindo você" | |
+ subject_android: "está te seguindo agora" | |
title: Novo seguidor | |
+ follow_group: | |
+ subject: "%{name} + %{count_others} %{actor} seguiram você" | |
+ subject_android: "%{name} e %{count_others} outros seguiram você" | |
follow_request: | |
- action: Gerenciar seguidores pendentes | |
- body: "%{name} quer te seguir" | |
- subject: 'Seguidor pendente: %{name}' | |
- title: Novo seguidor pendente | |
+ action: Gerenciar solicitações de seguidores | |
+ body: "%{name} pediu para seguir você" | |
+ subject: 'Seguidor aguardando: %{name}' | |
+ subject_android: "pediu para te seguir" | |
+ title: Nova solicitação de seguidor | |
mention: | |
action: Responder | |
- body: "%{name} te mencionou em:" | |
- subject: "%{name} te mencionou" | |
+ body: "Você foi mencionado por %{name} em:" | |
+ subject: "Você foi mencionado por %{name}" | |
+ subject_android: "mencionou você em uma Truth" | |
title: Nova menção | |
+ mention_group: | |
+ subject: "%{name} + %{count_others} %{actor} mencionaram você" | |
+ subject_android: "%{name} e %{count_others} outros mencionaram você" | |
+ group_mention: | |
+ action: Responder | |
+ body: "Você foi mencionado por %{name} em:" | |
+ subject: "Você foi mencionado por %{name}" | |
+ subject_android: "mencionou você em %{group}" | |
+ title: Nova menção | |
+ group_mention_group: | |
+ subject: "%{name} + %{count_others} %{actor} mencionaram você" | |
+ subject_android: "%{name} e %{count_others} outros mencionaram você" | |
+ poll: | |
+ subject: Uma enquete postada por %{name} foi encerrada | |
+ subject_android: "concluiu uma enquete" | |
reblog: | |
- body: "%{name} deu boost no seu toot:" | |
- subject: "%{name} deu boost no seu toot" | |
- title: Novo boost | |
+ body: "Sua Truth foi RePostada por %{name}:" | |
+ subject: "%{name} RePostou sua Truth" | |
+ subject_android: "ReTruthed sua Truth" | |
+ title: Nova RePostagem | |
+ reblog_group: | |
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth" | |
+ subject_android: "%{name} e mais %{count_others} outros ReTruthed sua Truth" | |
+ group_reblog: | |
+ body: "Sua Truth foi RePostada por %{name}:" | |
+ subject: "%{name} RePostou sua Truth" | |
+ subject_android: "ReTruthed sua Truth de %{group}" | |
+ title: Nova RePostagem | |
+ group_reblog_group: | |
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth" | |
+ subject_android: "%{name} e %{count_others} outros ReTruthed a sua Truth de grupo" | |
+ group_request: | |
+ body: '%{name} wants to join your group:' | |
+ subject: "%{name} wants to join your group" | |
+ subject_android: "você tem uma nova solicitação de membro" | |
+ title: Group Join Request | |
+ status: | |
+ subject: "%{name} acabou de postar" | |
+ subject_android: "acabou de postar" | |
+ group_promoted: | |
+ body: You are now an admin for %{group} | |
+ subject: You are now an admin for %{group} | |
+ subject_android: "agora você é um administrador" | |
+ title: You're an admin | |
+ group_demoted: | |
+ body: You are no longer an admin for %{group} | |
+ subject: You are no longer an admin for %{group} | |
+ subject_android: "você não é mais administrador" | |
+ title: You're no longer an admin | |
+ user_approved: | |
+ edit_profile_action: Ir para o perfil | |
+ edit_profile_step: Para terminar, não deixe de personalizar seu perfil carregando um avatar, um título, mudando seu nome escolhido e muito mais. Se quiser avaliar novos seguidores antes de aprová-los, você pode bloquear sua conta. | |
+ explanation: Estamos super empolgados com sua chegada à nossa comunidade de Buscadores da Verdade. Acreditamos na liberdade de expressão e incentivamos todos os pontos de vista, já que não discriminamos ninguém devido a uma ideologia política. | |
+ extra_step: Além disso, gostaríamos de lembrar que somos uma plataforma nova e ainda estamos corrigindo vários bugs na nossa tecnologia. Podemos garantir que estamos trabalhando duro para aprimorar tudo o mais rápido possível! Obrigado! | |
+ final_action: Comece a postar | |
+ final_step: 'Comece a postar! Mesmo sem nenhum seguidor suas postagens públicas podem ser vistas por outras pessoas, por exemplo, na timeline local e nas hashtags. Você pode querer se apresentar com a hashtag #apresentações.' | |
+ full_handle: Seu identificador completo (handle) | |
+ full_handle_hint: É o que você quer dizer aos seus amigos para que possam seguir você ou lhe enviar mensagens de outro servidor. | |
+ review_preferences_action: Mudar preferências | |
+ review_preferences_step: Não deixe de configurar suas preferências, como os e-mails que quer receber ou o nível de privacidade padrão para as suas postagens. Se não costuma sentir enjoo você pode optar por reproduzir GIFs automaticamente. | |
+ subject: "Sua espera terminou! Toque aqui para começar a usar o Truth Social." | |
+ subject_android: "Sua espera terminou! Toque aqui para começar a usar o Truth Social." | |
+ web: | |
+ subject: "Bem-vindo(a) ao Truth, onde a Verdade impera" | |
+ tip_federated_timeline: A timeline federada é uma ampla visão ao vivo da rede do Truth. Mas só inclui as pessoas que seus vizinhos estão seguindo, então não é uma visão completa. | |
+ tip_following: Você segue os administradores do seu servidor por padrão Para encontrar mais gente interessante, verifique sua timeline local e federada. | |
+ tip_local_timeline: A timeline local é uma visão ao vivo das pessoas em %{instance}. São seus vizinhos de porta! | |
+ tip_mobile_webapp: Se o seu navegador móvel sugerir que você adicione o Truth à sua tela inicial, você poderá receber notificações por push. O Truth funciona de muitas maneiras como um aplicativo nativo! | |
+ tips: Dicas | |
+ title: Bem-vindo(a) ao Truth Social, %{name}! | |
+ verify_sms_prompt: | |
+ subject: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma! | |
+ subject_android: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma! | |
notifications: | |
email_events: Eventos para notificações por e-mail | |
email_events_hint: 'Selecione os eventos que deseja receber notificações:' | |
@@ -1014,7 +1136,7 @@ | |
instructions_html: "<strong>Escaneie este código QR no Google Authenticator ou em um aplicativo TOTP similar no seu telefone</strong>. A partir de agora, esse aplicativo irá gerar tokens que você terá que digitar ao fazer login." | |
manual_instructions: 'Se você não pode escanear o código QR e precisa digitá-lo manualmente, aqui está o segredo em texto:' | |
setup: Configurar | |
- wrong_code: O código digitado é inválido! O horário do servidor e o horário do dispositivo estão corretos? | |
+ wrong_code: O código que você inseriu é inválido! | |
pagination: | |
newer: Mais novo | |
next: Próximo | |
@@ -1108,7 +1230,7 @@ | |
windows_mobile: Windows Mobile | |
windows_phone: Windows Phone | |
revoke: Fechar | |
- revoke_success: Sessão fechada com sucesso | |
+ revoke_success: A sessão foi revogada com sucesso | |
title: Sessões | |
settings: | |
account: Conta | |
@@ -1150,7 +1272,7 @@ | |
one: 'continha hashtag não permitida: %{tags}' | |
other: 'continha hashtags não permitidas: %{tags}' | |
errors: | |
- in_reply_not_found: O toot que você quer responder parece não existir. | |
+ in_reply_not_found: Parece que a postagem à qual você está tentando responder não existe. | |
language_detection: Detectar idioma automaticamente | |
open_in_web: Abrir no navegador | |
over_character_limit: limite de caracteres de %{max} excedido | |
@@ -1279,7 +1401,7 @@ | |
two_factor_authentication: | |
add: Adicionar | |
disable: Desativar | |
- disabled_success: Autenticação de dois fatores desabilitada com sucesso | |
+ disabled_success: Autenticação de dois fatores desativada com sucesso | |
edit: Editar | |
enabled: Autenticação de dois fatores ativada | |
enabled_success: Autenticação de dois fatores ativada com sucesso | |
@@ -1288,28 +1410,34 @@ | |
methods: Métodos de dois fatores | |
otp: Aplicativo autenticador | |
recovery_codes: Códigos de recuperação de reserva | |
- recovery_codes_regenerated: Códigos de recuperação gerados com sucesso | |
+ recovery_codes_regenerated: Códigos de recuperação regerados com sucesso | |
recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. <strong>Mantenha os códigos de recuperação em um local seguro</strong>. Por exemplo, você pode imprimi-los e guardá-los junto com outros documentos importantes. | |
webauthn: Chaves de segurança | |
user_mailer: | |
backup_ready: | |
explanation: Você pediu um backup completo da sua conta no Mastodon. E agora está pronto para ser baixado! | |
- subject: Seu arquivo está pronto para ser baixado | |
+ subject: Seu arquivo está pronto para download | |
title: Baixar arquivo | |
sign_in_token: | |
details: 'Aqui estão os detalhes da tentativa:' | |
explanation: 'Detectamos uma tentativa de acessar sua conta a partir de um endereço IP não reconhecido. Se for você, insira o código de segurança abaixo na página de desafio:' | |
further_actions: 'Se não foi você, por favor mude sua senha e ative a autenticação de dois fatores em sua conta. Você pode fazê-lo aqui:' | |
- subject: Por favor, confirme a tentativa de acesso | |
+ subject: Confirme a tentativa de login | |
title: Tentativa de acesso | |
+ status_removed: | |
+ explanation: We use artificial intelligence (AI) to assist our hardworking moderators, and some Truths are flagged for deletion or marked “sensitive” by AI. While the AI we use is very good, it is not error-proof. Assisted by technology, our moderators use their best judgment to ensure compliance with our Terms of Service. Please give our team time to review your Truth to determine whether it violates our Terms of Service. After a thorough review, we will reinstate the Truth or uphold its removal. | |
+ title: Your Truth was flagged for review | |
+ subject: Sua Truth foi sinalizada para revisão | |
warning: | |
explanation: | |
- disable: Enquanto sua conta está congelada, seus dados de conta permanecem intactos, mas você não pode realizar nenhuma ação até que esteja destrancada. | |
- sensitive: Seus arquivos de mídia carregados e mídias vinculadas serão tratados como sensíveis. | |
- silence: Enquanto sua conta está silenciada, somente pessoas que já estão seguindo você poderão ver seus toots nessa instância, e você pode ser excluído de várias listas públicas. No entanto, outros ainda podem te seguir manualmente. | |
- suspend: Sua conta foi banida e todos os seus toots e mídias foram irreversivelmente excluídos desta instância e das instâncias dos seus seguidores. | |
- verify: Você é um membro certificado da comunidade | |
- unverify: Você não é mais um membro certificado da comunidade. | |
+ ban_html: Sua conta foi banida por violar nossos Termos de Serviço. Banimentos sem prazo definido são um tipo de sanção raro e muito grave, geralmente aplicados somente às mais sérias violações de nossos Termos de Serviço. Se você acredita que nossa decisão de banir sua conta foi injusta, imoral ou simplesmente errada, recomendamos que entre com um recurso. Envie um e-mail para %{email}. Na linha de assunto, escreva "@Appeal" junto com seu nome de usuário. Aguarde 48 horas para que nossa equipe avalie seu recurso contra o banimento. Após uma avaliação cuidadosa, decidiremos reverter ou manter o banimento. | |
+ disable_html: Você não pode mais entrar na sua conta nem usá-la de qualquer forma que seja, mas seu perfil e seus outros dados permanecerão intactos. | |
+ sensitive_html: Os arquivos de mídia que você carregou e os links de mídia que publicou serão tratados como confidenciais. | |
+ silence_html: Você continua podendo usar sua conta, mas apenas as pessoas que já seguem você verão suas postagens nesse servidor. Além disso, você poderá ser excluído de várias listagens públicas. No entanto, as pessoas continuam podendo seguir você manualmente. | |
+ suspend_html: Sua conta foi suspensa e ficará inacessível por %{suspension_duration}. | |
+ suspend_indefinite_html: Sua conta foi suspensa e ficará inacessível. | |
+ verify_html: Você é um membro certificado da comunidade | |
+ unverify_html: Você não é mais um membro certificado da comunidade | |
get_in_touch: Você pode responder a este e-mail para entrar em contato com a equipe de %{instance}. | |
review_server_policies: Revisar as políticas da instância | |
statuses: 'Especificamente, para:' | |
@@ -1329,6 +1457,8 @@ | |
suspend: Conta banida | |
verify: Conta verificada | |
unverify: Verificação de conta removida | |
+ waitlisted: | |
+ title: Sua conta foi criada com sucesso! | |
welcome: | |
edit_profile_action: Configurar perfil | |
edit_profile_step: Você pode personalizar o seu perfil enviando um avatar, uma capa, alterando seu nome de exibição e etc. Se você preferir aprovar seus novos seguidores antes de eles te seguirem, você pode trancar a sua conta. | |
@@ -1350,8 +1480,10 @@ | |
follow_limit_reached: Você não pode seguir mais de %{limit} pessoas | |
generic_access_help_html: Problemas para acessar sua conta? Você pode entrar em contato com %{email} para obter ajuda | |
invalid_otp_token: Código de dois fatores inválido | |
- invalid_sign_in_token: Cógido de segurança inválido | |
+ invalid_sign_in_token: Código de segurança inválido | |
otp_lost_help_html: Se você perder o acesso à ambos, você pode entrar em contato com %{email} | |
+ previously_used_password: Por favor, use uma senha que você não usou anteriormente. | |
+ password_mismatch: A senha e a confirmação da senha não coincidem. | |
seamless_external_login: Você entrou usando um serviço externo, então configurações de e-mail e senha não estão disponíveis. | |
signed_in_as: 'Entrou como:' | |
suspicious_sign_in_confirmation: Parece que você não fez login deste dispositivo antes, e você não fez login por um tempo. Portanto, estamos enviando um código de segurança para o seu endereço de e-mail para confirmar que é você. | |
diff -ru truth-old/opensource/config/locales/pt-PT.yml truth-new/opensource/config/locales/pt-PT.yml | |
--- truth-old/opensource/config/locales/pt-PT.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/locales/pt-PT.yml 2024-04-05 09:17:47 | |
@@ -1,87 +1,89 @@ | |
--- | |
-pt-PT: | |
+pt-BR: | |
about: | |
- about_hashtag_html: Estes são toots públicos marcados com <strong>#%{hashtag}</strong>. Podes interagir com eles se tiveres uma conta Mastodon. | |
- about_mastodon_html: Mastodon é uma rede social baseada em protocolos abertos da web e software livre e gratuito. É descentralizado como e-mail. | |
- about_this: Sobre esta instância | |
- active_count_after: activo | |
- active_footnote: Utilizadores activos mensais (UAM) | |
+ about_hashtag_html: Estes são toots públicos com a hashtag <strong>#%{hashtag}</strong>. Você pode interagir com eles se tiver uma conta em qualquer lugar no fediverso. | |
+ about_mastodon_html: 'A rede social do futuro: Sem anúncios, sem vigilância corporativa, com design ético e muita descentralização! Possua seus próprios dados com o Mastodon!' | |
+ about_this: Sobre | |
+ active_count_after: ativo | |
+ active_footnote: Usuários Ativos Mensalmente (UAM) | |
administered_by: 'Administrado por:' | |
api: API | |
- apps: Aplicações móveis | |
- apps_platforms: Usar o Mastodon a partir do iOS, Android e outras plataformas | |
- browse_directory: Navegue pelo directório de perfis e filtre por interesses | |
- browse_local_posts: Visualize as publicações públicas desta instância em tempo real | |
- browse_public_posts: Visualize as publicações públicas do Mastodon em tempo real | |
- contact: Contacto | |
- contact_missing: Não configurado | |
- contact_unavailable: n.d. | |
- discover_users: Descobrir utilizadores | |
+ apps: Aplicativos | |
+ apps_platforms: Use o Mastodon a partir do iOS, Android e outras plataformas | |
+ browse_directory: Navegue pelo diretório de perfis e filtre por interesses | |
+ browse_local_posts: Navegue pelos toots públicos locais em tempo real | |
+ browse_public_posts: Navegue pelos toots públicos globais em tempo real | |
+ contact: Contato | |
+ contact_missing: Não definido | |
+ contact_unavailable: Não disponível | |
+ discover_users: Descubra usuários | |
documentation: Documentação | |
- federation_hint_html: Ter uma conta em %{instance} permitirá seguir pessoas em qualquer instância Mastodon. | |
- get_apps: Experimente uma aplicação | |
- hosted_on: Mastodon em %{domain} | |
+ federation_hint_html: Com uma conta em %{instance} você vai poder seguir e interagir com pessoas de qualquer canto do fediverso. | |
+ get_apps: Experimente um aplicativo | |
+ hosted_on: Instância Mastodon em %{domain} | |
instance_actor_flash: | | |
- Esta conta é um actor virtual usado para representar a própria instância e não um utilizador individual. | |
- É usada para motivos de federação e não deve ser bloqueada a não ser que que queira bloquear a instância por completo. Se for esse o caso, deverá usar o bloqueio de domínio. | |
- learn_more: Saber mais | |
- privacy_policy: Política de privacidade | |
- rules: Regras da instância | |
- rules_html: 'Abaixo está um resumo das regras que precisa seguir se pretender ter uma conta nesta instância do Mastodon:' | |
- see_whats_happening: Veja o que está a acontecer | |
+ Esta conta é um ator virtual usado para representar o próprio servidor e não qualquer usuário individual. | |
+ É usado para propósitos de federação e não deve ser bloqueado a menos que queira bloquear toda a instância, o que no caso devia usar um bloqueio de domínio. | |
+ learn_more: Saiba mais | |
+ privacy_policy: Política de Privacidade | |
+ rules: Regras do servidor | |
+ rules_html: 'Abaixo está um resumo das regras que você precisa seguir se você quer ter uma conta neste servidor do Mastodon:' | |
+ see_whats_happening: Veja o que está acontecendo | |
server_stats: 'Estatísticas da instância:' | |
- source_code: Código fonte | |
+ source_code: Código-fonte | |
status_count_after: | |
- one: publicação | |
- other: publicações | |
- status_count_before: Que fizeram | |
- tagline: Siga os seus amigos e descubra novas amizades | |
+ one: toot | |
+ other: toots | |
+ status_count_before: Autores de | |
+ tagline: Siga seus amigos e faça novas amizades | |
terms: Termos de serviço | |
unavailable_content: Conteúdo indisponível | |
unavailable_content_description: | |
domain: Instância | |
- reason: Motivo | |
- rejecting_media: 'Arquivos de media destas instâncias não serão processados ou armazenados, e nenhuma miniatura será exibida, o que requer que o utilizador clique e abra o arquivo original manualmente:' | |
- rejecting_media_title: Media filtrada | |
- silenced: 'Publicações destas instâncias serão ocultas em linhas do tempo e conversas públicas, e nenhuma notificação será gerada a partir das interações dos seus utilizadores, a menos que você os esteja a seguir:' | |
+ reason: 'Motivo:' | |
+ rejecting_media: 'Arquivos de mídia destas instâncias não serão processados ou armazenados e nenhuma miniatura será exibida, exigindo que o usuário abra o arquivo original manualmente:' | |
+ rejecting_media_title: Mídia filtrada | |
+ silenced: 'Toots destas instâncias serão ocultos em linhas e conversas públicas, e nenhuma notificação será gerada a partir das interações dos seus usuários, a menos que esteja sendo seguido:' | |
silenced_title: Servidores silenciados | |
- suspended: 'Nenhum dado dessas instâncias será processado, armazenado ou trocado, tornando qualquer interação ou comunicação com os utilizadores dessas instâncias impossível:' | |
- suspended_title: Servidores suspensos | |
- unavailable_content_html: Mastodon geralmente permite que você veja o conteúdo e interaja com utilizadores de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico. | |
+ suspended: 'Você não será capaz de seguir ninguém destas instâncias, e nenhum dado delas será processado, armazenado ou trocado:' | |
+ suspended_title: Servidores banidos | |
+ unavailable_content_html: Mastodon geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico. | |
user_count_after: | |
- one: utilizador | |
- other: utilizadores | |
- user_count_before: Casa para | |
- what_is_mastodon: O que é o Mastodon? | |
+ one: usuário | |
+ other: usuários | |
+ user_count_before: Casa de | |
+ what_is_mastodon: O que é Mastodon? | |
+ ads: | |
+ why_copy: Mostramos anúncios de produtos e serviços que achamos que nossos usuários podem gostar. | |
accounts: | |
- choices_html: 'escolhas de %{name}:' | |
- endorsements_hint: Você pode, através da interface web, escolher endossar pessoas que segue, e elas aparecerão aqui. | |
- featured_tags_hint: Você pode destacar hashtags específicas que serão exibidas aqui. | |
+ choices_html: 'Sugestões de %{name}:' | |
+ endorsements_hint: Você pode sugerir pessoas que você segue, elas aparecerão aqui. | |
+ featured_tags_hint: Você pode destacar hashtags específicas, elas aparecerão aqui. | |
follow: Seguir | |
followers: | |
one: Seguidor | |
other: Seguidores | |
- following: A seguir | |
- instance_actor_flash: Esta conta é um actor virtual usado para representar a própria instância e não um utilizador individual. É usada para motivos de federação e não deve ser suspenso. | |
- joined: Aderiu %{date} | |
- last_active: última vez activo | |
- link_verified_on: A posse deste link foi verificada em %{date} | |
- media: Media | |
- moved_html: "%{name} mudou-se para %{new_profile_link}:" | |
- network_hidden: Esta informação não está disponível | |
+ following: Seguindo | |
+ instance_actor_flash: Esta conta é um ator virtual usado para representar o próprio servidor e não um usuário individual. É utilizada para fins de federação e não deve ser suspensa. | |
+ joined: Entrou em %{date} | |
+ last_active: última atividade | |
+ link_verified_on: O link foi verificado em %{date} | |
+ media: Mídia | |
+ moved_html: "%{name} se mudou para %{new_profile_link}:" | |
+ network_hidden: Informação indisponível | |
never_active: Nunca | |
- nothing_here: Não há nada aqui! | |
- people_followed_by: Pessoas seguidas por %{name} | |
+ nothing_here: Nada aqui! | |
+ people_followed_by: Pessoas que %{name} segue | |
people_who_follow: Pessoas que seguem %{name} | |
pin_errors: | |
- following: Tu tens de estar a seguir a pessoa que pretendes apoiar | |
+ following: Você deve estar seguindo a pessoa que você deseja sugerir | |
posts: | |
- one: Publicação | |
- other: Publicações | |
- posts_tab_heading: Publicações | |
- posts_with_replies: Posts e Respostas | |
+ one: Toot | |
+ other: Toots | |
+ posts_tab_heading: Toots | |
+ posts_with_replies: Toots e respostas | |
roles: | |
- admin: Administrador(a) | |
+ admin: Admin | |
bot: Robô | |
group: Grupo | |
moderator: Moderador | |
@@ -89,235 +91,193 @@ | |
unfollow: Deixar de seguir | |
admin: | |
account_actions: | |
- action: Executar acção | |
- title: Executar acção de moderação em %{acct} | |
+ action: Tomar uma atitude | |
+ title: Moderar %{acct} | |
account_moderation_notes: | |
- create: Criar nota | |
+ create: Deixar nota | |
created_msg: Nota de moderação criada com sucesso! | |
- delete: Eliminar | |
+ delete: Excluir | |
destroyed_msg: Nota de moderação excluída com sucesso! | |
accounts: | |
- add_email_domain_block: Adicionar o domínio de email à lista negra | |
+ add_email_domain_block: Adicionar o domínio de e-mail à lista negra | |
approve: Aprovar | |
- approve_all: Aprovar todos | |
- approved_msg: Inscrição de %{username} aprovada com sucesso | |
- are_you_sure: Tens a certeza? | |
- avatar: Imagem de Perfil | |
+ approve_all: Aprovar tudo | |
+ approved_msg: Aprovado com sucesso o pedido de registro de %{username} | |
+ are_you_sure: Você tem certeza? | |
+ avatar: Imagem de perfil | |
by_domain: Domínio | |
change_email: | |
changed_msg: E-mail da conta alterado com sucesso! | |
- current_email: E-mail actual | |
+ current_email: E-mail atual | |
label: Alterar e-mail | |
new_email: Novo e-mail | |
submit: Alterar e-mail | |
title: Alterar e-mail para %{username} | |
confirm: Confirmar | |
confirmed: Confirmado | |
- confirming: A confirmar | |
- delete: Eliminar dados | |
- deleted: Eliminada | |
- demote: Despromoveu | |
- destroyed_msg: Os dados de %{username} estão agora em fila de espera para serem eliminados de imediato | |
- disable: Desativar | |
- disable_two_factor_authentication: Desativar 2FA | |
- disabled: Congelada | |
- display_name: Nome a mostrar | |
+ confirming: Confirmando | |
+ delete: Excluir dados | |
+ deleted: Excluído | |
+ demote: Rebaixar | |
+ destroyed_msg: Os dados de %{username} estão na fila para serem excluídos em breve | |
+ disable: Congelar | |
+ disable_two_factor_authentication: Desativar autenticação de dois fatores | |
+ disabled: Desativada | |
+ display_name: Nome de exibição | |
location: localização | |
website: local na rede Internet | |
domain: Domínio | |
edit: Editar | |
email: E-mail | |
- email_status: Estado do e-mail | |
- enable: Ativar | |
- enabled: Ativado | |
- enabled_msg: Descongelou com sucesso a conta %{username} | |
+ email_status: Status do e-mail | |
+ enable: Descongelar | |
+ enabled: Ativada | |
+ enabled_msg: Descongelada com sucesso a conta de %{username} | |
followers: Seguidores | |
- follows: A seguir | |
- header: Cabeçalho | |
+ follows: Seguindo | |
+ header: Capa | |
inbox_url: URL da caixa de entrada | |
- invite_request_text: Razões para se juntar a nós | |
- invited_by: Convidado(a) por | |
+ invite_request_text: Motivos para entrar | |
+ invited_by: Convidado por | |
ip: IP | |
- joined: Aderiu | |
+ joined: Entrou | |
location: | |
all: Todos | |
- local: Local | |
remote: Remoto | |
- title: Local | |
- login_status: Estado de início de sessão | |
- media_attachments: Anexos de media | |
+ title: Localização | |
+ login_status: Situação da conta | |
+ media_attachments: Mídias anexadas | |
memorialize: Converter em memorial | |
- memorialized: Em memória | |
- memorialized_msg: Conta %{username} transformada com sucesso em memorial | |
+ memorialized: Convertidas em memorial | |
+ memorialized_msg: Transformou com sucesso %{username} em uma conta memorial | |
moderation: | |
- active: Activo | |
+ active: Ativo | |
all: Todos | |
pending: Pendente | |
silenced: Silenciados | |
- suspended: Supensos | |
+ suspended: Banidos | |
title: Moderação | |
moderation_notes: Notas de moderação | |
- most_recent_activity: Actividade mais recente | |
+ most_recent_activity: Atividade mais recente | |
most_recent_ip: IP mais recente | |
- no_account_selected: Nenhuma conta foi alterada porque nenhuma foi selecionada | |
- no_limits_imposed: Sem limites impostos | |
+ no_account_selected: Nenhuma conta foi alterada, pois nenhuma conta foi selecionada | |
+ no_limits_imposed: Nenhum limite imposto | |
not_subscribed: Não inscrito | |
- pending: Pendente de revisão | |
- perform_full_suspension: Fazer suspensão completa | |
+ pending: Revisão pendente | |
+ perform_full_suspension: Banir | |
promote: Promover | |
protocol: Protocolo | |
public: Público | |
- push_subscription_expires: A Inscrição PuSH expira | |
+ push_subscription_expires: Inscrição PuSH expira | |
redownload: Atualizar perfil | |
- redownloaded_msg: Atualizado com sucesso o perfil de %{username} da origem | |
- reject: Rejeitar | |
- reject_all: Rejeitar todas | |
- rejected_msg: Inscrição de %{username} rejeitada com sucesso | |
- remove_avatar: Remover a imagem de perfil | |
- remove_header: Remover o cabeçalho | |
- removed_avatar_msg: Imagem de perfil de %{username} removida com sucesso | |
- removed_header_msg: Imagem de cabeçalho de %{username} removida com sucesso | |
+ redownloaded_msg: Atualizado com sucesso o perfil de %{username} a partir da origem | |
+ reject: Vetar | |
+ reject_all: Vetar tudo | |
+ rejected_msg: Rejeitado com sucesso o pedido de registro de %{username} | |
+ remove_avatar: Remover imagem de perfil | |
+ remove_header: Remover capa | |
+ removed_avatar_msg: Removida com sucesso a imagem de avatar de %{username} | |
+ removed_header_msg: Removida com sucesso a imagem de capa de %{username} | |
resend_confirmation: | |
- already_confirmed: Este utilizador já está confirmado | |
- send: Reenviar um email de confirmação | |
- success: Email de confirmação enviado com sucesso! | |
- reset: Restaurar | |
- reset_password: Reset palavra-passe | |
- resubscribe: Reinscrever | |
+ already_confirmed: Este usuário já está confirmado | |
+ send: Reenviar o e-mail de confirmação | |
+ success: E-mail de confirmação enviado com sucesso! | |
+ reset: Redefinir | |
+ reset_password: Redefinir senha | |
+ resubscribe: Reinscrever-se | |
role: Permissões | |
roles: | |
- admin: Administrador(a) | |
+ admin: Administrador | |
moderator: Moderador | |
- staff: Equipa | |
- user: Utilizador | |
+ staff: Equipe | |
+ user: Usuário | |
search: Pesquisar | |
- search_same_email_domain: Outros utilizadores com o mesmo domínio de email | |
- search_same_ip: Outros utilizadores com o mesmo IP | |
- sensitive: Marcar como sensível | |
- sensitized: marcada como sensível | |
- shared_inbox_url: URL da caixa de entrada compartilhada | |
+ search_same_email_domain: Outros usuários com o mesmo domínio de e-mail | |
+ search_same_ip: Outros usuários com o mesmo IP | |
+ sensitive: Sensíveis | |
+ sensitized: marcadas como sensíveis | |
+ shared_inbox_url: Link da caixa de entrada compartilhada | |
show: | |
- created_reports: Relatórios gerados por esta conta | |
- targeted_reports: Relatórios feitos sobre esta conta | |
- silence: Silêncio | |
- silenced: Silenciada | |
- statuses: Status | |
+ created_reports: Denúncias desta conta | |
+ targeted_reports: Denúncias sobre esta conta | |
+ silence: Silenciar | |
+ silenced: Silenciado | |
+ statuses: Toots | |
subscribe: Inscrever-se | |
- suspended: Suspensa | |
- suspension_irreversible: Os dados desta conta foram eliminados irreversivelmente. Pode cancelar a suspensão da conta para torná-la utilizável, mas ela não irá recuperar os dados que possuía anteriormente. | |
- suspension_reversible_hint_html: A conta foi suspensa e os dados serão totalmente eliminados em %{date}. Até lá, a conta poderá ser recuperada sem quaisquer efeitos negativos. Se deseja eliminar todos os dados desta conta imediatamente, pode fazê-lo em baixo. | |
- time_in_queue: Aguardando na fila %{time} | |
+ suspended: Banido | |
+ suspension_irreversible: Os dados desta conta foram excluídos de forma irreversível. Você pode remover a suspensão da conta para torná-la utilizável, mas ela não irá recuperar nenhum dado que ela possuía anteriormente. | |
+ suspension_reversible_hint_html: A conta foi suspensa e os dados serão totalmente removidos em %{date}. Até lá, a conta pode ser restaurada sem nenhum efeito negativo. Se você deseja remover todos os dados da conta imediatamente, você pode fazer isso abaixo. | |
+ time_in_queue: Esperando na fila por %{time} | |
title: Contas | |
unconfirmed_email: E-mail não confirmado | |
- undo_sensitized: Desmarcar como sensível | |
- undo_silenced: Desfazer silenciar | |
- undo_suspension: Desfazer supensão | |
- unsilenced_msg: Removeu com sucesso as limitações da conta %{username} | |
+ undo_sensitized: Desfazer sensível | |
+ undo_silenced: Desfazer silêncio | |
+ undo_suspension: Desbanir | |
+ unsilenced_msg: Removidas com sucesso as limitações da conta de %{username} | |
unsubscribe: Cancelar inscrição | |
- unsuspended_msg: Removeu com sucesso a suspensão da conta %{username} | |
- username: Utilizador | |
- view_domain: Ver resumo do domínio | |
- warn: Aviso | |
+ unsuspended_msg: Removida com sucesso a suspensão da conta de %{username} | |
+ username: Nome de usuário | |
+ view_domain: Ver resumo para o domínio | |
+ warn: Notificar | |
web: Web | |
- whitelisted: Está na lista branca | |
+ whitelisted: Permitido | |
action_logs: | |
action_types: | |
- assigned_to_self_report: Atribuir Relatório | |
- change_email_user: Alterar E-mail do Utilizador | |
- confirm_user: Confirmar Utilizador | |
+ assigned_to_self_report: Adicionar relatório | |
+ change_email_user: Editar e-mail do usuário | |
+ confirm_user: Confirmar Usuário | |
create_account_warning: Criar Aviso | |
create_announcement: Criar Anúncio | |
create_custom_emoji: Criar Emoji Personalizado | |
- create_domain_allow: Criar Permissão de Domínio | |
+ create_domain_allow: Adicionar domínio permitido | |
create_domain_block: Criar Bloqueio de Domínio | |
create_email_domain_block: Criar Bloqueio de Domínio de E-mail | |
create_ip_block: Criar regra de IP | |
- create_unavailable_domain: Criar Domínio Indisponível | |
- demote_user: Despromover Utilizador | |
- destroy_announcement: Eliminar Anúncio | |
- destroy_custom_emoji: Eliminar Emoji Personalizado | |
- destroy_domain_allow: Eliminar Permissão de Domínio | |
- destroy_domain_block: Eliminar Bloqueio de Domínio | |
- destroy_email_domain_block: Eliminar Bloqueio de Domínio de E-mail | |
- destroy_ip_block: Eliminar regra de IP | |
- destroy_status: Eliminar Publicação | |
- destroy_unavailable_domain: Eliminar Domínio Indisponível | |
- disable_2fa_user: Desativar 2FA | |
+ demote_user: Rebaixar usuário | |
+ destroy_announcement: Excluir anúncio | |
+ destroy_custom_emoji: Excluir emoji personalizado | |
+ destroy_domain_allow: Excluir domínio permitido | |
+ destroy_domain_block: Excluir Bloqueio de Domínio | |
+ destroy_email_domain_block: Excluir bloqueio de domínio de e-mail | |
+ destroy_ip_block: Excluir regra de IP | |
+ destroy_status: Excluir Status | |
+ disable_2fa_user: Desativar autenticação de dois fatores | |
disable_custom_emoji: Desativar Emoji Personalizado | |
- disable_user: Desativar Utilizador | |
+ disable_user: Desativar usuário | |
enable_custom_emoji: Ativar Emoji Personalizado | |
- enable_user: Ativar Utilizador | |
- memorialize_account: Memorizar Conta | |
- promote_user: Promover Utilizador | |
- remove_avatar_user: Remover Imagem de Perfil | |
+ enable_user: Ativar usuário | |
+ memorialize_account: Converter conta em memorial | |
+ promote_user: Promover usuário | |
+ remove_avatar_user: Remover Avatar | |
reopen_report: Reabrir Relatório | |
- reset_password_user: Repor Password | |
+ reset_password_user: Redefinir a senha | |
resolve_report: Resolver Relatório | |
- sensitive_account: Marcar a media na sua conta como sensível | |
- silence_account: Silenciar Conta | |
+ sensitive_account: Marcar a mídia na sua conta como sensível | |
+ silence_account: Silenciar conta | |
suspend_account: Suspender Conta | |
- unassigned_report: Desatribuir Relatório | |
- unsensitive_account: Desmarcar a media na sua conta como sensível | |
- unsilence_account: Deixar de Silenciar Conta | |
- unsuspend_account: Retirar Suspensão à Conta | |
- update_announcement: Atualizar Anúncio | |
- update_custom_emoji: Atualizar Emoji Personalizado | |
- update_domain_block: Atualizar Bloqueio de Domínio | |
- update_status: Atualizar Estado | |
+ unassigned_report: Remover relatório | |
+ unsensitive_account: Desmarcar a mídia na sua conta como sensível | |
+ unsilence_account: Desfazer silenciar conta | |
+ unsuspend_account: Remover suspensão de conta | |
+ update_announcement: Editar anúncio | |
+ update_custom_emoji: Editar Emoji Personalizado | |
+ update_domain_block: Atualizar bloqueio de domínio | |
+ update_status: Editar Status | |
actions: | |
- assigned_to_self_report_html: "%{name} atribuiu o relatório %{target} a si próprio" | |
- change_email_user_html: "%{name} alterou o endereço de e-mail do utilizador %{target}" | |
- confirm_user_html: "%{name} confirmou o endereço de e-mail do utilizador %{target}" | |
create_account_warning_html: "%{name} enviou um aviso para %{target}" | |
- create_announcement_html: "%{name} criou o novo anúncio %{target}" | |
- create_custom_emoji_html: "%{name} carregou o novo emoji %{target}" | |
- create_domain_allow_html: "%{name} habilitou a federação com o domínio %{target}" | |
create_domain_block_html: "%{name} bloqueou o domínio %{target}" | |
- create_email_domain_block_html: "%{name} bloqueou o domínio de e-mail %{target}" | |
- create_ip_block_html: "%{name} criou regra para o IP %{target}" | |
- create_unavailable_domain_html: "%{name} parou a entrega ao domínio %{target}" | |
- demote_user_html: "%{name} despromoveu o utilizador %{target}" | |
- destroy_announcement_html: "%{name} eliminou o anúncio %{target}" | |
- destroy_custom_emoji_html: "%{name} destruiu o emoji %{target}" | |
- destroy_domain_allow_html: "%{name} desabilitou a federação com o domínio %{target}" | |
- destroy_domain_block_html: "%{name} desbloqueou o domínio %{target}" | |
- destroy_email_domain_block_html: "%{name} desbloqueou o domínio de e-mail %{target}" | |
- destroy_ip_block_html: "%{name} eliminou regra para o IP %{target}" | |
- destroy_status_html: "%{name} removeu a publicação de %{target}" | |
- destroy_unavailable_domain_html: "%{name} retomou a entrega ao domínio %{target}" | |
- disable_2fa_user_html: "%{name} desativou o requerimento de autenticação em dois passos para o utilizador %{target}" | |
- disable_custom_emoji_html: "%{name} desabilitou o emoji %{target}" | |
- disable_user_html: "%{name} desativou o acesso para o utilizador %{target}" | |
- enable_custom_emoji_html: "%{name} habilitou o emoji %{target}" | |
- enable_user_html: "%{name} ativou o acesso para o utilizador %{target}" | |
- memorialize_account_html: "%{name} transformou a conta de %{target} em um memorial" | |
- promote_user_html: "%{name} promoveu o utilizador %{target}" | |
- remove_avatar_user_html: "%{name} removeu a imagem de perfil de %{target}" | |
- reopen_report_html: "%{name} reabriu o relatório %{target}" | |
- reset_password_user_html: "%{name} restabeleceu a palavra-passe do utilizador %{target}" | |
- resolve_report_html: "%{name} resolveu o relatório %{target}" | |
- sensitive_account_html: "%{name} marcou a media de %{target} como sensível" | |
- silence_account_html: "%{name} silenciou a conta de %{target}" | |
- suspend_account_html: "%{name} suspendeu a conta de %{target}" | |
- unassigned_report_html: "%{name} desatribuiu o realtório %{target}" | |
- unsensitive_account_html: "%{name} desmarcou a media de %{target} como sensível" | |
- unsilence_account_html: "%{name} desativou o silêncio de %{target}" | |
- unsuspend_account_html: "%{name} desativou a suspensão de %{target}" | |
- update_announcement_html: "%{name} atualizou o anúncio %{target}" | |
- update_custom_emoji_html: "%{name} atualizou o emoji %{target}" | |
- update_domain_block_html: "%{name} atualizou o bloqueio de domínio para %{target}" | |
- update_status_html: "%{name} atualizou o estado de %{target}" | |
- deleted_status: "(publicação eliminada)" | |
- empty: Não foram encontrados registos. | |
+ create_email_domain_block_html: "%{name} bloqueou do domínio de e-mail %{target}" | |
+ deleted_status: "(status excluído)" | |
+ empty: Nenhum registro encontrado. | |
filter_by_action: Filtrar por ação | |
- filter_by_user: Filtrar por utilizador | |
- title: Registo de auditoria | |
+ filter_by_user: Filtrar por usuário | |
+ title: Auditar histórico | |
announcements: | |
- destroyed_msg: Anúncio eliminado com sucesso! | |
+ destroyed_msg: Anúncio excluído com sucesso! | |
edit: | |
title: Editar anúncio | |
- empty: Nenhum anúncio encontrado. | |
- live: Em exibição | |
+ empty: Sem anúncios. | |
+ live: Ao vivo | |
new: | |
create: Criar anúncio | |
title: Novo anúncio | |
@@ -326,184 +286,158 @@ | |
scheduled_for: Agendado para %{time} | |
scheduled_msg: Anúncio agendado para publicação! | |
title: Anúncios | |
- unpublish: Anular publicação | |
- unpublished_msg: Anúncio retirado de exibição com sucesso! | |
+ unpublish: Cancelar publicação | |
+ unpublished_msg: Anúncio despublicado com sucesso! | |
updated_msg: Anúncio atualizado com sucesso! | |
custom_emojis: | |
assign_category: Atribuir categoria | |
by_domain: Domínio | |
copied_msg: Cópia local do emoji criada com sucesso | |
copy: Copiar | |
- copy_failed_msg: Não foi possível criar uma cópia local deste emoji | |
+ copy_failed_msg: Não foi possível criar cópia local do emoji | |
create_new_category: Criar nova categoria | |
created_msg: Emoji criado com sucesso! | |
- delete: Eliminar | |
- destroyed_msg: Emoji destruído com sucesso! | |
+ delete: Excluir | |
+ destroyed_msg: Emoji excluído com sucesso! | |
disable: Desativar | |
disabled: Desativado | |
- disabled_msg: Desativado com sucesso este emoji | |
- emoji: Emoji | |
+ disabled_msg: Emoji desativado com sucesso | |
enable: Ativar | |
enabled: Ativado | |
- enabled_msg: Ativado com sucesso este emoji | |
+ enabled_msg: Emoji ativado com sucesso | |
image_hint: PNG de até 50KB | |
- list: Lista | |
+ list: Listar | |
listed: Listado | |
new: | |
- title: Adicionar novo emoji customizado | |
- not_permitted: Não está autorizado a executar esta ação | |
+ title: Adicionar novo emoji personalizado | |
+ not_permitted: Você não tem permissão para executar esta ação | |
overwrite: Sobrescrever | |
- shortcode: Código de atalho | |
- shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underscores | |
- title: Emojis customizados | |
- uncategorized: Sem categoria | |
+ shortcode: Atalho | |
+ shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underlines ("_") | |
+ title: Emojis personalizados | |
+ uncategorized: Não categorizado | |
unlist: Não listar | |
- unlisted: Não listado | |
+ unlisted: Não-listado | |
update_failed_msg: Não foi possível atualizar esse emoji | |
updated_msg: Emoji atualizado com sucesso! | |
upload: Enviar | |
dashboard: | |
authorized_fetch_mode: Modo seguro | |
- backlog: trabalhos atrasados | |
+ backlog: tarefas na fila | |
config: Configuração | |
- feature_deletions: Eliminações da conta | |
- feature_invites: Links de convites | |
+ feature_deletions: Exclusão de contas | |
+ feature_invites: Convites | |
feature_profile_directory: Diretório de perfis | |
- feature_registrations: Registos | |
+ feature_registrations: Novas contas | |
feature_relay: Repetidor da federação | |
- feature_timeline_preview: Pré-visualização da cronologia | |
- features: Componentes | |
- hidden_service: Federação com serviços escondidos | |
- open_reports: relatórios abertos | |
- pending_tags: hashtags a aguardar revisão | |
- pending_users: utilizadores a aguardar revisão | |
- recent_users: Utilizadores recentes | |
- search: Pesquisa com texto completo | |
- single_user_mode: Modo de utilizador único | |
+ feature_timeline_preview: Prévia da linha | |
+ features: Funcionalidades | |
+ hidden_service: Federação com serviços onion | |
+ open_reports: Denúncias em aberto | |
+ pending_tags: hashtags pendentes | |
+ pending_users: usuários pendentes | |
+ recent_users: Usuários recentes | |
+ search: Pesquisa em texto | |
+ single_user_mode: Modo de usuário único | |
software: Software | |
- space: Utilização do espaço | |
- title: Painel de controlo | |
- total_users: total de utilizadores | |
- trends: Tendências | |
- week_interactions: interacções desta semana | |
- week_users_active: activo esta semana | |
- week_users_new: utilizadores nesta semana | |
- whitelist_mode: Modo lista branca | |
+ space: Uso de espaço em disco | |
+ title: Painel de controle | |
+ total_users: usuários no total | |
+ trends: Em alta | |
+ week_interactions: interações essa semana | |
+ week_users_active: ativos essa semana | |
+ week_users_new: usuários essa semana | |
+ whitelist_mode: Modo lista de permitidos | |
domain_allows: | |
- add_new: Colocar domínio na lista branca | |
- created_msg: Domínio foi adicionado à lista branca com sucesso | |
- destroyed_msg: Domínio foi removido da lista branca | |
- undo: Remover da lista branca | |
+ add_new: Permitir domínio | |
+ created_msg: Domínio foi permitido | |
+ destroyed_msg: Domínio foi bloqueado | |
+ undo: Bloquear | |
domain_blocks: | |
- add_new: Adicionar novo | |
- created_msg: Bloqueio do domínio está a ser processado | |
- destroyed_msg: Bloqueio de domínio está a ser removido | |
+ add_new: Adicionar novo bloqueio de domínio | |
+ created_msg: Domínio está sendo bloqueado | |
+ destroyed_msg: Domínio desbloqueado | |
domain: Domínio | |
edit: Editar bloqueio de domínio | |
- existing_domain_block_html: Você já impôs limites mais restritivos a %{name}, é necessário primeiro <a href="%{unblock_url}">desbloqueá-lo</a>. | |
+ existing_domain_block_html: Você já impôs limites mais estritos em %{name}, você precisa <a href="%{unblock_url}">desbloqueá-lo</a> primeiro. | |
new: | |
create: Criar bloqueio | |
- hint: O bloqueio de dominio não vai previnir a criação de entradas na base de dados, mas irá retroativamente e automaticamente aplicar métodos de moderação específica nessas contas. | |
+ hint: O bloqueio de domínio não vai prevenir a criação de entradas de contas na base de dados, mas vai retroativamente e automaticamente aplicar métodos específicos de moderação nessas contas. | |
severity: | |
- desc_html: "<strong>Silenciar</strong> irá fazer com que as publicações dessa conta sejam invisíveis para quem não a segue. <strong>Supender</strong> irá eliminar todo o conteúdo guardado dessa conta, media e informação de perfil. Use <strong>Nenhum</strong> se apenas deseja rejeitar arquivos de media." | |
+ desc_html: "<strong>Silenciar</strong> vai fazer os posts da conta invisíveis para qualquer um que não os esteja seguindo. <strong>Suspender</strong> vai remover todo o conteúdo, mídia, e dados de perfil da conta. Use <strong>Nenhum</strong> se você só quer rejeitar arquivos de mídia." | |
noop: Nenhum | |
silence: Silenciar | |
- suspend: Suspender | |
+ suspend: Banir | |
title: Novo bloqueio de domínio | |
obfuscate: Ofuscar nome de domínio | |
- obfuscate_hint: Ofuscar parcialmente o nome de domínio na lista, se estiverem habilitadas as limitações na publicação da lista de domínios | |
private_comment: Comentário privado | |
- private_comment_hint: Comentário sobre essa limitação de domínio para uso interno pelos moderadores. | |
+ private_comment_hint: Comente sobre essa restrição ao domínio para uso interno dos moderadores. | |
public_comment: Comentário público | |
- public_comment_hint: Comentário sobre essa limitação de domínio para o público geral, se ativada a divulgação da lista de limitações de domínio. | |
- reject_media: Rejeitar ficheiros de media | |
- reject_media_hint: Remove arquivos de media armazenados localmente e rejeita descarregar novos arquivos no futuro. Irrelevante para suspensões | |
- reject_reports: Rejeitar relatórios | |
- reject_reports_hint: Ignorar todos os relatórios vindos deste domínio. Irrelevantes para efectuar suspensões | |
- rejecting_media: a rejeitar ficheiros de media | |
- rejecting_reports: a rejeitar relatórios | |
+ public_comment_hint: Comente sobre essa restrição ao domínio para o público geral, caso a divulgação da lista de bloqueio esteja ativada. | |
+ reject_media: Rejeitar arquivos de mídia | |
+ reject_media_hint: Remove arquivos de mídia armazenados localmente e recusa fazer download de qualquer um no futuro. Irrelevante para suspensões | |
+ reject_reports: Rejeitar denúncias | |
+ reject_reports_hint: Ignora todas as denúncias vindo deste domínio. Irrelevante para suspensões | |
+ rejecting_media: rejeitando arquivos de mídia | |
+ rejecting_reports: rejeitando denúncias | |
severity: | |
silence: silenciado | |
- suspend: suspenso | |
+ suspend: banido | |
show: | |
affected_accounts: | |
- one: Uma conta na base de dados afectada | |
- other: "%{count} contas na base de dados afectadas" | |
+ one: Uma conta no banco de dados foi afetada | |
+ other: "%{count} contas no banco de dados foram afetadas" | |
retroactive: | |
- silence: Não silenciar contas afetadas existentes deste domínio | |
- suspend: Não suspender todas as contas existentes nesse domínio | |
- title: Remover o bloqueio de domínio de %{domain} | |
- undo: Anular | |
- undo: Anular | |
+ silence: Dessilenciar contas existentes afetadas deste domínio | |
+ suspend: Remover a suspensão das contas afetadas deste domínio | |
+ title: Desfazer bloqueio de domínio para %{domain} | |
+ undo: Desfazer | |
+ undo: Desfazer bloqueio de domínio | |
view: Ver domínios bloqueados | |
email_domain_blocks: | |
add_new: Adicionar novo | |
- created_msg: Bloqueio de domínio de email criado com sucesso | |
- delete: Eliminar | |
- destroyed_msg: Bloqueio de domínio de email excluído com sucesso | |
+ created_msg: Domínio de e-mail adicionado à lista negra com sucesso | |
+ delete: Excluir | |
+ destroyed_msg: Domínio de e-mail excluído da lista negra com sucesso | |
domain: Domínio | |
- empty: Nenhum domínio de e-mail atualmente na lista negra. | |
+ empty: Nenhum domínio de e-mail atualmente bloqueado. | |
from_html: de %{domain} | |
new: | |
create: Adicionar domínio | |
- title: Novo bloqueio de domínio de email | |
- title: Bloqueio de Domínio de Email | |
- follow_recommendations: | |
- description_html: "<strong>Recomendações de quem seguir ajudam novos utilizadores a encontrar conteúdo interessante rapidamente.</strong>. Quando um utilizador não interage com outros o suficiente para formar recomendações personalizadas, estas contas são recomendadas. Elas são recalculadas diariamente a partir de uma mistura de contas com mais atividade recente e maior número de seguidores locais para um determinado idioma." | |
- language: Para o idioma | |
- status: Estado | |
- suppress: Suprimir recomendação de contas a seguir | |
- suppressed: Suprimida | |
- title: Seguir recomendações | |
- unsuppress: Restaurar recomendações de contas a seguir | |
+ title: Nova entrada de lista negra de e-mail | |
+ title: Lista de negra de e-mail | |
instances: | |
- back_to_all: Todas | |
- back_to_limited: Limitadas | |
back_to_warning: Aviso | |
by_domain: Domínio | |
- delivery: | |
- all: Todas | |
- clear: Limpar erros de entrega | |
- restart: Reiniciar entrega | |
- stop: Parar entrega | |
- title: Entrega | |
- unavailable: Indisponível | |
- unavailable_message: Entrega indisponível | |
- warning: Aviso | |
- warning_message: | |
- one: Falhou entrega %{count} dia | |
- other: Falhou entrega %{count} dias | |
- delivery_available: Entrega disponível | |
- delivery_error_days: Dias de erro de entrega | |
- delivery_error_hint: Se a entrega não for possível durante %{count} dias, será automaticamente marcada como não realizável. | |
- empty: Não foram encontrados domínios. | |
+ delivery_available: Envio disponível | |
+ empty: Nenhum domínio encontrado. | |
known_accounts: | |
one: "%{count} conta conhecida" | |
other: "%{count} contas conhecidas" | |
moderation: | |
- all: Todas | |
- limited: Limitadas | |
+ all: Todos | |
+ limited: Limitados | |
title: Moderação | |
- private_comment: Comentários privados | |
- public_comment: Comentários públicos | |
- title: Instâncias conhecidas | |
- total_blocked_by_us: Bloqueadas por nós | |
- total_followed_by_them: Seguidas por eles | |
- total_followed_by_us: Seguidas por nós | |
- total_reported: Relatórios sobre eles | |
- total_storage: Anexos de media | |
+ private_comment: Comentário privado | |
+ public_comment: Comentário público | |
+ title: Federação | |
+ total_blocked_by_us: Bloqueado por nós | |
+ total_followed_by_them: Seguidos por eles | |
+ total_followed_by_us: Seguidos por nós | |
+ total_reported: Denúncias sobre eles | |
+ total_storage: Mídias anexadas | |
invites: | |
- deactivate_all: Desactivar todos | |
+ deactivate_all: Desativar todos | |
filter: | |
all: Todos | |
- available: Disponíveis | |
- expired: Expirados | |
+ available: Disponível | |
+ expired: Expirado | |
title: Filtro | |
title: Convites | |
ip_blocks: | |
add_new: Criar regra | |
created_msg: Nova regra de IP adicionada com sucesso | |
- delete: Eliminar | |
+ delete: Excluir | |
expires_in: | |
'1209600': 2 semanas | |
'15778476': 6 meses | |
@@ -521,40 +455,39 @@ | |
title: Relações de %{acct} | |
relays: | |
add_new: Adicionar novo repetidor | |
- delete: Eliminar | |
- description_html: Um <strong>repetidor de federação</strong> é um servidor intermediário que troca grandes volumes de publicações públicas entre instâncias que o subscrevem e publicam. <strong>Ele pode ajudar pequenas e medias instâncias a descobrir conteúdo do fediverso</strong> que, de outro modo, exigiria que os utilizadores locais seguissem manualmente outras pessoas em instâncias remotas. | |
- disable: Desactivar | |
- disabled: Desactivado | |
- enable: Activar | |
- enable_hint: Uma vez ativado, a tua instância irá subscrever a todas as publicações deste repetidor e irá começar a enviar as suas publicações públicas para ele. | |
+ delete: Excluir | |
+ description_html: Um <strong>repetidor de federação</strong> é um servidor intermediário que troca um grande volume de toots públicos entre instâncias que se inscrevem e publicam nele. <strong>O repetidor pode ser usado para ajudar instâncias pequenas e médias a descobrir conteúdo pelo fediverso</strong>, que normalmente precisariam que usuários locais manualmente seguissem outras pessoas em instâncias remotas. | |
+ disable: Desativar | |
+ disabled: Desativado | |
+ enable: Ativar | |
+ enable_hint: Uma vez ativado, sua instância se inscreverá para receber todos os toots públicos desse repetidor; E vai começar a enviar todos os toots públicos desta instância para o repetidor. | |
enabled: Ativado | |
- inbox_url: URL do repetidor | |
- pending: À espera da aprovação do repetidor | |
- save_and_enable: Guardar e ativar | |
- setup: Configurar uma ligação ao repetidor | |
- signatures_not_enabled: Relays não funcionarão corretamente enquanto o modo seguro ou o modo lista branca estiverem ativados | |
- status: Estado | |
- title: Retransmissores | |
+ inbox_url: Link do repetidor | |
+ pending: Esperando pela aprovação do repetidor | |
+ save_and_enable: Salvar e ativar | |
+ setup: Configurar uma conexão de repetidor | |
+ signatures_not_enabled: Repetidores não funcionarão adequadamente enquanto o modo seguro ou o modo lista de permitidos estiverem ativos | |
+ title: Repetidores | |
report_notes: | |
- created_msg: Relatório criado com sucesso! | |
- destroyed_msg: Nota de relatório eliminada com sucesso! | |
+ created_msg: Nota de denúncia criada com sucesso! | |
+ destroyed_msg: Nota de denúncia excluída com sucesso! | |
reports: | |
account: | |
notes: | |
one: "%{count} nota" | |
other: "%{count} notas" | |
reports: | |
- one: "%{count} relatório" | |
- other: "%{count} relatórios" | |
- action_taken_by: Ação tomada por | |
- are_you_sure: Tens a certeza? | |
- assign_to_self: Atribuí-me a mim | |
- assigned: Atribuído ao moderador | |
- by_target_domain: Domínio da conta reportada | |
+ one: "%{count} denúncia" | |
+ other: "%{count} denúncias" | |
+ action_taken_by: Atitude tomada por | |
+ are_you_sure: Você tem certeza? | |
+ assign_to_self: Pegar | |
+ assigned: Moderador responsável | |
+ by_target_domain: Domínio da conta denunciada | |
comment: | |
none: Nenhum | |
- created_at: Relatado | |
- forwarded: Encaminhado | |
+ created_at: Denunciado | |
+ forwarded: Encaminhados | |
forwarded_to: Encaminhado para %{domain} | |
mark_as_resolved: Marcar como resolvido | |
mark_as_unresolved: Marcar como não resolvido | |
@@ -562,423 +495,430 @@ | |
create: Adicionar nota | |
create_and_resolve: Resolver com nota | |
create_and_unresolve: Reabrir com nota | |
- delete: Eliminar | |
- placeholder: Descreve as ações que foram tomadas ou quaisquer outras atualizações relacionadas... | |
- reopen: Reabrir relatório | |
+ delete: Excluir | |
+ placeholder: Descreva que ações foram tomadas, ou quaisquer outras atualizações relacionadas… | |
+ reopen: Reabrir denúncia | |
report: 'Denúncia #%{id}' | |
reported_account: Conta denunciada | |
- reported_by: Reportado por | |
+ reported_by: Denunciada por | |
resolved: Resolvido | |
- resolved_msg: Relatório resolvido com sucesso! | |
- status: Estado | |
- title: Relatórios | |
- unassign: Não atribuir | |
- unresolved: Por resolver | |
+ resolved_msg: Denúncia resolvida com sucesso! | |
+ title: Denúncias | |
+ unassign: Largar | |
+ unresolved: Não resolvido | |
updated_at: Atualizado | |
rules: | |
add_new: Adicionar regra | |
- delete: Eliminar | |
- description_html: Embora a maioria afirme ter lido e concordado com os termos de serviço, geralmente as pessoas só leem depois de surgir um problema. <strong>Dê uma olhada nas regras do seu servidor fornecendo-as em uma lista de marcadores planos.</strong> Tente manter as regras individuais curtas e simples, mas tente também não dividi-las em muitos itens separados. | |
+ delete: Deletar | |
+ description_html: Embora a maioria afirme ter lido e concordado com os termos de serviço, geralmente as pessoas só leem depois de surgir um problema. <strong>Faça com que seja mais fácil ver as regras do seu servidor rapidamente fornecendo-as em uma lista.</strong> Tente manter cada regra curta e simples, mas também tente não dividi-las em muitos itens separados. | |
edit: Editar regra | |
- empty: Nenhuma regra de instância foi ainda definida. | |
- title: Regras da instância | |
+ empty: Nenhuma regra do servidor foi definida. | |
+ title: Regras do servidor | |
settings: | |
activity_api_enabled: | |
- desc_html: Contagem semanais de publicações locais, utilizadores activos e novos registos | |
- title: Publicar estatísticas agregadas sobre atividade dos utilizadores | |
+ desc_html: Contagem de toots locais, usuários ativos e novos usuários semanalmente | |
+ title: Publicar estatísticas agregadas sobre atividade de usuários | |
bootstrap_timeline_accounts: | |
- desc_html: Separa os nomes de utilizadores por vírgulas. Funciona apenas com contas locais e desbloqueadas. O padrão quando vazio são todos os administradores locais. | |
- title: Seguidores predefinidos para novas contas | |
+ desc_html: Separe nomes de usuário através de vírgulas. Funciona apenas com contas locais e destrancadas. O padrão quando vazio são todos os administradores locais. | |
+ title: Usuários a serem seguidos por padrão por novas contas | |
contact_information: | |
- email: Inserir um endereço de email para tornar público | |
- username: Insira um nome de utilizador | |
+ email: E-mail | |
+ username: Usuário de contato | |
custom_css: | |
- desc_html: Modificar a aparência com CSS carregado em cada página | |
+ desc_html: Alterar o visual com CSS carregado em todas as páginas | |
title: CSS personalizado | |
default_noindex: | |
- desc_html: Afeta todos os utilizadores que não alteraram esta configuração | |
- title: Desactivar, por omissão, a indexação de utilizadores por parte dos motores de pesquisa | |
+ desc_html: Afeta qualquer usuário que não tenha alterado esta configuração manualmente | |
+ title: Optar por excluir usuários da indexação de mecanismos de pesquisa por padrão | |
domain_blocks: | |
- all: Para toda a gente | |
+ all: Para todos | |
disabled: Para ninguém | |
title: Mostrar domínios bloqueados | |
- users: Para utilizadores locais que se encontrem autenticados | |
+ users: Para usuários locais logados | |
domain_blocks_rationale: | |
title: Mostrar motivo | |
hero: | |
- desc_html: Apresentado na primeira página. Pelo menos 600x100px recomendados. Quando não é definido, é apresentada a miniatura da instância | |
- title: Imagem Hero | |
+ desc_html: Aparece na página inicial. Recomendado ao menos 600x100px. Se não estiver definido, a miniatura da instância é usada no lugar | |
+ title: Imagem de capa | |
mascot: | |
- desc_html: Apresentada em múltiplas páginas. Pelo menos 293x205px recomendados. Quando não é definida, é apresentada a mascote predefinida | |
- title: Imagem da mascote | |
+ desc_html: Mostrado em diversas páginas. Recomendado ao menos 293×205px. Quando não está definido, o mascote padrão é mostrado | |
+ title: Imagem do mascote | |
peers_api_enabled: | |
- desc_html: Nomes de domínio que esta instância encontrou no fediverso | |
+ desc_html: Nomes de domínio que essa instância encontrou no fediverso | |
title: Publicar lista de instâncias descobertas | |
preview_sensitive_media: | |
- desc_html: A pre-visualização de links noutros sites irá apresentar uma miniatura, mesmo que a media seja marcada como sensível | |
- title: Mostrar media sensível em pre-visualizações OpenGraph | |
+ desc_html: A prévia do link em outros sites vai incluir uma miniatura mesmo se a mídia estiver marcada como sensível | |
+ title: Mostrar mídia sensível em prévias OpenGraph | |
profile_directory: | |
- desc_html: Permite aos utilizadores serem descobertos | |
- title: Ativar directório do perfil | |
+ desc_html: Permitir que usuários possam ser descobertos | |
+ title: Ativar diretório de perfis | |
registrations: | |
closed_message: | |
- desc_html: Mostrar na página inicial quando registos estão encerrados<br/>Podes usar tags HTML | |
- title: Mensagem de registos encerrados | |
+ desc_html: Mostrado na página inicial quando a instância está fechada. Você pode usar tags HTML | |
+ title: Mensagem de instância fechada | |
deletion: | |
- desc_html: Permitir a qualquer utilizador eliminar a sua conta | |
- title: Permitir eliminar contas | |
+ desc_html: Permitir que qualquer um exclua a própria conta | |
+ title: Exclusão aberta de contas | |
min_invite_role: | |
disabled: Ninguém | |
title: Permitir convites de | |
require_invite_text: | |
- desc_html: Quando os registos exigirem aprovação manual, faça o texto "Porque se quer juntar a nós?" da solicitação de convite obrigatório, em vez de opcional | |
- title: Exigir que novos utilizadores preencham um texto de solicitação de convite | |
+ desc_html: Quando o cadastro de novas contas exigir aprovação manual, tornar obrigatório, ao invés de opcional, o texto de solicitação de convite em "Por que você deseja criar uma conta aqui?" | |
+ title: Exigir que novos usuários preencham um texto de solicitação de convite | |
registrations_mode: | |
modes: | |
- approved: Registo sujeito a aprovação | |
- none: Ninguém se pode registar | |
- open: Qualquer pessoa se pode registar | |
- title: Modo de registo | |
+ approved: Aprovação necessária para criar conta | |
+ none: Ninguém pode criar conta | |
+ open: Qualquer um pode criar conta | |
+ title: Modo de novos usuários | |
show_known_fediverse_at_about_page: | |
- desc_html: Quando comutado, irá mostrar a previsualização de publicações de todo o fediverse conhecido. De outro modo só mostrará publicações locais. | |
- title: Mostrar o fediverse conhecido na previsualização da cronologia | |
+ desc_html: Quando ativado, mostra toots globais na prévia da linha, se não, mostra somente toots locais | |
+ title: Mostrar toots globais na prévia da linha | |
show_staff_badge: | |
- desc_html: Mostrar um crachá da equipa na página de utilizador | |
- title: Mostrar crachá da equipa | |
+ desc_html: Mostrar uma insígnia de Equipe na página de usuário | |
+ title: Mostrar insígnia de equipe | |
site_description: | |
- desc_html: Mostrar como parágrafo na página inicial e usado como meta tag.Podes usar tags HTML, em particular <code><a></code> e <code><em></code>. | |
- title: Descrição do site | |
+ desc_html: Parágrafo introdutório na página inicial. Descreva o que faz esse servidor especial, e qualquer outra coisa de importante. Você pode usar tags HTML, em especial <code><a></code> e <code><em></code>. | |
+ title: Descrição da instância | |
site_description_extended: | |
- desc_html: Mostrar na página de mais informações<br/>Podes usar tags HTML | |
- title: Página de mais informações | |
+ desc_html: Um ótimo lugar para seu código de conduta, regras, diretrizes e outras coisas para diferenciar a sua instância. Você pode usar tags HTML | |
+ title: Informação estendida personalizada | |
site_short_description: | |
- desc_html: Mostrada na barra lateral e em etiquetas de metadados. Descreve o que o Mastodon é e o que torna esta instância especial num único parágrafo. Se deixada em branco, remete para a descrição da instância. | |
- title: Breve descrição da instância | |
+ desc_html: Mostrada na barra lateral e em etiquetas de metadados. Descreve o que é o Mastodon e o que torna esta instância especial num único parágrafo. Se deixada em branco, é substituído pela descrição da instância. | |
+ title: Descrição curta da instância | |
site_terms: | |
- desc_html: Podes escrever a sua própria política de privacidade, termos de serviço, entre outras coisas. Pode utilizar etiquetas HTML | |
+ desc_html: Você pode escrever a sua própria Política de Privacidade, Termos de Serviço, entre outras coisas. Você pode usar tags HTML | |
title: Termos de serviço personalizados | |
- site_title: Título do site | |
+ site_title: Nome da instância | |
thumbnail: | |
- desc_html: Usada para visualizações via OpenGraph e API. Recomenda-se 1200x630px | |
+ desc_html: Usada para prévias via OpenGraph e API. Recomenda-se 1200x630px | |
title: Miniatura da instância | |
timeline_preview: | |
- desc_html: Exibir a linha temporal pública na página inicial | |
- title: Visualização da linha temporal | |
+ desc_html: Mostra a linha do tempo pública na página inicial e permite acesso da API à mesma sem autenticação | |
+ title: Permitir acesso não autenticado à linha pública | |
title: Configurações do site | |
trendable_by_default: | |
- desc_html: Afecta as hashtags que ainda não tenham sido proibidas | |
- title: Permitir hashtags em tendência sem revisão prévia | |
+ desc_html: Afeta as hashtags que não foram reprovadas anteriormente | |
+ title: Permitir que hashtags fiquem em alta sem revisão prévia | |
trends: | |
- desc_html: Exibir publicamente hashtags atualmente em destaque que já tenham sido revistas anteriormente | |
- title: Hashtags em destaque | |
+ desc_html: Mostrar publicamente hashtags previamente revisadas que estão em alta | |
+ title: Hashtags em alta | |
site_uploads: | |
- delete: Eliminar arquivo carregado | |
- destroyed_msg: Upload do site eliminado com sucesso! | |
+ delete: Excluir arquivo enviado | |
+ destroyed_msg: Upload do site excluído com sucesso! | |
statuses: | |
back_to_account: Voltar para página da conta | |
batch: | |
- delete: Eliminar | |
- nsfw_off: NSFW OFF | |
- nsfw_on: NSFW ON | |
- deleted: Eliminado | |
- failed_to_execute: Falhou ao executar | |
+ delete: Excluir | |
+ nsfw_off: Desmarcar como sensível | |
+ nsfw_on: Marcar como sensível | |
+ deleted: Excluídos | |
+ failed_to_execute: Falha ao executar | |
media: | |
- title: Media | |
- no_media: Não há media | |
- no_status_selected: Nenhum estado foi alterado porque nenhum foi selecionado | |
- title: Estado das contas | |
- with_media: Com media | |
+ title: Mídia | |
+ no_media: Sem mídia | |
+ no_status_selected: Nenhum status foi modificado porque nenhum estava selecionado | |
+ title: Toots da conta | |
+ with_media: Com mídia | |
system_checks: | |
- database_schema_check: | |
- message_html: Existem migrações de base de dados pendentes. Por favor, execute-as para garantir que o aplicativo se comporte como esperado | |
rules_check: | |
- action: Gerir regras da instância | |
- message_html: Não definiu nenhuma regra para a instância. | |
- sidekiq_process_check: | |
- message_html: Nenhum processo Sidekiq em execução para a(s) fila(s) %{value}. Reveja a configuração do seu Sidekiq | |
+ message_html: Você não definiu nenhuma regra de servidor. | |
tags: | |
- accounts_today: Usos únicos hoje | |
+ accounts_today: Usos únicos de hoje | |
accounts_week: Usos únicos desta semana | |
breakdown: Descrição do consumo atual por fonte | |
- last_active: Última actividade | |
- most_popular: Mais popular | |
- most_recent: Mais recente | |
- name: Hashtag | |
- review: Estado da revisão | |
- reviewed: Revista | |
- title: Hashtags | |
- trending_right_now: Tendências agora | |
- unique_uses_today: "%{count} publicando hoje" | |
- unreviewed: Não revista | |
- updated_msg: Definições de hashtags actualizadas com sucesso | |
+ last_active: Última atividade | |
+ most_popular: Mais populares | |
+ most_recent: Mais recentes | |
+ review: Status da revisão | |
+ reviewed: Revisado | |
+ trending_right_now: Em alta no momento | |
+ unique_uses_today: "%{count} tootando hoje" | |
+ unreviewed: Não revisadas | |
+ updated_msg: Configurações de hashtag atualizadas com sucesso | |
title: Administração | |
warning_presets: | |
add_new: Adicionar novo | |
- delete: Eliminar | |
- edit_preset: Editar o aviso predefinido | |
- empty: Ainda não definiu nenhum aviso predefinido. | |
- title: Gerir os avisos predefinidos | |
+ delete: Excluir | |
+ edit_preset: Editar o aviso pré-definido | |
+ title: Gerenciar os avisos pré-definidos | |
admin_mailer: | |
+ account_invitation: | |
+ subject: Você foi convidado a entrar no Truth Social! | |
new_pending_account: | |
- body: Em baixo, estão os detalhes da nova conta. Pode aprovar ou rejeitar esta inscrição. | |
+ body: Os detalhes da nova conta estão abaixo. Você pode aprovar ou vetar. | |
subject: Nova conta para revisão em %{instance} (%{username}) | |
new_report: | |
- body: "%{reporter} relatou %{target}" | |
- body_remote: Alguém de %{domain} relatou %{target} | |
- subject: Novo relatório sobre %{instance} (#%{id}) | |
+ body: "%{reporter} denunciou %{target}" | |
+ body_remote: Alguém da instância %{domain} reportou %{target} | |
+ subject: Nova denúncia sobre %{instance} (#%{id}) | |
new_trending_tag: | |
- body: 'A hashtag #%{name} está hoje a destacar-se, mas não foi anteriormente revista. Ela não será exibida publicamente a menos que você o permita, ou limite-se a salvar o formulário tal como está, para nunca mais ouvir falar dela.' | |
- subject: Nova hashtag para revisão em %{instance} (#%{name}) | |
+ body: 'A hashtag #%{name} está em alta hoje, mas não foi previamente revisada. Ela não estará visível publicamente a menos que você aprove, ou salve o formulário do jeito que está para nunca mais ouvir falar dela.' | |
+ subject: Nova hashtag disponível para revisão em %{instance} (#%{name}) | |
aliases: | |
- add_new: Criar pseudónimo | |
- created_msg: Criou com sucesso um novo pseudónimo. Pode agora iniciar a migração da conta antiga. | |
- deleted_msg: O pseudónimo foi eliminado com sucesso. Migrar dessa conta para esta não será mais possível. | |
- empty: Não tem pseudónimos. | |
- hint_html: Se quiser mudar de outra conta para esta, pode criar aqui um pseudónimo, que é necessário antes de poder prosseguir com a migração de seguidores da conta antiga para esta. Esta ação por si só é <strong>inofensiva e reversível</strong>. <strong>A migração da conta é iniciada a partir da conta antiga</strong>. | |
- remove: Desvincular pseudónimo | |
+ add_new: Criar alias | |
+ created_msg: Um novo alias foi criado com sucesso. Agora você pode iniciar a mudança da conta antiga. | |
+ deleted_msg: Alias removido com sucesso. Não será mais possível se mudar daquela conta para esta conta. | |
+ empty: Você não tem alias. | |
+ hint_html: Se você quiser migrar de uma outra conta para esta, você pode criar um alias aqui, o que é necessário antes que você possa migrar os seguidores da conta antiga para esta. Esta ação por si só é <strong>inofensiva e reversível</strong>. <strong>A migração da conta é iniciada pela conta antiga</strong>. | |
+ remove: Desvincular alias | |
appearance: | |
- advanced_web_interface: Interface web avançada | |
- advanced_web_interface_hint: 'Se quiser utilizar toda a largura do seu ecrã, a interface web avançada permite-lhe configurar várias colunas diferentes para ver tanta informação ao mesmo tempo quanto quiser: Página inicial, notificações, cronologia federada, qualquer número de listas e hashtags.' | |
+ advanced_web_interface: Interface avançada de colunas | |
+ advanced_web_interface_hint: 'Se você deseja usar toda a sua largura de tela, a interface avançada permite que você configure muitas colunas diferentes para ver tantas informações ao mesmo tempo quanto você deseja: Página inicial, notificações, linha local, linha global, qualquer número de listas e hashtags.' | |
animations_and_accessibility: Animações e acessibilidade | |
- confirmation_dialogs: Caixas de confirmação | |
+ confirmation_dialogs: Diálogos de confirmação | |
discovery: Descobrir | |
localization: | |
body: Mastodon é traduzido por voluntários. | |
- guide_link: https://crowdin.com/project/mastodon/pt-PT | |
+ guide_link: https://br.crowdin.com/project/mastodon | |
guide_link_text: Todos podem contribuir. | |
sensitive_content: Conteúdo sensível | |
- toot_layout: Disposição do Toot | |
+ toot_layout: Layout do Toot | |
application_mailer: | |
notification_preferences: Alterar preferências de e-mail | |
- salutation: "%{name}," | |
- settings: 'Alterar preferências de email: %{link}' | |
+ settings: 'Alterar e-mail de preferência: %{link}' | |
view: 'Ver:' | |
view_profile: Ver perfil | |
- view_status: Ver publicação | |
+ view_status: Ver toot | |
applications: | |
- created: Aplicação criada com sucesso | |
- destroyed: Aplicação eliminada com sucesso | |
- invalid_url: O URL é inválido | |
- regenerate_token: Regenerar token de acesso | |
- token_regenerated: Token de acesso regenerado com sucesso | |
- warning: Cuidado com estes dados. Não partilhar com ninguém! | |
- your_token: O teu token de acesso | |
+ created: Aplicativo criado com sucesso | |
+ destroyed: Aplicativo excluído com sucesso | |
+ invalid_url: O link fornecido é inválido | |
+ regenerate_token: Gerar código de acesso | |
+ token_regenerated: Código de acesso gerado com sucesso | |
+ warning: Tenha cuidado com estes dados. Nunca compartilhe com alguém! | |
+ your_token: Seu código de acesso | |
auth: | |
- apply_for_account: Solicitar um convite | |
- change_password: Palavra-passe | |
- checkbox_agreement_html: Concordo com as <a href="%{rules_path}" target="_blank">regras da instância</a> e com os <a href="%{terms_path}" target="_blank">termos de serviço</a> | |
+ apply_for_account: Solicitar convite | |
+ change_password: Senha | |
+ checkbox_agreement_html: Concordo com <a href="%{rules_path}" target="_blank">as regras da instância</a> e com <a href="%{terms_path}" target="_blank">os termos de serviço</a> | |
checkbox_agreement_without_rules_html: Concordo com os <a href="%{terms_path}" target="_blank">termos do serviço</a> | |
- delete_account: Eliminar conta | |
- delete_account_html: Se deseja eliminar a sua conta, pode <a href="%{path}">continuar aqui</a>. Uma confirmação será solicitada. | |
+ delete_account: Excluir conta | |
+ delete_account_html: Se você deseja excluir sua conta, você pode <a href="%{path}">fazer isso aqui</a>. Uma confirmação será solicitada. | |
description: | |
- prefix_invited_by_user: "@%{name} convidou-o a juntar-se a esta instância do Mastodon!" | |
- prefix_sign_up: Inscreva-se hoje no Mastodon! | |
- suffix: Com uma conta, poderá seguir pessoas, publicar atualizações e trocar mensagens com utilizadores de qualquer instância Mastodon e muito mais! | |
- didnt_get_confirmation: Não recebeu o email de confirmação? | |
- dont_have_your_security_key: Não tem a sua chave de segurança? | |
- forgot_password: Esqueceste a palavra-passe? | |
- invalid_reset_password_token: Token de modificação da palavra-passe é inválido ou expirou. Por favor, solicita um novo. | |
- link_to_otp: Insere um código de duas etapas do teu telemóvel ou um código de recuperação | |
- link_to_webauth: Usa o teu dispositivo de chave de segurança | |
+ prefix_invited_by_user: "@%{name} convidou você para entrar nesta instância Mastodon!" | |
+ prefix_sign_up: Crie uma conta no Mastodon hoje! | |
+ suffix: Com uma conta, você poderá seguir pessoas, postar atualizações, trocar mensagens com usuários de qualquer instância Mastodon e muito mais! | |
+ didnt_get_confirmation: Não recebeu instruções de confirmação? | |
+ dont_have_your_security_key: Não está com a sua chave de segurança? | |
+ forgot_password: Esqueceu a sua senha? | |
+ invalid_reset_password_token: O token de redefinição de senha é inválido ou expirou. Solicite um novo token. | |
+ link_to_otp: Digite um código de duas etapas do seu telefone ou um código de recuperação | |
+ link_to_webauth: Use seu dispositivo de chave de segurança | |
login: Entrar | |
logout: Sair | |
- migrate_account: Mudar para uma conta diferente | |
- migrate_account_html: Se deseja redirecionar esta conta para uma outra pode <a href="%{path}">configurar isso aqui</a>. | |
- or_log_in_with: Ou iniciar sessão com | |
- providers: | |
- cas: CAS | |
- saml: SAML | |
- register: Registar | |
- registration_closed: "%{instance} não está a aceitar novos membros" | |
+ migrate_account: Mudar-se para outra conta | |
+ migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>. | |
+ or_log_in_with: Ou entre com | |
+ register: Criar conta | |
+ registration_closed: "%{instance} não está aceitando novos membros" | |
resend_confirmation: Reenviar instruções de confirmação | |
- reset_password: Criar nova palavra-passe | |
- security: Alterar palavra-passe | |
- set_new_password: Editar palavra-passe | |
+ reset_password: Redefinir senha | |
+ security: Segurança | |
+ set_new_password: Definir uma nova senha | |
setup: | |
- email_below_hint_html: Se o endereço de e-mail abaixo estiver incorreto, pode alterá-lo aqui e receber um novo e-mail de confirmação. | |
- email_settings_hint_html: O e-mail de confirmação foi enviado para %{email}. Se esse endereço de e-mail não estiver correcto, pode alterá-lo nas definições da conta. | |
- title: Configuração | |
+ email_below_hint_html: Se o endereço de e-mail abaixo não for seu, você pode alterá-lo aqui e receber um novo e-mail de confirmação. | |
+ email_settings_hint_html: O e-mail de confirmação foi enviado para %{email}. Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta. | |
+ title: Configurações | |
status: | |
- account_status: Estado da conta | |
- confirming: A aguardar que conclua a confirmação do e-mail. | |
- functional: A sua conta está totalmente operacional. | |
- pending: A sua inscrição está pendente de revisão pela nossa equipa. Isso pode demorar algum tempo. Receberá um e-mail se a sua conta for aprovada. | |
- redirecting_to: A sua conta está inativa porque está atualmente a ser redirecionada para %{acct}. | |
- too_fast: Formulário enviado muito rapidamente, tente novamente. | |
- trouble_logging_in: Problemas em iniciar sessão? | |
+ account_status: Status da conta | |
+ confirming: Confirmação por e-mail pendente. | |
+ functional: Sua conta está totalmente operacional. | |
+ pending: Sua solicitação está com revisão pendente por parte de nossa equipe. Você receberá um e-mail se ela for aprovada. | |
+ redirecting_to: Sua conta está inativa porque atualmente está redirecionando para %{acct}. | |
+ too_fast: O formulário foi enviado muito rapidamente, tente novamente. | |
+ trouble_logging_in: Problemas para entrar? | |
use_security_key: Usar chave de segurança | |
authorize_follow: | |
- already_following: Tu já estás a seguir esta conta | |
- already_requested: Já enviou anteriormente um pedido para seguir esta conta | |
+ already_following: Você já segue | |
+ already_requested: Você já enviou uma solicitação para seguir esta conta | |
error: Infelizmente, ocorreu um erro ao buscar a conta remota | |
follow: Seguir | |
- follow_request: 'Enviaste uma solicitação de seguidor para:' | |
- following: 'Sucesso! Agora estás a seguir a:' | |
+ follow_request: 'Você mandou solicitação para seguir para:' | |
+ following: 'Sucesso! Você agora está seguindo:' | |
post_follow: | |
- close: Ou podes simplesmente fechar esta janela. | |
- return: Voltar ao perfil do utilizador | |
+ close: Ou você pode simplesmente fechar esta janela. | |
+ return: Mostrar o perfil do usuário | |
web: Voltar à página inicial | |
title: Seguir %{acct} | |
challenge: | |
confirm: Continuar | |
- hint_html: "<strong>Dica:</strong> Não vamos pedir novamente a sua senha durante a próxima hora." | |
+ hint_html: "<strong>Dica:</strong> Não pediremos novamente sua senha pela próxima hora." | |
invalid_password: Senha inválida | |
- prompt: Confirme a sua senha para continuar | |
+ prompt: Confirme sua senha para continuar | |
+ chats: | |
+ errors: | |
+ creator_started: Não foi possível aceitar esse chat já que você o iniciou | |
+ blocked_constraints: Não foi possível executar a solicitação devido a restrições bloqueadas | |
+ unfollowed_and_left_chat_by_user: This user has left the chat and no longer follows you | |
crypto: | |
errors: | |
invalid_key: não é uma chave Ed25519 ou Curve25519 válida | |
invalid_signature: não é uma assinatura Ed25519 válida | |
date: | |
formats: | |
- default: "%d %b %Y" | |
- with_month_name: "%d %B %Y" | |
+ default: "%d %b, %Y" | |
+ with_month_name: "%d de %b de %Y" | |
datetime: | |
distance_in_words: | |
- about_x_hours: "%{count}h" | |
- about_x_months: "%{count} meses" | |
- about_x_years: "%{count} anos" | |
- almost_x_years: "%{count} anos" | |
- half_a_minute: Justo agora | |
- less_than_x_minutes: "%{count} meses" | |
- less_than_x_seconds: Justo agora | |
- over_x_years: "%{count} anos" | |
- x_days: "%{count} dias" | |
- x_minutes: "%{count} minutos" | |
- x_months: "%{count} meses" | |
- x_seconds: "%{count} segundos" | |
+ about_x_months: "%{count}m" | |
+ about_x_years: "%{count}a" | |
+ almost_x_years: "%{count}a" | |
+ half_a_minute: Agora | |
+ less_than_x_seconds: Agora | |
+ over_x_years: "%{count}a" | |
+ x_minutes: "%{count}min" | |
+ x_months: "%{count}m" | |
+ x_seconds: "%{count}seg" | |
deletes: | |
- challenge_not_passed: A informação que introduziu não estava correta | |
- confirm_password: Introduza a sua palavra-passe atual para verificar a sua identidade | |
- confirm_username: Introduza o seu nome de utilizador para confirmar o procedimento | |
- proceed: Eliminar conta | |
- success_msg: A sua conta foi eliminada com sucesso | |
+ challenge_not_passed: A informação que você inseriu não está correta | |
+ confirm_password: Digite a sua senha atual para verificar a sua identidade | |
+ confirm_username: Digite seu nome de usuário para confirmar o procedimento | |
+ proceed: Excluir conta | |
+ success_msg: Sua conta foi excluída com sucesso | |
warning: | |
- before: 'Antes de continuar, por favor leia cuidadosamente estas notas:' | |
- caches: O conteúdo que foi armazenado em cache por outras instâncias pode persistir | |
- data_removal: As suas publicações e outros dados serão eliminados permanentemente | |
- email_change_html: Pode <a href="%{path}">alterar o seu endereço de e-mail</a> sem eliminar a sua conta | |
- email_contact_html: Se ainda não chegou, pode enviar um e-mail a <a href="mailto:%{email}">%{email}</a> para obter ajuda | |
- email_reconfirmation_html: Se não recebeu o e-mail de confirmação, pode <a href="%{path}">pedi-lo novamente</a> | |
- irreversible: Não será possível restaurar ou reativar sua conta | |
- more_details_html: Para mais detalhes, leia a <a href="%{terms_path}">política de privacidade</a>. | |
- username_available: O seu nome de utilizador ficará novamente disponível | |
- username_unavailable: O seu nome de utilizador permanecerá indisponível | |
+ before: 'Antes de prosseguir, por favor leia com cuidado:' | |
+ caches: Conteúdo que foi armazenado em cache por outras instâncias pode continuar a existir | |
+ data_removal: Seus toots e outros dados serão removidos permanentemente | |
+ email_change_html: Você pode <a href="%{path}">alterar seu endereço de e-mail</a> sem excluir sua conta | |
+ email_contact_html: Se você ainda não recebeu, você pode enviar um e-mail pedindo ajuda para <a href="mailto:%{email}">%{email}</a> | |
+ email_reconfirmation_html: Se você não está recebendo o e-mail de confirmação, você pode <a href="%{path}">solicitá-lo novamente</a> | |
+ irreversible: Você não conseguirá restaurar ou reativar a sua conta | |
+ more_details_html: Para mais detalhes, consulte a <a href="%{terms_path}">Política de Privacidade</a>. | |
+ username_available: Seu nome de usuário ficará disponível novamente | |
+ username_unavailable: Seu nome de usuário permanecerá indisponível | |
directories: | |
- directory: Dirétorio de perfil | |
- explanation: Descobre utilizadores com base nos seus interesses | |
- explore_mastodon: Explorar %{title} | |
+ directory: Diretório de perfis | |
+ explanation: Descobrir usuários baseado em seus interesses | |
+ explore_mastodon: Explore o %{title} | |
domain_validator: | |
invalid_domain: não é um nome de domínio válido | |
errors: | |
- '400': O pedido que submeteu foi inválido ou mal formulado. | |
- '403': Não tens a permissão necessária para ver esta página. | |
- '404': A página que estás a procurar não existe. | |
+ '400': A solicitação enviada é inválida ou incorreta. | |
+ '403': Você não tem permissão para ver esta página. | |
+ '404': A página pela qual você está procurando não existe. | |
'406': Esta página não está disponível no formato solicitado. | |
- '410': A página que estás a procurar não existe mais. | |
+ '410': A página que você procura não existe mais. | |
'422': | |
- content: "A verificação de segurança falhou. \nDesativaste o uso de cookies?" | |
- title: A verificação de segurança falhou | |
- '429': Desacelerado | |
+ content: Falha na verificação de segurança. Você está bloqueando cookies? | |
+ title: Falha na verificação de segurança | |
+ '429': Excesso de solicitações | |
'500': | |
- content: Desculpe, mas algo correu mal. | |
- title: Esta página não está correta | |
- '503': A página não pôde ser apresentada devido a uma falha temporária do servidor. | |
- noscript_html: Para usar o aplicativo web do Mastodon, por favor ativa o JavaScript. Alternativamente, experimenta um dos <a href="%{apps_path}">apps nativos</a> para o Mastodon na sua plataforma. | |
+ content: Desculpe, algo deu errado por aqui. | |
+ title: Esta página não está certa | |
+ '503': A página não pôde ser carregada devido a uma falha temporária do servidor. | |
+ noscript_html: Para usar o aplicativo web do Mastodon, por favor ative o JavaScript. Ou, se quiser, experimente um dos <a href="%{apps_path}">aplicativos nativos</a> para o Mastodon em sua plataforma. | |
+ api: | |
+ '401': Esse método requer um usuário autenticado | |
+ '403': Essa ação não é permitida | |
+ '404': Registro não encontrado | |
+ '503': Ocorreu um problema temporário ao atender sua solicitação. Tente novamente. | |
+ assertion: Não foi possível verificar sua afirmação | |
+ attestation: Não foi possível verificar seu atestado | |
+ data_fetch: Não foi possível resgatar os dados remotos | |
+ duplicate: Registro duplicado | |
+ login_disabled: No momento seu login está desativado | |
+ login_pending: No momento seu login está aguardando aprovação | |
+ missing_email: Está faltando um endereço de e-mail confirmado para o seu login | |
+ ssl: Não foi possível verificar o Certificado SSL remoto | |
+ outside_scopes: Essa ação está fora do escopo autorizado | |
+ unauthorized: Não autorizado | |
existing_username_validator: | |
- not_found: não foi possível encontrar um utilizador local com esse nome | |
+ not_found: não foi possível encontrar um usuário local com esse nome de usuário | |
not_found_multiple: não foi possível encontrar %{usernames} | |
exports: | |
archive_takeout: | |
date: Data | |
- download: Descarregar o teu arquivo | |
- hint_html: Pode pedir um arquivo das suas <strong>publicações e ficheiros de media carregados</strong>. Os dados no ficheiro exportado estarão no formato ActivityPub, que pode ser lido com qualquer software compatível. Pode solicitar um arquivo a cada 7 dias. | |
- in_progress: A compilar o seu arquivo... | |
- request: Pede o teu arquivo | |
+ download: Baixe o seu arquivo | |
+ hint_html: Você pode pedir um arquivo dos seus <strong>toots e mídias enviadas</strong>. Os dados exportados estarão no formato ActivityPub, que podem ser lidos por qualquer software compatível. Você pode pedir um arquivo a cada 7 dias. | |
+ in_progress: Preparando o seu arquivo... | |
+ request: Solicitar o seu arquivo | |
size: Tamanho | |
- blocks: Bloqueaste | |
- bookmarks: Itens Salvos | |
- csv: CSV | |
+ blocks: Você bloqueou | |
+ bookmarks: Marcadores | |
domain_blocks: Bloqueios de domínio | |
lists: Listas | |
- mutes: Tens em silêncio | |
- storage: Armazenamento de media | |
+ mutes: Você silenciou | |
+ storage: Armazenamento de mídia | |
featured_tags: | |
- add_new: Adicionar nova | |
+ add_new: Adicionar hashtag | |
errors: | |
- limit: Já atingiste o limite máximo de hashtags | |
- hint_html: "<strong>O que são hashtags em destaque?</strong> Elas são exibidas de forma bem visível no seu perfil público e permitem que as pessoas consultem as suas publicações públicas especificamente sob essas hashtags. São uma ótima ferramenta para manter o controlo de trabalhos criativos ou projetos de longo prazo." | |
+ limit: Você atingiu o limite de hashtags em destaque | |
+ hint_html: "<strong>O que são hashtags em destaque?</strong> Elas são mostradas no seu perfil público e permitem que as pessoas acessem seus toots públicos que contenham especificamente essas hashtags. São uma excelente ferramenta para acompanhar os trabalhos criativos ou os projetos de longo prazo." | |
filters: | |
contexts: | |
account: Perfis | |
- home: Cronologia inicial | |
+ home: Página inicial | |
notifications: Notificações | |
- public: Cronologias públicas | |
- thread: Conversações | |
+ public: Linhas públicas | |
+ thread: Conversas | |
edit: | |
- title: Editar filtros | |
+ title: Editar filtro | |
errors: | |
- invalid_context: Inválido ou nenhum contexto fornecido | |
- invalid_irreversible: Filtragem irreversível só funciona no contexto das notificações ou do início | |
+ invalid_context: Contexto inválido ou nenhum contexto informado | |
+ invalid_irreversible: O filtro irreversível só funciona com os contextos página inicial e notificações | |
index: | |
- delete: Eliminar | |
- empty: Não tem filtros. | |
+ delete: Remover | |
+ empty: Sem filtros. | |
title: Filtros | |
new: | |
- title: Adicionar novo filtro | |
+ title: Adicionar filtro | |
footer: | |
- developers: Responsáveis pelo desenvolvimento | |
+ developers: Desenvolvedores | |
more: Mais… | |
resources: Recursos | |
- trending_now: Tendências atuais | |
+ trending_now: Em alta no momento | |
generic: | |
all: Tudo | |
- changes_saved_msg: Alterações guardadas! | |
+ changes_saved_msg: Alterações salvas com sucesso! | |
copy: Copiar | |
- delete: Eliminar | |
+ delete: Excluir | |
no_batch_actions_available: Nenhuma ação em lote disponível nesta página | |
order_by: Ordenar por | |
- save_changes: Guardar alterações | |
+ save_changes: Salvar alterações | |
validation_errors: | |
- one: Algo não está correcto. Por favor vê o erro abaixo | |
- other: Algo não está correto. Por favor vê os %{count} erros abaixo | |
+ one: Algo errado não está certo! Por favor, analise o erro abaixo | |
+ other: Algo errado não está certo! Por favor, analise os %{count} erros abaixo | |
+ groups: | |
+ errors: | |
+ pending_request_conflict: O proprietário ou administrador do grupo já tomou medidas sobre esta solicitação. | |
+ too_many_admins: Você pode atribuir até %{count} administradores para o grupo. | |
html_validator: | |
- invalid_markup: 'contém marcação HTML inválida: %{error}' | |
+ invalid_markup: 'contém HTML inválido: %{error}' | |
identity_proofs: | |
active: Ativo | |
authorize: Sim, autorizar | |
- authorize_connection_prompt: Autorizar esta conexão criptográfica? | |
+ authorize_connection_prompt: Autorizar essa conexão criptográfica? | |
errors: | |
- failed: A conexão criptográfica falhou. Por favor, tente novamente a partir de %{provider}. | |
+ failed: Falha na conexão criptográfica. Por favor, tente novamente a partir de %{provider}. | |
keybase: | |
- invalid_token: Os tokens Keybase são hashes de assinaturas e devem conter 66 caracteres hexadecimais | |
- verification_failed: O Keybase não reconhece este token como uma assinatura do utilizador do Keybase %{kb_username}. Por favor, tente novamente a partir do Keybase. | |
- wrong_user: Não é possível criar um comprovativo para %{proving} enquanto estiver conetado como %{current}. Inicie sessão como %{proving} e tente novamente. | |
- explanation_html: Aqui pode conetar criptograficamente as suas outras identidades, tais como um perfil Keybase. Isto permite que outras pessoas lhe enviem mensagens encriptadas e confiar em conteúdo que você lhes envia. | |
- i_am_html: Sou %{username} em %{service}. | |
+ invalid_token: Tokens keybase são hashes de assinatura e devem conter 66 caracteres hexa | |
+ verification_failed: Keybase não reconhece esse token como uma assinatura do usuário keybase %{kb_username}. Por favor, tente novamente a partir do Keybase. | |
+ wrong_user: Não foi possível criar uma prova para %{proving} como %{current}. Entre como %{proving} e tente novamente. | |
+ explanation_html: Você pode conectar criptograficamente suas outras identidades, tais quais seu perfil Keybase. Isso permite outras pessoas de lhe enviarem mensagens criptografadas e confiar no conteúdo que você as envia. | |
+ i_am_html: Eu sou %{username} em %{service}. | |
identity: Identidade | |
inactive: Inativo | |
- publicize_checkbox: 'E publique isso:' | |
- publicize_toot: 'Está comprovado! Eu sou %{username} em %{service}: %{url}' | |
- remove: Remover comprovatido da conta | |
- removed: Comprovativo removido da conta com sucesso | |
- status: Estado da verificação | |
+ publicize_checkbox: 'E toote isso:' | |
+ publicize_toot: 'Está provado! Eu sou %{username} no %{service}: %{url}' | |
+ remove: Remover prova da conta | |
+ removed: Prova removida da conta com sucesso | |
+ status: Status da verificação | |
view_proof: Ver prova | |
imports: | |
- errors: | |
- over_rows_processing_limit: contém mais de %{count} linhas | |
modes: | |
merge: Juntar | |
- merge_long: Manter os registos existentes e adicionar novos registos | |
- overwrite: Escrever por cima | |
- overwrite_long: Substituir os registos atuais pelos novos | |
- preface: Podes importar dados que tenhas exportado de outra instância, como a lista de pessoas que segues ou bloqueadas. | |
- success: Os teus dados foram enviados com sucesso e serão processados em breve | |
+ merge_long: Manter os registros existentes e adicionar novos | |
+ overwrite: Sobrescrever | |
+ overwrite_long: Substituir os registros atuais com os novos | |
+ preface: Você pode importar dados que você exportou de outra instância, como a lista de pessoas que você segue ou bloqueou. | |
+ success: Os seus dados foram enviados com sucesso e serão processados em instantes | |
types: | |
blocking: Lista de bloqueio | |
- bookmarks: Itens salvos | |
+ bookmarks: Marcadores | |
domain_blocking: Lista de domínios bloqueados | |
- following: Lista de pessoas que estás a seguir | |
- muting: Lista de utilizadores silenciados | |
+ following: Pessoas que você segue | |
+ muting: Lista de silenciados | |
upload: Enviar | |
in_memoriam_html: Em memória. | |
invites: | |
@@ -992,143 +932,231 @@ | |
'604800': 1 semana | |
'86400': 1 dia | |
expires_in_prompt: Nunca | |
- generate: Gerar | |
- invited_by: 'Tu foste convidado por:' | |
+ generate: Gerar convite | |
+ invited_by: 'Você recebeu convite de:' | |
max_uses: | |
one: 1 uso | |
other: "%{count} usos" | |
max_uses_prompt: Sem limite | |
- prompt: Gerar e partilhar ligações com outras pessoas para permitir acesso a essa instância | |
+ prompt: Gere e compartilhe links para permitir acesso a essa instância | |
table: | |
- expires_at: Expira | |
+ expires_at: Expira em | |
uses: Usos | |
title: Convidar pessoas | |
lists: | |
errors: | |
- limit: Número máximo de listas alcançado | |
+ limit: Você atingiu o máximo de listas | |
media_attachments: | |
validations: | |
- images_and_video: Não é possível anexar um vídeo a uma publicação que já contém imagens | |
- not_ready: Não é possível anexar arquivos que não terminaram de ser processados. Tente novamente daqui a pouco! | |
+ images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens | |
+ not_ready: Não é possível anexar arquivos cujo processamento ainda não foi concluído. Tente novamente daqui a pouco! | |
too_many: Não é possível anexar mais de 4 arquivos | |
migrations: | |
- acct: username@domain da nova conta | |
+ acct: Mudou-se para | |
cancel: Cancelar redirecionamento | |
- cancel_explanation: Cancelar o redirecionamento irá reativar a sua conta atual, mas não trará de volta os seguidores que foram migrados para essa conta. | |
- cancelled_msg: Cancelou com sucesso o redireccionamento. | |
+ cancel_explanation: Cancelar o redirecionamento reativará a sua conta atual, mas não trará de volta os seguidores que não foram migrados para aquela conta. | |
+ cancelled_msg: Redirecionamento cancelado com sucesso. | |
errors: | |
- already_moved: é a mesma conta para a qual já migrou | |
- missing_also_known_as: não está a referenciar esta conta | |
- move_to_self: não pode ser conta atual | |
- not_found: não pode ser encontrado | |
+ already_moved: é a mesma conta que você migrou | |
+ missing_also_known_as: não está referenciando esta conta | |
+ move_to_self: não pode ser a conta atual | |
+ not_found: não pôde ser encontrado | |
on_cooldown: Você está no período de espera | |
- followers_count: Seguidores no momento da migração | |
- incoming_migrations: A migrar de uma conta diferente | |
- incoming_migrations_html: Para migrar de outra conta para esta, primeiro você precisa <a href="%{path}">criar um pseudónimo</a>. | |
- moved_msg: A sua conta está agora a ser redireccionada para %{acct} e os seus seguidores estão a ser transferidos. | |
- not_redirecting: A sua conta não está atualmente a ser redirecionada para nenhuma outra conta. | |
- on_cooldown: Migrou recentemente a sua conta. Esta função ficará disponível novamente em %{count} dias. | |
- past_migrations: Migrações anteriores | |
+ followers_count: Seguidores no momento da mudança | |
+ incoming_migrations: Migrando de outra conta | |
+ incoming_migrations_html: Para mover de outra conta para esta, você precisa <a href="%{path}">criar um alias</a>. | |
+ moved_msg: Agora sua conta está redirecionando para %{acct} e seus seguidores estão sendo movidos. | |
+ not_redirecting: Sua conta não está redirecionando para nenhuma outra conta atualmente. | |
+ on_cooldown: Você migrou recentemente sua conta. Esta função ficará disponível novamente em %{count} dias. | |
+ past_migrations: Migrações passadas | |
proceed_with_move: Migrar seguidores | |
- redirected_msg: A sua conta está agora a ser redireccionada para %{acct}. | |
- redirecting_to: A sua conta está a ser redireccionada para %{acct}. | |
+ redirected_msg: Agora sua conta está redirecionando para %{acct}. | |
+ redirecting_to: Sua conta está redirecionando para %{acct}. | |
set_redirect: Definir redirecionamento | |
warning: | |
backreference_required: A nova conta deve primeiro ser configurada para que esta seja referenciada | |
- before: 'Antes de continuar, leia cuidadosamente estas notas:' | |
- cooldown: Após a migração, há um período de tempo de espera durante o qual não poderá voltar a migrar | |
- disabled_account: Posteriormente, a sua conta atual não será totalmente utilizável. No entanto, continuará a ter acesso à exportação de dados, bem como à reativação. | |
- followers: Esta ação irá migrar todos os seguidores da conta atual para a nova conta | |
- only_redirect_html: Em alternativa, pode <a href="%{path}">apenas colocar um redireccionamento no seu perfil</a>. | |
- other_data: Nenhum outro dado será migrado automaticamente | |
- redirect: O perfil da sua conta atual será atualizado com um aviso de redirecionamento e será excluído das pesquisas | |
+ before: 'Antes de prosseguir, por favor leia com cuidado:' | |
+ cooldown: Depois de se mudar, há um período de espera para poder efetuar uma nova mudança | |
+ disabled_account: Sua conta não estará totalmente funcional ao término deste processo. Entretanto, você terá acesso à exportação de dados bem como à reativação. | |
+ followers: Esta ação moverá todos os seguidores da conta atual para a nova conta | |
+ only_redirect_html: Alternativamente, você pode <a href="%{path}">apenas colocar um redirecionamento no seu perfil</a>. | |
+ other_data: Nenhum outro dado será movido automaticamente | |
+ redirect: O perfil atual da sua conta será atualizado com um aviso de redirecionamento e também será excluído das pesquisas | |
moderation: | |
title: Moderação | |
move_handler: | |
- carry_blocks_over_text: Este utilizador migrou de %{acct}, que você tinha bloqueado. | |
- carry_mutes_over_text: Este utilizador migrou de %{acct}, que você tinha silenciado. | |
- copy_account_note_text: 'Este utilizador migrou de %{acct}, aqui estão as suas notas anteriores sobre ele:' | |
+ carry_blocks_over_text: Este usuário mudou de %{acct}, que você havia bloqueado. | |
+ carry_mutes_over_text: Este usuário mudou de %{acct}, que você havia silenciado. | |
+ copy_account_note_text: 'Este usuário saiu de %{acct}, aqui estão suas notas anteriores sobre ele:' | |
notification_mailer: | |
+ chat: | |
+ subject: "Nova mensagem de %{name}" | |
+ subject_android: "lhe enviou uma mensagem" | |
+ sent_message: "Nova mensagem" | |
digest: | |
action: Ver todas as notificações | |
- body: Aqui tens um breve resumo do que perdeste desde o último acesso a %{since} | |
- mention: "%{name} mencionou-te em:" | |
+ body: Aqui está um breve resumo das mensagens que você não viu desde seu último acesso em %{since} | |
+ mention: "%{name} mencionou você em:" | |
new_followers_summary: | |
- one: Tens um novo seguidor! Boa! | |
- other: Tens %{count} novos seguidores! Fantástico! | |
+ one: Além disso, enquanto estava fora você ganhou um novo seguidor! Oba! | |
+ other: Além disso, enquanto estava fora você ganhou %{count} novos seguidores! Incrível! | |
subject: | |
- one: "1 nova notificação desde o último acesso \U0001F418" | |
- other: "%{count} novas notificações desde o último acesso \U0001F418" | |
- title: Enquanto estiveste ausente… | |
+ one: "1 nova notificação desde seu último acesso \U0001F418" | |
+ other: "%{count} novas notificações desde seu último acesso \U0001F418" | |
+ subject_android: | |
+ one: "1 nova notificação desde seu último acesso \U0001F418" | |
+ other: "%{count} novas notificações desde seu último acesso \U0001F418" | |
+ title: Na sua ausência... | |
favourite: | |
- body: 'O teu post foi adicionado aos favoritos por %{name}:' | |
- subject: "%{name} adicionou o teu post aos favoritos" | |
+ body: "Sua postagem foi curtida por %{name}:" | |
+ subject: "%{name} curtiu sua postagem" | |
+ subject_android: "gostou da sua Truth" | |
title: Novo favorito | |
+ favourite_group: | |
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth" | |
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth" | |
+ group_favourite: | |
+ body: "Sua postagem foi curtida por %{name}:" | |
+ subject: "%{name} curtiu sua postagem" | |
+ subject_android: "gostou da sua Truth em %{group}" | |
+ title: Novo favorito | |
+ group_favourite_group: | |
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth" | |
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth em %{group}" | |
follow: | |
- body: "%{name} é teu seguidor!" | |
- subject: "%{name} começou a seguir-te" | |
+ body: "%{name} está seguindo você!" | |
+ subject: "%{name} está seguindo você" | |
+ subject_android: "está te seguindo agora" | |
title: Novo seguidor | |
+ follow_group: | |
+ subject: "%{name} + %{count_others} %{actor} seguiram você" | |
+ subject_android: "%{name} e %{count_others} outros seguiram você" | |
follow_request: | |
- action: Gerir pedidos de seguidores | |
- body: "%{name} solicitou autorização para te seguir" | |
- subject: 'Seguidor pendente: %{name}' | |
+ action: Gerenciar solicitações de seguidores | |
+ body: "%{name} pediu para seguir você" | |
+ subject: 'Seguidor aguardando: %{name}' | |
+ subject_android: 'pediu para te seguir' | |
title: Nova solicitação de seguidor | |
mention: | |
action: Responder | |
- body: 'Foste mencionado por %{name}:' | |
- subject: "%{name} mencionou-te" | |
+ body: "Você foi mencionado por %{name} em:" | |
+ subject: "Você foi mencionado por %{name}" | |
+ subject_android: "mencionou você em uma Truth" | |
title: Nova menção | |
+ mention_group: | |
+ subject: "%{name} + %{count_others} %{actor} mencionaram você" | |
+ subject_android: "%{name} e %{count_others} outros mencionaram você" | |
+ group_mention: | |
+ action: Responder | |
+ body: "Você foi mencionado por %{name} em:" | |
+ subject: "Você foi mencionado por %{name}" | |
+ subject_android: "mencionou você em %{group}" | |
+ title: Nova menção | |
+ group_mention_group: | |
+ subject: "%{name} + %{count_others} %{actor} mencionaram você" | |
+ subject_android: "%{name} e %{count_others} outros mencionaram você" | |
poll: | |
- subject: Uma votação realizada por %{name} terminou | |
+ subject: Uma enquete postada por %{name} foi encerrada | |
+ subject_android: "concluiu uma enquete" | |
reblog: | |
- body: 'O teu post foi partilhado por %{name}:' | |
- subject: "%{name} partilhou o teu post" | |
- title: Nova partilha | |
+ body: "Sua Truth foi RePostada por %{name}:" | |
+ subject: "%{name} RePostou sua Truth" | |
+ subject_android: "ReTruthed sua Truth" | |
+ title: Nova RePostagem | |
+ reblog_group: | |
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth" | |
+ subject_android: "%{name} e mais %{count_others} outros ReTruthed sua Truth" | |
+ group_reblog: | |
+ body: "Sua Truth foi RePostada por %{name}:" | |
+ subject: "%{name} RePostou sua Truth" | |
+ subject_android: "ReTruthed sua Truth de %{group}" | |
+ title: Nova RePostagem | |
+ group_reblog_group: | |
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth" | |
+ subject_android: "%{name} e %{count_others} outros ReTruthed a sua Truth de grupo" | |
+ group_request: | |
+ body: '%{name} wants to join your group:' | |
+ subject: "%{name} wants to join your group" | |
+ subject_android: "você tem uma nova solicitação de membro" | |
+ title: Group Join Request | |
status: | |
- subject: "%{name} acabou de publicar" | |
+ subject: "%{name} acabou de postar" | |
+ subject_android: "acabou de postar" | |
+ group_promoted: | |
+ body: You are now an admin for %{group} | |
+ subject: You are now an admin for %{group} | |
+ subject_android: "agora você é um administrador" | |
+ title: You're an admin | |
+ group_demoted: | |
+ body: You are no longer an admin for %{group} | |
+ subject: You are no longer an admin for %{group} | |
+ subject_android: "você não é mais administrador" | |
+ title: You're no longer an admin | |
+ user_approved: | |
+ edit_profile_action: Ir para o perfil | |
+ edit_profile_step: Para terminar, não deixe de personalizar seu perfil carregando um avatar, um título, mudando seu nome escolhido e muito mais. Se quiser avaliar novos seguidores antes de aprová-los, você pode bloquear sua conta. | |
+ explanation: Estamos super empolgados com sua chegada à nossa comunidade de Buscadores da Verdade. Acreditamos na liberdade de expressão e incentivamos todos os pontos de vista, já que não discriminamos ninguém devido a uma ideologia política. | |
+ extra_step: Além disso, gostaríamos de lembrar que somos uma plataforma nova e ainda estamos corrigindo vários bugs na nossa tecnologia. Podemos garantir que estamos trabalhando duro para aprimorar tudo o mais rápido possível! Obrigado! | |
+ final_action: Comece a postar | |
+ final_step: 'Comece a postar! Mesmo sem nenhum seguidor suas postagens públicas podem ser vistas por outras pessoas, por exemplo, na timeline local e nas hashtags. Você pode querer se apresentar com a hashtag #apresentações.' | |
+ full_handle: Seu identificador completo (handle) | |
+ full_handle_hint: É o que você quer dizer aos seus amigos para que possam seguir você ou lhe enviar mensagens de outro servidor. | |
+ review_preferences_action: Mudar preferências | |
+ review_preferences_step: Não deixe de configurar suas preferências, como os e-mails que quer receber ou o nível de privacidade padrão para as suas postagens. Se não costuma sentir enjoo você pode optar por reproduzir GIFs automaticamente. | |
+ subject: "Sua espera terminou! Toque aqui para começar a usar o Truth Social." | |
+ subject_android: "Sua espera terminou! Toque aqui para começar a usar o Truth Social." | |
+ web: | |
+ subject: "Bem-vindo(a) ao Truth, onde a Verdade impera" | |
+ tip_federated_timeline: A timeline federada é uma ampla visão ao vivo da rede do Truth. Mas só inclui as pessoas que seus vizinhos estão seguindo, então não é uma visão completa. | |
+ tip_following: Você segue os administradores do seu servidor por padrão Para encontrar mais gente interessante, verifique sua timeline local e federada. | |
+ tip_local_timeline: A timeline local é uma visão ao vivo das pessoas em %{instance}. São seus vizinhos de porta! | |
+ tip_mobile_webapp: Se o seu navegador móvel sugerir que você adicione o Truth à sua tela inicial, você poderá receber notificações por push. O Truth funciona de muitas maneiras como um aplicativo nativo! | |
+ tips: Dicas | |
+ title: Bem-vindo(a) ao Truth Social, %{name}! | |
+ verify_sms_prompt: | |
+ subject: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma! | |
+ subject_android: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma! | |
notifications: | |
email_events: Eventos para notificações por e-mail | |
- email_events_hint: 'Selecione os eventos para os quais deseja receber notificações:' | |
- other_settings: Outras opções de notificações | |
+ email_events_hint: 'Selecione os eventos que deseja receber notificações:' | |
+ other_settings: Outras opções para notificações | |
number: | |
human: | |
decimal_units: | |
- format: "%n%u" | |
units: | |
- billion: MM | |
- million: M | |
- quadrillion: Q | |
- thousand: mil | |
- trillion: T | |
+ billion: BI | |
+ million: MI | |
+ quadrillion: QUA | |
+ thousand: MIL | |
+ trillion: TRI | |
otp_authentication: | |
- code_hint: Introduz o código gerado pela tua aplicação de autenticação para confirmar | |
- description_html: Se ativar a <strong>autenticação em duas etapas</strong>, para entrar na sua conta terá de ter consigo o seu telefone, que vai gerar os tokens necessários à validação do seu acesso. | |
- enable: Ativar | |
- instructions_html: "<strong>Digitalize este código QR no Google Authenticator ou num aplicativo TOTP similar no seu telefone</strong>. A partir de agora, esse aplicativo irá gerar tokens que você terá que introduzir para aceder à sua conta." | |
- manual_instructions: 'Se você não consegue digitalizar o código QR e precisa introduzi-lo manualmente, aqui está o código em texto:' | |
+ code_hint: Digite o código gerado pelo seu aplicativo autenticador para confirmar | |
+ description_html: Se você habilitar a <strong>autenticação de dois fatores</strong> usando um aplicativo autenticador, o login exigirá que você esteja com o seu telefone, que gerará tokens para você entrar. | |
+ enable: Habilitar | |
+ instructions_html: "<strong>Escaneie este código QR no Google Authenticator ou em um aplicativo TOTP similar no seu telefone</strong>. A partir de agora, esse aplicativo irá gerar tokens que você terá que digitar ao fazer login." | |
+ manual_instructions: 'Se você não pode escanear o código QR e precisa digitá-lo manualmente, aqui está o segredo em texto:' | |
setup: Configurar | |
- wrong_code: O código introduzido é inválido! A hora do servidor e a hora do dispositivo estão corretos? | |
+ wrong_code: O código que você inseriu é inválido! | |
pagination: | |
- newer: Mais nova | |
- next: Seguinte | |
- older: Mais velha | |
+ newer: Mais novo | |
+ next: Próximo | |
+ older: Mais antigo | |
prev: Anterior | |
- truncate: "…" | |
polls: | |
errors: | |
- already_voted: Tu já votaste nesta sondagem | |
+ already_voted: Enquete votada | |
duplicate_options: contém itens duplicados | |
- duration_too_long: está demasiado à frente no futuro | |
- duration_too_short: é demasiado cedo | |
- expired: A sondagem já terminou | |
- invalid_choice: A opção de voto escolhida não existe | |
- over_character_limit: não pode ter mais do que %{max} caracteres cada um | |
- too_few_options: tem de ter mais do que um item | |
- too_many_options: não pode conter mais do que %{max} itens | |
+ duration_too_long: é muito longe no futuro | |
+ duration_too_short: é curto demais | |
+ expired: A enquete já terminou | |
+ invalid_choice: Opção inválida | |
+ over_character_limit: não pode ter mais que %{max} caracteres em cada | |
+ too_few_options: deve ter mais que um item | |
+ too_many_options: não pode ter mais que %{max} itens | |
preferences: | |
other: Outro | |
posting_defaults: Padrões de publicação | |
- public_timelines: Cronologias públicas | |
+ public_timelines: Linhas públicas | |
reactions: | |
errors: | |
limit_reached: Limite de reações diferentes atingido | |
@@ -1136,9 +1164,9 @@ | |
relationships: | |
activity: Atividade da conta | |
dormant: Inativo | |
- follow_selected_followers: Seguir seguidores selecionados | |
+ follow_selected_followers: Seguir os seguidores selecionados | |
followers: Seguidores | |
- following: A seguir | |
+ following: Seguindo | |
invited: Convidado | |
last_active: Última atividade | |
most_recent: Mais recente | |
@@ -1147,36 +1175,36 @@ | |
primary: Primário | |
relationship: Relação | |
remove_selected_domains: Remover todos os seguidores dos domínios selecionados | |
- remove_selected_followers: Remover seguidores selecionados | |
- remove_selected_follows: Deixar de seguir os utilizadores selecionados | |
- status: Estado da conta | |
+ remove_selected_followers: Remover os seguidores selecionados | |
+ remove_selected_follows: Deixar de seguir usuários selecionados | |
+ status: Status da conta | |
remote_follow: | |
- acct: Introduza o seu utilizador@domínio do qual quer seguir | |
- missing_resource: Não foi possível achar a URL de redirecionamento para sua conta | |
- no_account_html: Não tens uma conta? Tu podes <a href='%{sign_up_path}' target='_blank'> aderir aqui</a> | |
- proceed: Prossiga para seguir | |
- prompt: 'Você vai seguir:' | |
- reason_html: "<strong> Porque é este passo necessário?</strong> <code>%{instance}</code> pode não ser a instância onde você está registado. Por isso, precisamos de o redirecionar para a sua instância de origem em primeiro lugar." | |
+ acct: Digite o seu usuário@domínio para continuar | |
+ missing_resource: Não foi possível encontrar o link de redirecionamento para sua conta | |
+ no_account_html: Não tem uma conta? Você pode <a href='%{sign_up_path}' target='_blank'>criar uma aqui</a> | |
+ proceed: Continue para seguir | |
+ prompt: 'Você seguirá:' | |
+ reason_html: "<strong>Por que esse passo é necessário?</strong> <code>%{instance}</code> pode não ser a instância onde você se hospedou, então precisamos redirecionar você para a sua instância primeiro." | |
remote_interaction: | |
favourite: | |
- proceed: Prosseguir para os favoritos | |
- prompt: 'Queres favoritar esta publicação:' | |
+ proceed: Continue para favoritar | |
+ prompt: 'Você favoritará este toot:' | |
reblog: | |
- proceed: Prosseguir com partilha | |
- prompt: 'Queres partilhar esta publicação:' | |
+ proceed: Continue para dar boost | |
+ prompt: 'Você dará boost neste toot:' | |
reply: | |
- proceed: Prosseguir com resposta | |
- prompt: 'Queres responder a esta publicação:' | |
+ proceed: Continue para responder | |
+ prompt: 'Você responderá este toot:' | |
scheduled_statuses: | |
- over_daily_limit: Excedeste o limite de %{limit} publicações agendadas para esse dia | |
- over_total_limit: Tu excedeste o limite de %{limit} publicações agendadas | |
- too_soon: A data de agendamento tem de ser futura | |
+ over_daily_limit: Você excedeu o limite de %{limit} toots agendados para esse dia | |
+ over_total_limit: Você excedeu o limite de %{limit} toots agendados | |
+ too_soon: A data agendada precisa ser no futuro | |
sessions: | |
activity: Última atividade | |
browser: Navegador | |
browsers: | |
alipay: Alipay | |
- blackberry: Blackberry | |
+ blackberry: BlackBerry | |
chrome: Chrome | |
edge: Microsoft Edge | |
electron: Electron | |
@@ -1186,7 +1214,7 @@ | |
micro_messenger: MicroMessenger | |
nokia: Navegador Nokia S40 Ovi | |
opera: Opera | |
- otter: Lontra | |
+ otter: Otter | |
phantom_js: PhantomJS | |
qq: QQ Browser | |
safari: Safari | |
@@ -1194,36 +1222,28 @@ | |
weibo: Weibo | |
current_session: Sessão atual | |
description: "%{browser} em %{platform}" | |
- explanation: Estes são os navegadores que estão conectados com a tua conta do Mastodon. | |
- ip: IP | |
+ explanation: Estes são os navegadores que estão conectados com a sua conta Mastodon. | |
platforms: | |
- adobe_air: Adobe Air | |
- android: Android | |
- blackberry: Blackberry | |
- chrome_os: ChromeOS | |
- firefox_os: SO Firefox | |
- ios: iOS | |
- linux: Linux | |
- mac: Mac | |
+ blackberry: BlackBerry | |
+ mac: MacOS | |
other: Plataforma desconhecida | |
- windows: Windows | |
windows_mobile: Windows Mobile | |
windows_phone: Windows Phone | |
- revoke: Revogar | |
- revoke_success: Sessão revogada com sucesso | |
+ revoke: Fechar | |
+ revoke_success: A sessão foi revogada com sucesso | |
title: Sessões | |
settings: | |
account: Conta | |
- account_settings: Definições da conta | |
- aliases: Pseudónimos da conta | |
- appearance: Aspecto | |
+ account_settings: Configurações da conta | |
+ aliases: Alias da conta | |
+ appearance: Aparência | |
authorized_apps: Aplicativos autorizados | |
- back: Voltar ao Mastodon | |
- delete: Eliminação da conta | |
+ back: Voltar para o Mastodon | |
+ delete: Exclusão de conta | |
development: Desenvolvimento | |
edit_profile: Editar perfil | |
export: Exportar dados | |
- featured_tags: Hashtags destacadas | |
+ featured_tags: Hashtags em destaque | |
identity_proofs: Provas de identidade | |
import: Importar | |
import_and_export: Importar e exportar | |
@@ -1232,35 +1252,35 @@ | |
preferences: Preferências | |
profile: Perfil | |
relationships: Seguindo e seguidores | |
- two_factor_authentication: Autenticação em dois passos | |
+ two_factor_authentication: Autenticação de dois fatores | |
webauthn_authentication: Chaves de segurança | |
statuses: | |
attached: | |
audio: | |
one: "%{count} áudio" | |
other: "%{count} áudios" | |
- description: 'Anexadas: %{attached}' | |
+ description: 'Anexado: %{attached}' | |
image: | |
one: "%{count} imagem" | |
other: "%{count} imagens" | |
video: | |
one: "%{count} vídeo" | |
other: "%{count} vídeos" | |
- boosted_from_html: Partilhadas de %{acct_link} | |
- content_warning: 'Aviso de conteúdo: %{warning}' | |
+ boosted_from_html: Boost de %{acct_link} | |
+ content_warning: 'Aviso de Conteúdo: %{warning}' | |
disallowed_hashtags: | |
- one: 'continha uma hashtag proibida: %{tags}' | |
- other: 'continha as hashtags proibidas: %{tags}' | |
+ one: 'continha hashtag não permitida: %{tags}' | |
+ other: 'continha hashtags não permitidas: %{tags}' | |
errors: | |
- in_reply_not_found: A publicação a que está a tentar responder parece não existir. | |
- language_detection: Detectar automaticamente a língua | |
- open_in_web: Abrir no browser | |
- over_character_limit: limite de caracter excedeu %{max} | |
+ in_reply_not_found: Parece que a postagem à qual você está tentando responder não existe. | |
+ language_detection: Detectar idioma automaticamente | |
+ open_in_web: Abrir no navegador | |
+ over_character_limit: limite de caracteres de %{max} excedido | |
pin_errors: | |
- limit: Já fixaste a quantidade máxima de publicações | |
- ownership: Posts de outras pessoas não podem ser fixados | |
- private: Post não-público não pode ser fixado | |
- reblog: Não podes fixar uma partilha | |
+ limit: Quantidade máxima de toots excedida | |
+ ownership: Toots dos outros não podem ser fixados | |
+ private: Toots não-públicos não podem ser fixados | |
+ reblog: Boosts não podem ser fixados | |
poll: | |
total_people: | |
one: "%{count} pessoa" | |
@@ -1273,109 +1293,107 @@ | |
show_newer: Mostrar mais recentes | |
show_older: Mostrar mais antigos | |
show_thread: Mostrar conversa | |
- sign_in_to_participate: Inicie a sessão para participar na conversa | |
- title: '%{name}: "%{quote}"' | |
+ sign_in_to_participate: Entre para participar dessa conversa | |
visibilities: | |
- direct: Direto | |
- private: Mostrar apenas para seguidores | |
- private_long: Mostrar apenas para seguidores | |
+ private: Privado | |
+ private_long: Posta apenas para seguidores | |
public: Público | |
- public_long: Todos podem ver | |
- unlisted: Público, mas não mostre no timeline público | |
- unlisted_long: Todos podem ver, porém não será postado nas timelines públicas | |
+ public_long: Posta em linhas públicas | |
+ unlisted: Não-listado | |
+ unlisted_long: Não posta em linhas públicas | |
stream_entries: | |
pinned: Toot fixado | |
- reblogged: partilhado | |
+ reblogged: deu boost | |
sensitive_content: Conteúdo sensível | |
tags: | |
- does_not_match_previous_name: não coincide com o nome anterior | |
+ does_not_match_previous_name: não corresponde ao nome anterior | |
terms: | |
body_html: | | |
- <h2>Política de privacidade</h2> | |
- <h3 id="collect">Que informação nós recolhemos?</h3> | |
+ <h2>Política de Privacidade</h2> | |
+ <h3 id="collect">Quais dados nós coletamos?</h3> | |
<ul> | |
- <li><em>Informação básica da conta</em>: Se te registares neste servidor, pode-te ser pedido que indiques um nome de utilizador, um endereço de email e uma palavra-passe. Também podes introduzir informação adicional de perfil, tal como um nome a mostrar e dados biográficos, que carregues uma fotografia para o teu perfil e para o cabeçalho. O nome de utilizador, o nome a mostrar, a biografia, a imagem de perfil e a imagem de cabeçalho são sempre listados publicamente.</li> | |
- <li><em>Publicações, seguimento e outra informação pública</em>: A lista de pessoas que tu segues é pública, o mesmo é verdade para os teus seguidores. Quando tu publicas uma mensagem, a data e a hora são guardados, tal como a aplicação a partir da qual a mensagem foi enviada. As mensagens podem conter anexos média, tais como fotografias ou vídeos. Publicações públicas e não listadas são acessíveis publicamente. Quando expões uma publicação no teu perfil, isso é também informação disponível publicamente. As tuas publicações são enviadas aos teus seguidores. Em alguns casos isso significa que elas são enviadas para servidores diferentes onde são guardadas cópias. Quando tu apagas publicações, isso também é enviado para os teus seguidores. A ação de republicar ou favoritar outra publicação é sempre pública.</li> | |
- <li><em>Publicações diretas e exclusivas para seguidores</em>: Todas as publicações são guardadas e processadas no servidor. Publicações exclusivas para seguidores são enviadas para os teus seguidores e para utilizadores que são nelas mencionados. As publicações diretas são enviadas apenas para os utilizadores nelas mencionados. Em alguns casos isso significa que elas são enviadas para diferentes servidores onde são guardadas cópias das mesmas. Nós fazemos um grande esforço para limitar o acesso a estas publicações aos utilizadores autorizados, mas outros servidores podem falhar neste objetivo. Por isso, tu deves rever os servidores a que os teus seguidores pertencem. Tu podes ativar uma opção para aprovar e rejeitar manualmente novos seguidores nas configurações. <em>Por favor, tem em mente que os gestores do servidor e qualquer servidor que receba a publicação pode lê-la</em> e que os destinatários podem fazer uma captura de tela, copiar ou partilhar a publicação. <em>Não partilhes qualquer informação perigosa no Mastodon.</em></li> | |
- <li><em>IPs e outros metadados</em>: Quando inicias sessão, nós guardamos o endereço de IP a partir do qual iniciaste a sessão, tal como o nome do teu navegador. Todas as sessões estão disponíveis para verificação e revogação nas configurações. O último endereço de IP usado é guardado até 12 meses. Nós também podemos guardar registos de servidor, os quais incluem o endereço de IP de cada pedido dirigido ao nosso servidor.</li> | |
+ <li><em>Dados básicos de conta</em>: Se você criar conta nesta instância, um nome de usuário, um e-mail e uma senha serão exigidos. Você também pode adicionar dados extras como nome de exibição, biografia, imagem de perfil e capa. Com exceção do e-mail e da senha, os dados citados sempre são públicos.</li> | |
+ <li><em>Toots, seguindo e outros dados públicos</em>: A lista de pessoas que você segue e a sua lista de seguidores são públicas. Ao enviar um toot, a data, a hora e o aplicativo usado são armazenados. Toots podem conter mídias anexadas, como áudios, imagens e vídeos. Toots públicos e não-listados são visíveis publicamente. Os toots fixados no seu perfil são públicos. Seus toots são enviados aos seus seguidores, em alguns casos isso significa que os toots são enviados para instâncias diferentes e cópias são armazenadas lá. Quando você exclui toots, essa informação também é enviada aos seus seguidores. O ato de dar boost ou favoritar outro toot é sempre público.</li></li> | |
+ <li><em>Mensagens Diretas e toots privados</em>: Todos os toots são armazenados e processados na instância. Toots privados são enviados aos seus seguidores e aos usuários mencionados neles; Mensagens Diretas (ou toots diretos) são enviadas somente aos usuários mencionados nelas. Em alguns casos isso significa que os toots são enviados para instâncias diferentes e cópias são armazenadas lá. Nós trabalhamos constantemente para limitar o acesso a estes toots somente às pessoas autorizadas, porém outras instâncias podem não fazer o mesmo. Portanto, é importante analisar as instâncias dos seus seguidores. Você pode trancar a conta para aprovar ou vetar novos seguidores manualmente nas configurações. <em>Por favor, tenha em mente que os operadores da instância em que se está e das instâncias receptoras podem ver tais toots</em>, e que os destinatários podem fazer capturas de tela, copiar ou usar outra maneira para compartilhar os toots. <em>Não compartilhe informação confidencial pelo Mastodon.</em></li> | |
+ <li><em>IPs e outros metadados</em>: Ao entrar na sua conta, nós armazenamos o seu endereço de IP e o nome do navegador usado. Todas as sessões abertas estão disponíveis para serem analisadas e revogadas nas configurações. O último endereço de IP usado é armazenado por até 12 meses. Nós também podemos reter históricos da instância que incluem o endereço de IP de todas as conexões à nossa instância.</li> | |
</ul> | |
<hr class="spacer" /> | |
- <h3 id="use">Para que usamos a tua informação?</h3> | |
+ <h3 id="use">Como usamos os seus dados?</h3> | |
- <p>Qualquer informação que recolhemos sobre ti pode ser usada dos seguintes modos:</p> | |
+ <p>Todo dado que coletamos pode ser usado das seguintes maneiras:</p> | |
<ul> | |
- <li>Para providenciar a funcionalidade central do Mastodon. Tu só podes interagir com o conteúdo de outras pessoas e publicar o teu próprio conteúdo depois de teres iniciado sessão. Por exemplo, tu podes seguir outras pessoas para veres as suas publicações na tua cronologia inicial personalizada. </li> | |
- <li>Para ajudar na moderação da comunidade para, por exemplo, comparar o teu endereço IP com outros conhecidos, para determinar a fuga ao banimento ou outras violações.</li> | |
- <li>O endereço de e-mail que tu forneces pode ser usado para te enviar informações e/ou notificações sobre outras pessoas que estão a interagir com o teu conteúdo ou a enviar-te mensagens, para responderes a inquéritos e/ou outros pedidos ou questões.</li> | |
+ <li>Para prover a funcionalidade básica do Mastodon. Você só pode interagir com o conteúdo de outras pessoas e postar seu próprio conteúdo usando uma conta. Por exemplo, você pode seguir outras pessoas para ver seus toots na sua própria linha do tempo personalizada.</li> | |
+ <li>Para auxiliar na moderação da comunidade, por exemplo ao comparar o seu endereço de IP com outros endereços de IP conhecidos para determinar evasão de banimento e outras violações.</li> | |
+ <li>O endereço de e-mail que você fornecer pode ser usado para te enviar informações, notificações sobre outras pessoas interagindo com o seu conteúdo ou contigo e para responder a questões ou outras solicitações.</li> | |
</ul> | |
<hr class="spacer" /> | |
- <h3 id="protect">Como é que nós protegemos a tua informação?</h3> | |
+ <h3 id="protect">Como protegemos seus dados?</h3> | |
- <p>Nós implementamos uma variedade de medidas de segurança para garantir a segurança da tua informação pessoal quando tu introduzes, submetes ou acedes à mesma. Entre outras coisas, a tua sessão de navegação, tal como o tráfego entre as tuas aplicações e a API, estão seguras por SSL e a tua palavra-passe é codificada usando um forte algoritmo de sentido único. Tu podes activar a autenticação em dois passos para aumentares ainda mais a segurança do acesso à tua conta.</p> | |
+ <p>Nós implementamos diversas medidas de segurança para manter suas informações pessoais seguras quando você as acessa ou as envia. Entre outras coisas, sua sessão do navegador, bem como o tráfego entre os aplicativos e a API são asseguradas usando SSL e a sua senha é guardada usando um algoritmo forte de criptografia de mão única. Você pode ativar autenticação em dois fatores como forma de aumentar a segurança no acesso à sua conta.</p> | |
<hr class="spacer" /> | |
<h3 id="data-retention">Qual é a nossa política de retenção de dados?</h3> | |
- <p>Nós envidaremos todos os esforços no sentido de:</p> | |
+ <p>Nós trabalhamos constantemente para:</p> | |
<ul> | |
- <li>Guardar registos do servidor contendo o endereço de IP de todos os pedidos feitos a este servidor, considerando que estes registos não serão guardados por mais de 90 dias.</li> | |
- <li>Guardar os endereços de IP associados aos utilizadores registados durante um período que não ultrapassará os 12 meses.</li> | |
+ <li>Reter o histórico da instância contendo os endereços de IP de todas as conexões a essa instância. O histórico é mantido por não mais que 90 dias.</li> | |
+ <li>Reter os endereços de IP associados à usuários da instância por não mais que 12 meses.</li> | |
</ul> | |
- <p>Tu podes pedir e descarregar um ficheiro com o teu conteúdo, incluindo as tuas publicações, os ficheiros multimédia, a imagem de perfil e a imagem de cabeçalho.</p> | |
+ <p>Você pode solicitar e baixar um arquivo de todo o conteúdo da sua conta, incluindo seus toots, suas mídias, imagem de perfil e capa.</p> | |
- <p>Tu podes apagar a tua conta de modo definitivo e a qualquer momento.</p> | |
+ <p>YVocê pode excluir a sua conta irreversivelmente a qualquer momento.</p> | |
<hr class="spacer"/> | |
- <h3 id="cookies">Usamos cookies?</h3> | |
+ <h3 id="cookies">Nós usamos cookies?</h3> | |
- <p>Sim. Cookies são pequenos ficheiros que um site ou o seu fornecedor de serviço transfere para o disco rígido do teu computador através do teu navegador (se tu permitires). Estes cookies permitem ao site reconhecer o teu navegador e, se tu tiveres uma conta registada, associá-lo a ela.</p> | |
+ <p>Sim. Cookies são pequenos arquivos que um site ou serviço baixa através do seu navegador (se você permitir). Esses cookies permitem ao site conhecer seu navegador e, se você tiver uma conta, associá-lo a ela.</p> | |
- <p>Nós usamos os cookies para compreender e guardar as tuas preferências para as visitas futuras.</p> | |
+ <p>Nós usamos cookies para salvar suas preferências para futuras visitas.</p> | |
<hr class="spacer" /> | |
- <h3 id="disclose">Nós divulgamos alguma informação para entidades externas?</h3> | |
+ <h3 id="disclose">Nós compartilhamos algum dado para terceiros?</h3> | |
- <p>Nós não vendemos, trocamos ou transferimos de qualquer modo a tua informação pessoal que seja identificável para qualquer entidade externa. Isto não inclui terceiros de confiança que nos ajudam a manter o nosso site, conduzir o nosso negócio ou prestar-te este serviço, desde que esses terceiros concordem em manter essa informação confidencial. Poderemos também revelar a tua informação quando nós acreditamos que isso é apropriado para cumprir a lei, forçar a aplicação dos nossos termos de serviço ou proteger os direitos, propriedade e segurança, nossos e de outrem.</p> | |
+ <p>Nós não vendemos, trocamos ou compartilhamos de qualquer maneira dados que possam te identificar à terceiros. Isso não inclui terceiros confiáveis que nos auxiliam a operar o nosso site, realizar nosso serviço ou prestar assistência, contanto que esses terceiros se comprometam a manter essa informação confidencial. Nós podemos também divulgar informação quando acreditamos que é apropriado para obedecer a lei, para fazer cumprir nossas políticas ou proteger os nossos direitos, propriedade ou segurança, ou de outrém.</p> | |
- <p>O teu conteúdo público pode ser descarregado por outros servidores na rede. As tuas publicações públicas e exclusivas para os teus seguidores são enviadas para os servidores onde os teus seguidores residem e as mensagens directas são entregues aos servidores dos seus destinatários, no caso desses seguidores ou destinatários residirem num servidor diferente deste.</p> | |
+ <p>Seu conteúdo público pode ser acessado por outras instâncias na rede. Seus toots públicos e privados são enviados às instâncias dos seus seguidores e seus toots diretos são enviados às instâncias dos usuários mencionados neles, contanto que esses seguidores ou usuários estejam em uma instância diferente desta.</p> | |
- <p>Quando tu autorizas uma aplicação a usar a tua conta, dependendo da abrangência das permissões que tu aprovas, ela pode ter acesso à informação pública do teu perfil, à lista de quem segues, aos teus seguidores, às tuas listas, a todas as tuas publicações e aos teus favoritos. As aplicações nunca terão acesso ao teu endereço de e-mail ou à tua palavra-passe.</p> | |
+ <p>Quando você autoriza um aplicativo a usar sua conta, dependendo do nível de autorização das permissões que você aprovar, o aplicativo pode acessar seus dados públicos, a lista de usuários que você segue, seus seguidores, suas listas, suas Mensagens Diretas e seus toots favoritos. Aplicativos nunca podem acessar o seu endereço de e-mail ou senha.</p> | |
<hr class="spacer" /> | |
- <h3 id="children">Utilização do site por crianças</h3> | |
+ <h3 id="children">Uso desse site por crianças</h3> | |
- <p>Se este servidor estiver na EU ou na EEA: O nosso site, produtos e serviços são todos dirigidos a pessoas que têm, pelo menos, 16 de idade. Se tu tens menos de 16 anos, devido aos requisitos da GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) não uses este site.</p> | |
+ <p>Se a instância está na UE ou no EEE: Nosso site, produto e serviço são direcionados às pessoas que tem ao menos 16 anos de idade. Se você tem menos de 16 anos, de acordo com os requisitos da RGPD (<a href="https://pt.wikipedia.org/wiki/Regulamento_Geral_sobre_a_Prote%C3%A7%C3%A3o_de_Dados">Regulamento Geral sobre Proteção de Dados</a>) não use este site.</p> | |
- <p>Se este servidor estiver nos EUA: O nosso site, produtos e serviços são todos dirigidos a pessoas que têm, pelo menos, 13 anos de idade. Se tu tens menos de 13 anos de idade, devido aos requisitos da COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) não uses este site.</p> | |
+ <p>Se esta instância está nos EUA: Nosso site, produto e serviço são direcionados às pessoas que tem ao menos 13 anos de idade. Se você tem menos de 13 anos, de acordo com os requerimentos da COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) não use este site</p> | |
- <p>Os requisitos legais poderão ser diferentes se este servidor estiver noutra jurisdição.</p> | |
+ <p>Os requisitos da lei podem ser diferentes em outra jurisdição.</p> | |
<hr class="spacer" /> | |
- <h3 id="changes">Alterações à nossa Política de Privacidade</h3> | |
+ <h3 id="changes">Alterações na nossa Política de Privacidade</h3> | |
- <p>Se nós decidirmos alterar a nossa política de privacidade, nós iremos publicar essas alterações nesta página.</p> | |
+ <p>Se decidirmos mudar nossa Política de Privacidade, iremos disponibilizar as alterações nesta página.</p> | |
- <p>Este documento é CC-BY-SA. Ele foi actualizado pela última vez em 7 de Março 2018.</p> | |
+ <p>CC-BY-SA. Atualizado pela última vez em 7 de março de 2018.</p> | |
- <p>Originalmente adaptado de <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p> | |
- title: "%{instance} Termos de Serviço e Política de Privacidade" | |
+ <p>Adaptado originalmente de <a href="https://github.com/discourse/discourse">Política de Privacidade Discourse</a>.</p> | |
+ title: Termos de Serviço e Política de Privacidade de %{instance} | |
themes: | |
- contrast: Mastodon (Elevado contraste) | |
- default: Mastodon | |
- mastodon-light: Mastodon (Claro) | |
+ contrast: Mastodon (Alto contraste) | |
+ default: Mastodon (Noturno) | |
+ mastodon-light: Mastodon (Diurno) | |
time: | |
formats: | |
default: "%H:%M em %d de %b de %Y" | |
@@ -1383,99 +1401,109 @@ | |
two_factor_authentication: | |
add: Adicionar | |
disable: Desativar | |
- disabled_success: Autenticação em duas etapas desativada com sucesso | |
+ disabled_success: Autenticação de dois fatores desativada com sucesso | |
edit: Editar | |
- enabled: A autenticação em dois passos está ativada | |
- enabled_success: Autenticação em dois passos ativada com sucesso | |
- generate_recovery_codes: Gerar códigos para recuperar conta | |
- lost_recovery_codes: Códigos de recuperação permite que você recupere o acesso a sua conta se você perder seu telefone. Se você perder os códigos de recuperação, você pode regera-los aqui. Seus códigos antigos serão invalidados. | |
- methods: Métodos de duas etapas | |
- otp: Aplicação de autenticação | |
- recovery_codes: Cópia de segurança dos códigos de recuperação | |
- recovery_codes_regenerated: Códigos de recuperação foram gerados com sucesso | |
- recovery_instructions_html: Se tu alguma vez perderes o teu smartphone, to poderás usar um dos códigos de recuperação para voltares a ter acesso à tua conta. <strong>Mantém os códigos de recuperação seguros</strong>. Por exemplo, tu podes imprimi-los e guardá-los junto a outros documentos importantes. | |
+ enabled: Autenticação de dois fatores ativada | |
+ enabled_success: Autenticação de dois fatores ativada com sucesso | |
+ generate_recovery_codes: Gerar códigos de recuperação | |
+ lost_recovery_codes: Códigos de recuperação permitem que você recupere o acesso à sua conta caso perca o seu celular. Se você perdeu seus códigos de recuperação, você pode gerá-los novamente aqui. Seus códigos de recuperação anteriores serão invalidados. | |
+ methods: Métodos de dois fatores | |
+ otp: Aplicativo autenticador | |
+ recovery_codes: Códigos de recuperação de reserva | |
+ recovery_codes_regenerated: Códigos de recuperação regerados com sucesso | |
+ recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. <strong>Mantenha os códigos de recuperação em um local seguro</strong>. Por exemplo, você pode imprimi-los e guardá-los junto com outros documentos importantes. | |
webauthn: Chaves de segurança | |
user_mailer: | |
backup_ready: | |
- explanation: Pediste uma cópia completa da tua conta Mastodon. Ela já está pronta para descarregares! | |
- subject: O teu arquivo está pronto para descarregar | |
- title: Arquivo de ficheiros | |
+ explanation: Você pediu um backup completo da sua conta no Mastodon. E agora está pronto para ser baixado! | |
+ subject: Seu arquivo está pronto para download | |
+ title: Baixar arquivo | |
sign_in_token: | |
details: 'Aqui estão os detalhes da tentativa:' | |
- explanation: 'Detectamos uma tentativa de entrar na sua conta a partir de um endereço IP não reconhecido. Se é você, por favor, insira o código de segurança abaixo na página de acesso:' | |
- further_actions: 'Se não foi você, por favor altere sua senha e ative a autenticação de dois fatores na sua conta. Pode fazê-lo aqui:' | |
- subject: Por favor, confirme a tentativa de acesso | |
+ explanation: 'Detectamos uma tentativa de acessar sua conta a partir de um endereço IP não reconhecido. Se for você, insira o código de segurança abaixo na página de desafio:' | |
+ further_actions: 'Se não foi você, por favor mude sua senha e ative a autenticação de dois fatores em sua conta. Você pode fazê-lo aqui:' | |
+ subject: Confirme a tentativa de login | |
title: Tentativa de acesso | |
+ status_removed: | |
+ explanation: We use artificial intelligence (AI) to assist our hardworking moderators, and some Truths are flagged for deletion or marked “sensitive” by AI. While the AI we use is very good, it is not error-proof. Assisted by technology, our moderators use their best judgment to ensure compliance with our Terms of Service. Please give our team time to review your Truth to determine whether it violates our Terms of Service. After a thorough review, we will reinstate the Truth or uphold its removal. | |
+ title: Your Truth was flagged for review | |
+ subject: Sua Truth foi sinalizada para revisão | |
warning: | |
explanation: | |
- disable: Enquanto a tua conta está congelada, os seus dados permanecem intactos, mas tu não podes executar quaisquer acções até que ela seja desbloqueada. | |
- sensitive: Os seus ficheiros de media carregados e os media ligados serão tratados como sensíveis. | |
- silence: Enquanto a sua conta estiver limitada, só pessoas que já estiver a seguir irão ver as suas publicações nesta instância e poderá ser excluído de várias listagens públicas. No entanto, outros ainda o poderão seguir de forma manual. | |
- suspend: A sua conta foi suspensa e todas as suas publicações e os seus ficheiros de media foram irreversivelmente removidos desta instância e das instâncias onde tinhas seguidores. | |
- verify: Você é um membro certificado da comunidade | |
- unverify: Você não é mais um membro certificado da comunidade. | |
- get_in_touch: Pode responder a este e-mail para entrar em contacto com a equipa de %{instance}. | |
- review_server_policies: Reveja a política da instância | |
+ ban_html: Sua conta foi banida por violar nossos Termos de Serviço. Banimentos sem prazo definido são um tipo de sanção raro e muito grave, geralmente aplicados somente às mais sérias violações de nossos Termos de Serviço. Se você acredita que nossa decisão de banir sua conta foi injusta, imoral ou simplesmente errada, recomendamos que entre com um recurso. Envie um e-mail para %{email}. Na linha de assunto, escreva "@Appeal" junto com seu nome de usuário. Aguarde 48 horas para que nossa equipe avalie seu recurso contra o banimento. Após uma avaliação cuidadosa, decidiremos reverter ou manter o banimento. | |
+ disable_html: Você não pode mais entrar na sua conta nem usá-la de qualquer forma que seja, mas seu perfil e seus outros dados permanecerão intactos. | |
+ sensitive_html: Os arquivos de mídia que você carregou e os links de mídia que publicou serão tratados como confidenciais. | |
+ silence_html: Você continua podendo usar sua conta, mas apenas as pessoas que já seguem você verão suas postagens nesse servidor. Além disso, você poderá ser excluído de várias listagens públicas. No entanto, as pessoas continuam podendo seguir você manualmente. | |
+ suspend_html: Sua conta foi suspensa e ficará inacessível por %{suspension_duration}. | |
+ suspend_indefinite_html: Sua conta foi suspensa e ficará inacessível. | |
+ verify_html: Você é um membro certificado da comunidade | |
+ unverify_html: Você não é mais um membro certificado da comunidade | |
+ get_in_touch: Você pode responder a este e-mail para entrar em contato com a equipe de %{instance}. | |
+ review_server_policies: Revisar as políticas da instância | |
statuses: 'Especificamente, para:' | |
subject: | |
- disable: A tua conta %{acct} foi congelada | |
+ disable: Sua conta %{acct} foi bloqueada | |
none: Aviso para %{acct} | |
- sensitive: As publicações de media da sua conta %{acct} foram marcadas como sensíveis | |
- silence: A tua conta %{acct} foi limitada | |
- suspend: A tua conta %{acct} foi suspensa | |
- verify: Sua conta %{acct} foi verificada | |
+ sensitive: Sua conta %{acct} de postagem de mídia foi marcada como sensível | |
+ silence: Sua conta %{acct} foi silenciada | |
+ suspend: Sua conta %{acct} foi banida | |
+ verify: Sua conta% {acct} foi verificada | |
unverify: A verificação% {acct} da sua conta foi removida | |
title: | |
- disable: Conta congelada | |
+ disable: Conta bloqueada | |
none: Aviso | |
- sensitive: A sua media foi marcada como sensível | |
- silence: Conta limitada | |
- suspend: Conta suspensa | |
+ sensitive: Sua mídia foi marcada como sensível | |
+ silence: Conta silenciada | |
+ suspend: Conta banida | |
verify: Conta verificada | |
unverify: Verificação de conta removida | |
+ waitlisted: | |
+ title: Sua conta foi criada com sucesso! | |
welcome: | |
- edit_profile_action: Configura o perfil | |
- edit_profile_step: Podes personalizar o teu perfil carregando uma imagem de perfil e de cabeçalho ou alterando o nome a exibir, entre outras opções. Se preferires rever os novos seguidores antes deles te poderem seguir, podes tornar a tua conta privada. | |
- explanation: Aqui estão algumas dicas para começares | |
- final_action: Começa a publicar | |
- final_step: 'Começa a publicar! Mesmo sem seguidores, as tuas mensagens públicas podem ser vistas por outros, por exemplo, na cronologia local e em hashtags. Tu podes querer apresentar-te na hashtag #introductions.' | |
- full_handle: O teu nome completo | |
- full_handle_hint: Isto é o que você diria aos seus amigos para que eles lhe possam enviar mensagens ou seguir a partir de outra instância. | |
+ edit_profile_action: Configurar perfil | |
+ edit_profile_step: Você pode personalizar o seu perfil enviando um avatar, uma capa, alterando seu nome de exibição e etc. Se você preferir aprovar seus novos seguidores antes de eles te seguirem, você pode trancar a sua conta. | |
+ explanation: Aqui estão algumas dicas para você começar | |
+ final_action: Comece a tootar | |
+ final_step: 'Comece a tootar! Mesmo sem seguidores, suas mensagens públicas podem ser vistas pelos outros, por exemplo, na linha local e nas hashtags. Você pode querer fazer uma introdução usando a hashtag #introdução, ou em inglês usando a hashtag #introductions.' | |
+ full_handle: Seu nome de usuário completo | |
+ full_handle_hint: Isso é o que você compartilha com aos seus amigos para que eles possam te mandar toots ou te seguir a partir de outra instância. | |
review_preferences_action: Alterar preferências | |
- review_preferences_step: Certifica-te de configurar as tuas preferências, tais como os e-mails que gostarias de receber ou o nível de privacidade que desejas que as tuas publicações tenham por defeito. Se não sofres de enjoo, podes ativar a opção de auto-iniciar GIFs. | |
- subject: Bem-vindo ao Mastodon | |
- tip_federated_timeline: A cronologia federativa é uma visão global da rede Mastodon. Mas só inclui pessoas que os teus vizinhos subscrevem, por isso não é uma visão completa. | |
- tip_following: Segues o(s) administrador(es) do teu servidor por defeito. Para encontrar mais pessoas interessantes, procura nas cronologias local e federada. | |
- tip_local_timeline: A cronologia local é uma visão global das pessoas em %{instance}. Estes são os seus vizinhos mais próximos! | |
- tip_mobile_webapp: Se o teu navegador móvel te oferecer a possibilidade de adicionar o Mastodon ao teu homescreen, tu podes receber notificações push. Ele age como uma aplicação nativa de vários modos! | |
+ review_preferences_step: Não se esqueça de configurar suas preferências, como quais e-mails você gostaria de receber, que nível de privacidade você gostaria que seus toots tenham por padrão. Se você não sofre de enjoo com movimento, você pode habilitar GIFs animado automaticamente. | |
+ subject: Boas-vindas ao Mastodon | |
+ tip_federated_timeline: A linha global é uma visão contínua da rede do Mastodon. Mas ela só inclui pessoas de instâncias que a sua instância conhece, então não é a rede completa. | |
+ tip_following: Você vai seguir administrador(es) da sua instância por padrão. Para encontrar mais gente interessante, confira as linhas local e global. | |
+ tip_local_timeline: A linha local é uma visão contínua das pessoas em %{instance}. Estes são seus vizinhos! | |
+ tip_mobile_webapp: Se o seu navegador móvel oferecer a opção de adicionar Mastodon à tela inicial, você pode receber notificações push. Será como um aplicativo nativo! | |
tips: Dicas | |
- title: Bem-vindo a bordo, %{name}! | |
+ title: Boas vindas, %{name}! | |
users: | |
- follow_limit_reached: Não podes seguir mais do que %{limit} pessoas | |
- generic_access_help_html: Problemas para aceder à sua conta? Pode entrar em contacto com %{email} para obter ajuda | |
- invalid_otp_token: Código de autenticação inválido | |
- invalid_sign_in_token: Cógido de segurança inválido | |
- otp_lost_help_html: Se tu perdeste acesso a ambos, tu podes entrar em contacto com %{email} | |
- seamless_external_login: Tu estás ligado via um serviço externo. Por isso, as configurações da palavra-passe e do e-mail não estão disponíveis. | |
- signed_in_as: 'Registado como:' | |
- suspicious_sign_in_confirmation: Parece que não iniciou sessão através deste dispositivo antes, e não acede à sua conta há algum tempo. Portanto, enviámos um código de segurança para o seu endereço de e-mail para confirmar que é você. | |
+ follow_limit_reached: Você não pode seguir mais de %{limit} pessoas | |
+ generic_access_help_html: Problemas para acessar sua conta? Você pode entrar em contato com %{email} para obter ajuda | |
+ invalid_otp_token: Código de dois fatores inválido | |
+ invalid_sign_in_token: Código de segurança inválido | |
+ otp_lost_help_html: Se você perder o acesso à ambos, você pode entrar em contato com %{email} | |
+ previously_used_password: Por favor, use uma senha que você não usou anteriormente. | |
+ password_mismatch: A senha e a confirmação da senha não coincidem. | |
+ seamless_external_login: Você entrou usando um serviço externo, então configurações de e-mail e senha não estão disponíveis. | |
+ signed_in_as: 'Entrou como:' | |
+ suspicious_sign_in_confirmation: Parece que você não fez login deste dispositivo antes, e você não fez login por um tempo. Portanto, estamos enviando um código de segurança para o seu endereço de e-mail para confirmar que é você. | |
verification: | |
- explanation_html: 'Pode <strong>comprovar que é o dono dos links nos metadados do seu perfil</strong>. Para isso, o website para o qual o link aponta tem de conter um link para o seu perfil do Mastodon. Este link <strong>tem</strong> de ter um atributo <code>rel="me"</code>. O conteúdo do texto não é relevante. Aqui está um exemplo:' | |
+ explanation_html: 'Você pode <strong>verificar os links nos metadados do seu perfil</strong>. Para isso, o site citado deve conter um link de volta para o seu perfil do Mastodon. O link de volta <strong>deve</strong> conter um atributo <code>rel="me"</code>. O conteúdo ou texto do link não importa. Aqui está um exemplo:' | |
verification: Verificação | |
webauthn_credentials: | |
add: Adicionar nova chave de segurança | |
create: | |
- error: Ocorreu um problema ao adicionar sua chave de segurança. Tente novamente. | |
+ error: Houve um problema ao adicionar sua chave de segurança. Tente novamente. | |
success: A sua chave de segurança foi adicionada com sucesso. | |
- delete: Eliminar | |
- delete_confirmation: Tem a certeza de que pretende eliminar esta chave de segurança? | |
- description_html: Se você ativar a <strong>autenticação com chave de segurança</strong>, para aceder à sua conta será necessário que utilize uma das suas chaves de segurança. | |
+ delete: Excluir | |
+ delete_confirmation: Você tem certeza de que deseja excluir esta chave de segurança? | |
+ description_html: Se você habilitar a <strong>autenticação por chave de segurança</strong>, o login exigirá que você use uma das suas chaves de segurança. | |
destroy: | |
- error: Ocorreu um problema ao remover a sua chave de segurança. Tente novamente. | |
- success: A sua chave de segurança foi eliminada com sucesso. | |
+ error: Houve um problema ao excluir sua chave de segurança. Tente novamente. | |
+ success: Sua chave de segurança foi excluída com sucesso. | |
invalid_credential: Chave de segurança inválida | |
- nickname_hint: Introduza o apelido da sua nova chave de segurança | |
- not_enabled: Ainda não ativou o WebAuthn | |
- not_supported: Este navegador não suporta chaves de segurança | |
- otp_required: Para usar chaves de segurança, por favor ative primeiro a autenticação de duas etapas. | |
- registered_on: Registado em %{date} | |
+ nickname_hint: Digite o apelido da sua nova chave de segurança | |
+ not_enabled: Você ainda não habilitou o WebAuthn | |
+ not_supported: Este navegador não tem suporte a chaves de segurança | |
+ otp_required: Para usar chaves de segurança, por favor habilite primeiro a autenticação de dois fatores. | |
+ registered_on: Registrado em %{date} | |
diff -ru truth-old/opensource/config/navigation.rb truth-new/opensource/config/navigation.rb | |
--- truth-old/opensource/config/navigation.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/navigation.rb 2024-04-01 14:59:13 | |
@@ -26,7 +26,6 @@ | |
n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s| | |
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url | |
s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} | |
- s.item :reports, safe_join([fa_icon('bullhorn fw'), t('admin.trending_truths.title')]), admin_trending_truths_url, highlights_on: %r{/admin/trending_truths} | |
s.item :exports, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url, highlights_on: %r{/settings/export} | |
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts} | |
s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path | |
@@ -45,7 +44,6 @@ | |
s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} | |
s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays} | |
s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } | |
- s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } | |
end | |
n.item :logout, safe_join([fa_icon('sign-out fw'), t('auth.logout')]), destroy_user_session_url, link_html: { 'data-method' => 'delete' } | |
Only in truth-old/opensource/config: pghero.yml | |
diff -ru truth-old/opensource/config/routes.rb truth-new/opensource/config/routes.rb | |
--- truth-old/opensource/config/routes.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/routes.rb 2024-04-12 09:09:08 | |
@@ -5,6 +5,7 @@ | |
Rails.application.routes.draw do | |
root 'home#index' | |
+ resources :apidocs, only: [:index] | |
mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development? | |
@@ -12,7 +13,6 @@ | |
authenticate :user, lambda { |u| u.admin? } do | |
mount Sidekiq::Web, at: 'sidekiq', as: :sidekiq | |
- mount PgHero::Engine, at: 'pghero', as: :pghero | |
end | |
namespace :oauth do | |
@@ -27,17 +27,17 @@ | |
tokens: 'oauth/tokens' | |
end | |
+ get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } | |
+ get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger | |
+ get '.well-known/change-password', to: redirect('/auth/edit') | |
+ post '.well-known/skadnetwork/report-attribution/', to: 'well_known/skadnetwork#create' | |
+ | |
get 'manifest', to: 'manifests#show', defaults: { format: 'json' } | |
get 'intent', to: 'intents#show' | |
get 'custom.css', to: 'custom_css#show', as: :custom_css | |
- | |
- resource :instance_actor, path: 'actor', only: [:show] do | |
- resource :inbox, only: [:create], module: :activitypub | |
- resource :outbox, only: [:show], module: :activitypub | |
- end | |
- | |
get '/unsubscribe', to: 'unsubscribe#unsubscribe' | |
+ get '/link/:id', to: 'link#show', as: :link | |
devise_scope :user do | |
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite | |
@@ -50,16 +50,16 @@ | |
devise_for :users, path: 'auth', controllers: { | |
omniauth_callbacks: 'auth/omniauth_callbacks', | |
- sessions: 'auth/sessions', | |
- registrations: 'auth/registrations', | |
- passwords: 'auth/passwords', | |
- confirmations: 'auth/confirmations', | |
+ sessions: 'auth/sessions', | |
+ registrations: 'auth/registrations', | |
+ passwords: 'auth/passwords', | |
+ confirmations: 'auth/confirmations', | |
} | |
get '/users/:username', to: redirect('/@%{username}'), constraints: lambda { |req| req.format.nil? || req.format.html? } | |
resources :accounts, path: 'users', only: [:show], param: :username do | |
- get :remote_follow, to: 'remote_follow#new' | |
+ get :remote_follow, to: 'remote_follow#new' | |
post :remote_follow, to: 'remote_follow#create' | |
resources :statuses, only: [:show] do | |
@@ -76,15 +76,11 @@ | |
resource :follow, only: [:create], controller: :account_follow | |
resource :unfollow, only: [:create], controller: :account_unfollow | |
- resource :outbox, only: [:show], module: :activitypub | |
- resource :inbox, only: [:create], module: :activitypub | |
resource :claim, only: [:create], module: :activitypub | |
resources :collections, only: [:show], module: :activitypub | |
resource :followers_synchronization, only: [:show], module: :activitypub | |
end | |
- resource :inbox, only: [:create], module: :activitypub | |
- | |
get '/@:username', to: 'accounts#show', as: :short_account | |
get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies | |
get '/@:username/media', to: 'accounts#show', as: :short_account_media | |
@@ -92,7 +88,7 @@ | |
get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status | |
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status | |
- get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction | |
+ get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction | |
post '/interact/:id', to: 'remote_interaction#create' | |
get '/explore', to: 'directories#index', as: :explore | |
@@ -135,7 +131,6 @@ | |
resources :webauthn_credentials, only: [:index, :new, :create, :destroy], | |
path: 'security_keys', | |
controller: 'two_factor_authentication/webauthn_credentials' do | |
- | |
collection do | |
get :options | |
end | |
@@ -170,7 +165,7 @@ | |
get :player | |
end | |
- resources :tags, only: [:show] | |
+ resources :tags, only: [:show] | |
resources :emojis, only: [:show] | |
resources :invites, only: [:index, :create, :destroy] | |
resources :filters, except: [:show, :index] | |
@@ -235,8 +230,6 @@ | |
resources :reported_statuses, only: [:create] | |
end | |
- resources :trending_truths, only: [:index, :update, :destroy] | |
- | |
resources :report_notes, only: [:create, :destroy] | |
resources :accounts, only: [:index, :show, :destroy] do | |
@@ -331,6 +324,8 @@ | |
end | |
namespace :api do | |
+ get '/docs', to: 'docs#index' | |
+ | |
# OEmbed | |
get '/oembed', to: 'oembed#show', as: :oembed | |
@@ -340,7 +335,7 @@ | |
post :change_email, controller: 'user_settings' | |
post :delete_account, controller: 'user_settings' | |
- scope :accounts, defaults: { format: 'json' } do | |
+ scope :accounts, defaults: { format: 'json' } do | |
get :mfa, to: 'accounts#mfa' | |
scope :mfa do | |
scope :setup do | |
@@ -377,6 +372,10 @@ | |
post :unpin, to: 'pins#destroy' | |
end | |
+ collection do | |
+ resources :mutes, controller: 'statuses/mutes', only: :index | |
+ end | |
+ | |
member do | |
get :context | |
get 'context/ancestors', to: 'statuses#ancestors' | |
@@ -386,21 +385,35 @@ | |
namespace :timelines do | |
resource :home, only: :show, controller: :home | |
+ resource :following, only: :show, controller: :home | |
# resource :public, only: :show, controller: :public | |
resources :tag, only: :show | |
resources :list, only: :show | |
+ | |
+ resources :group, only: :show do | |
+ resources :tags, only: :show, path: 'tags', controller: 'group_tag' | |
+ end | |
end | |
namespace :truth do | |
namespace :trending do | |
resources :truths, only: :index | |
+ resources :groups, only: :index | |
+ resources :group_tags, only: :show | |
end | |
namespace :admin do | |
- resources :accounts, only: [:index, :update] | |
+ resources :accounts, only: [:index, :update] do | |
+ post 'mfa/confirm/totp', to: 'accounts#confirm_totp' | |
+ end | |
scope :accounts do | |
+ get :blacklist, to: 'accounts#blacklist' | |
get :count, to: 'accounts#count' | |
+ get :email_domain_blocks, to: 'accounts#email_domain_blocks' | |
end | |
+ resources :email_domain_blocks, only: [:index, :create, :destroy] | |
+ resources :marketing_notifications, only: [:create] | |
+ resources :media_attachments, only: [:destroy] | |
end | |
scope :password_reset do | |
@@ -411,8 +424,96 @@ | |
scope :email do | |
get :confirm, to: 'emails#email_confirm' | |
end | |
+ | |
+ namespace :carousels do | |
+ resources :avatars, only: [:index] do | |
+ post :seen, on: :collection | |
+ end | |
+ resources :groups, only: [:index] do | |
+ post :seen, on: :collection | |
+ end | |
+ resources :suggestions, only: [:index] | |
+ get 'avatars/accounts/:account_id/statuses', to: '/api/v1/accounts/statuses#index' | |
+ | |
+ get 'tv', to: '/api/v1/tv/carousel#index' | |
+ post 'tv/seen', to: '/api/v1/tv/carousel#seen' | |
+ end | |
+ | |
+ get '/ads', to: 'ads#index', as: :ads | |
+ get '/ads/impression', to: 'ads#impression', as: :ads_impression | |
+ | |
+ namespace :ios_device_check do | |
+ resources :challenge, only: [:index] | |
+ resources :rate_limit, only: [:index] | |
+ resources :attest, only: [:create] do | |
+ post :baseline, on: :collection | |
+ post :by_key_id, on: :collection | |
+ end | |
+ resources :assert, only: [:create] do | |
+ post :resolve, on: :collection | |
+ end | |
+ end | |
+ | |
+ namespace :android_device_check do | |
+ resources :challenge, only: [:create] | |
+ end | |
+ | |
+ resources :chats, only: [:index, :create] do | |
+ scope module: :chats do | |
+ resources :messages, only: [:index, :create, :destroy] | |
+ end | |
+ end | |
+ | |
+ namespace :policies do | |
+ get :pending | |
+ patch :accept, path: '/:policy_id/accept', to: 'policies/accept' | |
+ end | |
+ | |
+ resources :oauth_tokens, only: [:index, :destroy] | |
+ | |
+ namespace :suggestions do | |
+ resources :groups, only: [:index, :destroy] | |
+ | |
+ namespace :follows do | |
+ post :create, path: ':account_id', to: 'suggestions/follows' | |
+ end | |
+ | |
+ namespace :statuses do | |
+ post :create, path: ':account_id', to: 'suggestions/statuses' | |
+ end | |
+ end | |
+ | |
+ resources(:videos, only: :show) | |
end | |
+ namespace :pleroma do | |
+ namespace :chats do | |
+ post :by_account_id, path: 'by-account-id/:account_id' | |
+ get :by_account_id, path: 'by-account-id/:account_id', to: '/api/v1/pleroma/chats#get_by_account_id' | |
+ get 'silences', to: 'silences#index' | |
+ get 'sync' | |
+ get 'events', to: 'events#index' | |
+ get 'search', to: 'search#index' | |
+ get 'search/messages', to: 'search#search_messages' | |
+ get 'search/previews', to: 'search#search_previews' | |
+ end | |
+ | |
+ resources :chats, only: [:index, :destroy, :show, :update] do | |
+ post 'read', to: 'chats#mark_read' | |
+ post 'accept', to: 'chats#accept' | |
+ | |
+ scope module: :chats do | |
+ get 'sync', to: 'messages#sync' | |
+ resources :messages, only: [:index, :destroy, :create, :show] do | |
+ resources :reactions, only: [:show, :create, :destroy], param: :emoji | |
+ end | |
+ post 'silences', to: 'silences#create' | |
+ delete 'silences', to: 'silences#destroy' | |
+ get 'silences', to: 'silences#show' | |
+ end | |
+ end | |
+ end | |
+ | |
resources :streaming, only: [:index] | |
resources :custom_emojis, only: [:index] | |
resources :suggestions, only: [:index, :destroy] | |
@@ -452,16 +553,16 @@ | |
end | |
end | |
- resources :media, only: [:create, :update, :show] | |
- resources :blocks, only: [:index] | |
- resources :mutes, only: [:index] | |
- resources :favourites, only: [:index] | |
- resources :bookmarks, only: [:index] | |
- resources :reports, only: [:create] | |
- resources :trends, only: [:index] | |
- resources :filters, only: [:index, :create, :show, :update, :destroy] | |
+ resources :media, only: [:create, :update, :show] | |
+ resources :blocks, only: [:index] | |
+ resources :mutes, only: [:index] | |
+ resources :favourites, only: [:index] | |
+ resources :bookmarks, only: [:index] | |
+ resources :reports, only: [:create] | |
+ resources :trends, only: [:index] | |
+ resources :filters, only: [:index, :create, :show, :update, :destroy] | |
resources :endorsements, only: [:index] | |
- resources :markers, only: [:index, :create] | |
+ resources :markers, only: [:index, :create] | |
namespace :apps do | |
get :verify_credentials, to: 'credentials#show' | |
@@ -534,6 +635,54 @@ | |
resource :accounts, only: [:show, :create, :destroy], controller: 'lists/accounts' | |
end | |
+ namespace :groups do | |
+ resources :relationships, only: [:index] | |
+ resources :tags, only: [:index] | |
+ end | |
+ | |
+ get '/groups/mutes', to: 'groups/mutes#index' | |
+ | |
+ resources :groups, only: [:index, :create, :show, :update, :destroy] do | |
+ post :mute, to: 'groups/mutes#create' | |
+ post :unmute, to: 'groups/mutes#destroy' | |
+ | |
+ resources :memberships, only: [:index], controller: 'groups/memberships' | |
+ | |
+ resources :membership_requests, only: [:index], controller: 'groups/membership_requests' do | |
+ member do | |
+ post :authorize, to: 'groups/membership_requests#accept' | |
+ post :reject | |
+ end | |
+ post 'resolve', on: :collection | |
+ end | |
+ | |
+ resources :statuses, only: [:destroy], controller: 'groups/statuses' do | |
+ resource :pin, only: :create, controller: 'groups/statuses/pins' | |
+ post :unpin, to: 'groups/statuses/pins#destroy' | |
+ end | |
+ | |
+ resource :blocks, only: [:show, :create, :destroy], controller: 'groups/blocks' | |
+ | |
+ resources :tags, only: :update, controller: 'groups/tags' | |
+ | |
+ member do | |
+ post :join | |
+ post :leave | |
+ post :promote | |
+ post :demote | |
+ end | |
+ | |
+ collection do | |
+ get :search | |
+ get :lookup | |
+ get :validate | |
+ end | |
+ end | |
+ | |
+ resources :tags, only: [:show] do | |
+ resources :groups, only: :index, controller: 'tags/groups' | |
+ end | |
+ | |
namespace :featured_tags do | |
get :suggestions, to: 'suggestions#index' | |
end | |
@@ -548,20 +697,35 @@ | |
resource :subscription, only: [:create, :show, :update, :destroy] | |
end | |
+ namespace :tv do | |
+ resources :channels, only: :index | |
+ get 'accounts/:id/status', to: 'accounts#show' | |
+ get 'epg/:name', to: 'programme_guides#show' | |
+ get 'channels/:id/guide', to: 'guide#show' | |
+ put 'channels/:id/remind', to: 'program_reminder#update' | |
+ delete 'channels/:id/remind', to: 'program_reminder#destroy' | |
+ end | |
+ | |
get '/stats', to: 'admin#stats' | |
namespace :admin do | |
resources :statuses, only: [:index, :show] do | |
- post :sensitize | |
post :desensitize | |
- post :undiscard | |
post :discard | |
+ post :privatize | |
+ post :publicize | |
+ post :sensitize | |
+ post :undiscard | |
end | |
resources :accounts, only: [:index, :show, :create, :update, :destroy] do | |
resources :follows, only: [:show], param: :target_account_id, controller: 'accounts/follows' | |
+ resources :statuses, only: [:index], controller: 'accounts/statuses' | |
+ resources :webauthn_credentials, only: [:index], controller: 'accounts/webauthn_credentials' | |
+ | |
collection do | |
post :bulk_approve | |
end | |
+ | |
member do | |
post :enable | |
post :unsensitive | |
@@ -577,6 +741,13 @@ | |
resource :action, only: [:create], controller: 'account_actions' | |
end | |
+ post '/accounts/bulk_action', to: 'bulk_account_actions#create' | |
+ | |
+ resources :groups, only: [:index, :show, :update, :destroy] do | |
+ get :search, on: :collection | |
+ resources :statuses, only: [:index], controller: 'groups/statuses' | |
+ end | |
+ | |
resources :reports, only: [:index, :show] do | |
member do | |
resources :moderation_records, only: [:index] | |
@@ -587,16 +758,119 @@ | |
end | |
end | |
- resources :trending_statuses, only: [:index, :update, :destroy] | |
+ resources :trending_statuses, only: :index do | |
+ member do | |
+ put :include | |
+ put :exclude | |
+ end | |
+ | |
+ collection do | |
+ resources :settings, param: :name, controller: 'trending_statuses/settings', only: [:index, :update] | |
+ end | |
+ | |
+ collection do | |
+ resources :expressions, controller: 'trending_statuses/expressions', only: [:index, :create, :update, :destroy] | |
+ end | |
+ end | |
+ | |
+ resources :trending_tags, only: [:index, :update] | |
+ | |
+ resources :trending_groups, only: :index do | |
+ member do | |
+ put :include | |
+ put :exclude | |
+ end | |
+ | |
+ get :excluded, on: :collection | |
+ end | |
+ | |
+ resources :chat_messages, only: [:show, :destroy] | |
+ | |
+ resources :policies, only: [:index, :create, :destroy] | |
+ | |
+ namespace :truth do | |
+ resources :interactive_ads, only: :create | |
+ namespace :suggestions do | |
+ resources :groups, only: [:index, :show, :create, :destroy] | |
+ end | |
+ | |
+ namespace :android_device_check do | |
+ resources :integrity, only: [:create] | |
+ end | |
+ | |
+ namespace :ios_device_check do | |
+ resources :attest, only: [:create] | |
+ end | |
+ end | |
+ | |
+ namespace :tv do | |
+ resources :sessions, only: :index | |
+ end | |
+ | |
+ resources :tags, only: [:index, :update] | |
+ resources :registrations, only: [:create] | |
+ resources :links, only: [:update] | |
end | |
+ | |
+ resources :feeds, only: [ | |
+ :index, | |
+ # :create, | |
+ :show, | |
+ :update, | |
+ # :destroy | |
+ ] do | |
+ member do | |
+ post 'accounts/:account_id', to: 'feeds#add_account' | |
+ delete 'accounts/:account_id', to: 'feeds#remove_account' | |
+ patch :seen, to: 'feeds#seen' | |
+ end | |
+ end | |
+ | |
+ namespace :recommendations do | |
+ namespace :accounts do | |
+ resources :suppressions, only: [:create, :destroy] | |
+ end | |
+ namespace :groups do | |
+ resources :suppressions, only: [:create, :destroy] | |
+ end | |
+ end | |
+ | |
+ namespace :verify_sms do | |
+ resources :countries, only: :index | |
+ end | |
+ | |
+ namespace :push_notifications do | |
+ post '/:mark_id/mark', to: 'analytics#mark', as: :analytics_mark | |
+ end | |
end | |
namespace :v2 do | |
resources :media, only: [:create] | |
get '/search', to: 'search#index', as: :search | |
resources :suggestions, only: [:index, :destroy] | |
+ | |
+ namespace :pleroma do | |
+ namespace :chats do | |
+ get 'events', to: 'events#index' | |
+ end | |
+ end | |
+ | |
+ resources :statuses, only: [:show] do | |
+ member do | |
+ get 'context/ancestors', to: 'statuses#ancestors', as: 'ancestors' | |
+ get 'context/descendants', to: 'statuses#descendants', as: 'descendants' | |
+ end | |
+ end | |
+ | |
+ resources :feeds, only: [:index] | |
end | |
+ namespace :v4 do | |
+ namespace :truth do | |
+ get '/ads', to: 'ads#index' | |
+ end | |
+ end | |
+ | |
namespace :web do | |
resource :settings, only: [:update] | |
resource :embed, only: [:create] | |
@@ -606,13 +880,35 @@ | |
end | |
end | |
end | |
+ | |
+ namespace :mock do | |
+ get '/feeds', to: 'feeds#index' | |
+ post '/feeds', to: 'feeds#create' | |
+ get '/feeds/:id', to: 'feeds#show' | |
+ patch '/feeds/:id', to: 'feeds#update' | |
+ delete '/feeds/:id', to: 'feeds#destroy' | |
+ put '/feeds/sort', to: 'feeds#sort' | |
+ post '/feeds/:id/accounts/:account_id', to: 'feeds#add_account' | |
+ delete '/feeds/:id/accounts/:account_id', to: 'feeds#remove_account' | |
+ post '/feeds/groups/:group_id/unmute', to: 'feeds#unmute_group' | |
+ post '/feeds/groups/:group_id/mute', to: 'feeds#mute_group' | |
+ end | |
end | |
+ get '/api/v2/pleroma/chats', to: 'api/v1/pleroma/chats#index' | |
+ get '/api/v1/truth/trends/groups', to: 'api/v1/truth/trending/groups#index' | |
+ get '/api/v1/truth/trends/groups/:id/tags', to: 'api/v1/truth/trending/group_tags#show', as: :truth_trends_groups | |
+ | |
+ get '/api/oauth_tokens', to: 'api/v1/truth/oauth_tokens#index' | |
+ delete '/api/oauth_tokens/:id', to: 'api/v1/truth/oauth_tokens#destroy' | |
+ | |
+ get '/api/v1/trends/statuses', to: 'api/v1/truth/trending/truths#index' | |
+ | |
get '/web/(*any)', to: 'home#index', as: :web | |
- get '/about', to: 'about#show' | |
- get '/about/more', to: 'about#more' | |
- get '/terms', to: 'about#terms' | |
+ get '/about', to: 'about#show' | |
+ get '/about/more', to: 'about#more' | |
+ get '/terms', to: 'about#terms' | |
match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false | |
match '*unmatched_route', via: :all, to: 'application#raise_not_found', format: false | |
diff -ru truth-old/opensource/config/settings.yml truth-new/opensource/config/settings.yml | |
--- truth-old/opensource/config/settings.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/settings.yml 2024-04-01 14:59:13 | |
@@ -61,6 +61,7 @@ | |
- administrator | |
- mod | |
- moderator | |
+ - amtrak | |
disallowed_hashtags: # space separated string or list of hashtags without the hash | |
bootstrap_timeline_accounts: '' | |
activity_api_enabled: true | |
diff -ru truth-old/opensource/config/sidekiq.yml truth-new/opensource/config/sidekiq.yml | |
--- truth-old/opensource/config/sidekiq.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/sidekiq.yml 2024-04-12 09:09:08 | |
@@ -7,6 +7,9 @@ | |
- [mailers, 2] | |
- [pull] | |
- [chewy] | |
+ - [removal] | |
+ - [reblog-removal] | |
+ - [shared] | |
:scheduler: | |
:listened_queues_only: true | |
:schedule: | |
@@ -14,18 +17,10 @@ | |
every: '5m' | |
class: Scheduler::ScheduledStatusesScheduler | |
queue: scheduler | |
- trending_tags_scheduler: | |
- every: '5m' | |
- class: Scheduler::TrendingTagsScheduler | |
- queue: scheduler | |
media_cleanup_scheduler: | |
cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' | |
class: Scheduler::MediaCleanupScheduler | |
queue: scheduler | |
- feed_cleanup_scheduler: | |
- cron: '<%= Random.rand(0..59) %> <%= Random.rand(0..2) %> * * *' | |
- class: Scheduler::FeedCleanupScheduler | |
- queue: scheduler | |
follow_recommendations_scheduler: | |
cron: '<%= Random.rand(0..59) %> <%= Random.rand(6..9) %> * * *' | |
class: Scheduler::FollowRecommendationsScheduler | |
@@ -46,10 +41,6 @@ | |
cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *' | |
class: Scheduler::BackupCleanupScheduler | |
queue: scheduler | |
- pghero_scheduler: | |
- cron: '0 0 * * *' | |
- class: Scheduler::PgheroScheduler | |
- queue: scheduler | |
instance_refresh_scheduler: | |
cron: '0 * * * *' | |
class: Scheduler::InstanceRefreshScheduler | |
@@ -57,4 +48,20 @@ | |
users_approval_scheduler: | |
every: '5m' | |
class: Scheduler::UsersApprovalScheduler | |
+ queue: scheduler | |
+ refresh_receipt_scheduler: | |
+ cron: '0 0 * * *' | |
+ class: Scheduler::RefreshReceiptScheduler | |
+ queue: scheduler | |
+ tv_create_program_records_scheduler: | |
+ every: '10m' | |
+ class: Scheduler::TvCreateProgramRecordsScheduler | |
+ queue: scheduler | |
+ tv_refetch_channels_list_scheduler: | |
+ cron: '0 1 * * *' | |
+ class: Scheduler::TvRefetchChannelsListScheduler | |
+ queue: scheduler | |
+ device_verification_cleanup_scheduler: | |
+ cron: '0 2 * * *' | |
+ class: Scheduler::DeviceVerificationCleanupWorker | |
queue: scheduler | |
diff -ru truth-old/opensource/config/webpacker.yml truth-new/opensource/config/webpacker.yml | |
--- truth-old/opensource/config/webpacker.yml 2022-06-08 09:15:38 | |
+++ truth-new/opensource/config/webpacker.yml 2024-04-01 14:59:13 | |
@@ -50,7 +50,7 @@ | |
development: | |
<<: *default | |
- compile: true | |
+ compile: false | |
# Reference: https://webpack.js.org/configuration/dev-server/ | |
dev_server: | |
diff -ru truth-old/opensource/db/schema.rb truth-new/opensource/db/schema.rb | |
--- truth-old/opensource/db/schema.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/db/schema.rb 2024-04-01 14:59:13 | |
@@ -10,7 +10,7 @@ | |
# | |
# It's strongly recommended that you check this file into your version control system. | |
-ActiveRecord::Schema.define(version: 2022_05_25_140644) do | |
+ActiveRecord::Schema.define(version: 2022_06_10_102254) do | |
# These are extensions that must be enabled in order to support this database | |
enable_extension "plpgsql" | |
@@ -181,8 +181,10 @@ | |
t.text "location", default: "", null: false | |
t.text "website", default: "", null: false | |
t.boolean "whale", default: false | |
+ t.integer "interactions_score" | |
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin | |
t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true | |
+ t.index ["interactions_score"], name: "index_accounts_on_interactions_score" | |
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" | |
t.index ["uri"], name: "index_accounts_on_uri" | |
t.index ["url"], name: "index_accounts_on_url" | |
@@ -280,6 +282,32 @@ | |
t.index ["reference_account_id"], name: "index_canonical_email_blocks_on_reference_account_id" | |
end | |
+ create_table "chat_accounts", force: :cascade do |t| | |
+ t.bigint "account_id" | |
+ t.bigint "chat_id" | |
+ t.index ["account_id"], name: "index_chat_accounts_on_account_id" | |
+ t.index ["chat_id"], name: "index_chat_accounts_on_chat_id" | |
+ end | |
+ | |
+ create_table "chat_messages", force: :cascade do |t| | |
+ t.bigint "account_id" | |
+ t.bigint "chat_id" | |
+ t.text "content" | |
+ t.boolean "unread", default: true, null: false | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.index ["account_id"], name: "index_chat_messages_on_account_id" | |
+ t.index ["chat_id"], name: "index_chat_messages_on_chat_id" | |
+ end | |
+ | |
+ create_table "chats", force: :cascade do |t| | |
+ t.integer "unread" | |
+ t.bigint "created_by_account" | |
+ t.datetime "last_message" | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ end | |
+ | |
create_table "conversation_mutes", force: :cascade do |t| | |
t.bigint "conversation_id", null: false | |
t.bigint "account_id", null: false | |
@@ -417,6 +445,14 @@ | |
t.index ["tag_id"], name: "index_featured_tags_on_tag_id" | |
end | |
+ create_table "follow_deletes", force: :cascade do |t| | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.bigint "account_id", null: false | |
+ t.bigint "target_account_id", null: false | |
+ t.index ["account_id"], name: "index_follow_deletes_on_account_id" | |
+ end | |
+ | |
create_table "follow_recommendation_suppressions", force: :cascade do |t| | |
t.bigint "account_id", null: false | |
t.datetime "created_at", precision: 6, null: false | |
@@ -447,6 +483,89 @@ | |
t.index ["target_account_id"], name: "index_follows_on_target_account_id" | |
end | |
+ create_table "group_account_blocks", force: :cascade do |t| | |
+ t.bigint "account_id", null: false | |
+ t.bigint "group_id", null: false | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.index ["account_id", "group_id"], name: "index_group_account_blocks_on_account_id_and_group_id", unique: true | |
+ t.index ["group_id"], name: "index_group_account_blocks_on_group_id" | |
+ end | |
+ | |
+ create_table "group_deletion_requests", force: :cascade do |t| | |
+ t.bigint "group_id" | |
+ t.datetime "created_at", null: false | |
+ t.datetime "updated_at", null: false | |
+ t.index ["group_id"], name: "index_group_deletion_requests_on_group_id" | |
+ end | |
+ | |
+ create_table "group_membership_requests", force: :cascade do |t| | |
+ t.bigint "account_id", null: false | |
+ t.bigint "group_id", null: false | |
+ t.string "uri" | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.index ["account_id", "group_id"], name: "index_group_membership_requests_on_account_id_and_group_id", unique: true | |
+ t.index ["group_id"], name: "index_group_membership_requests_on_group_id" | |
+ t.index ["uri"], name: "index_group_membership_requests_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" | |
+ end | |
+ | |
+ create_table "group_memberships", force: :cascade do |t| | |
+ t.bigint "account_id", null: false | |
+ t.bigint "group_id", null: false | |
+ t.integer "role", default: 0, null: false | |
+ t.string "uri" | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.index ["account_id", "group_id"], name: "index_group_memberships_on_account_id_and_group_id", unique: true | |
+ t.index ["group_id", "role"], name: "index_group_memberships_on_group_id_and_role" | |
+ t.index ["uri"], name: "index_group_memberships_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" | |
+ end | |
+ | |
+ create_table "group_stats", force: :cascade do |t| | |
+ t.bigint "group_id", null: false | |
+ t.bigint "statuses_count", default: 0, null: false | |
+ t.bigint "members_count", default: 0, null: false | |
+ t.datetime "last_status_at" | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.index ["group_id"], name: "index_group_stats_on_group_id", unique: true | |
+ end | |
+ | |
+ create_table "groups", id: :bigint, default: -> { "timestamp_id('groups'::text)" }, force: :cascade do |t| | |
+ t.string "domain" | |
+ t.string "url" | |
+ t.text "note", default: "", null: false | |
+ t.string "display_name", default: "", null: false | |
+ t.boolean "locked", default: false, null: false | |
+ t.boolean "hide_members", default: false, null: false | |
+ t.datetime "suspended_at" | |
+ t.integer "suspension_origin" | |
+ t.boolean "discoverable" | |
+ t.string "avatar_file_name" | |
+ t.string "avatar_content_type" | |
+ t.bigint "avatar_file_size" | |
+ t.datetime "avatar_updated_at" | |
+ t.string "avatar_remote_url", default: "", null: false | |
+ t.string "header_file_name" | |
+ t.string "header_content_type" | |
+ t.bigint "header_file_size" | |
+ t.datetime "header_updated_at" | |
+ t.string "header_remote_url", default: "", null: false | |
+ t.integer "image_storage_schema_version" | |
+ t.string "uri" | |
+ t.string "outbox_url", default: "", null: false | |
+ t.string "inbox_url", default: "", null: false | |
+ t.string "shared_inbox_url", default: "", null: false | |
+ t.string "members_url", default: "", null: false | |
+ t.string "wall_url", default: "", null: false | |
+ t.text "private_key" | |
+ t.text "public_key", default: "", null: false | |
+ t.datetime "created_at", precision: 6, null: false | |
+ t.datetime "updated_at", precision: 6, null: false | |
+ t.index ["uri"], name: "index_groups_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" | |
+ end | |
+ | |
create_table "identities", force: :cascade do |t| | |
t.string "provider", default: "", null: false | |
t.string "uid", default: "", null: false | |
@@ -856,10 +975,12 @@ | |
t.bigint "in_reply_to_account_id" | |
t.bigint "poll_id" | |
t.datetime "deleted_at" | |
- t.bigint "quote_id" | |
t.bigint "deleted_by_id" | |
+ t.bigint "quote_id" | |
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" | |
t.index ["conversation_id"], name: "index_statuses_on_conversation_id" | |
+ t.bigint "group_id" | |
+ t.index ["group_id"], name: "index_statuses_on_group_id", where: "(group_id IS NOT NULL)" | |
t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" | |
t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" | |
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" | |
@@ -975,6 +1096,7 @@ | |
t.boolean "unsubscribe_from_emails", default: false | |
t.integer "ready_to_approve", default: 0 | |
t.boolean "unauth_visibility" | |
+ t.string "attestation_challenge" | |
t.index ["account_id"], name: "index_users_on_account_id" | |
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true | |
t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id" | |
@@ -1069,6 +1191,14 @@ | |
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade | |
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade | |
add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade | |
+ add_foreign_key "group_account_blocks", "accounts", on_delete: :cascade | |
+ add_foreign_key "group_account_blocks", "groups", on_delete: :cascade | |
+ add_foreign_key "group_deletion_requests", "groups", on_delete: :cascade | |
+ add_foreign_key "group_membership_requests", "accounts", on_delete: :cascade | |
+ add_foreign_key "group_membership_requests", "groups", on_delete: :cascade | |
+ add_foreign_key "group_memberships", "accounts", on_delete: :cascade | |
+ add_foreign_key "group_memberships", "groups", on_delete: :cascade | |
+ add_foreign_key "group_stats", "groups", on_delete: :cascade | |
add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade | |
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade | |
add_foreign_key "invites", "users", on_delete: :cascade | |
@@ -1110,6 +1240,7 @@ | |
add_foreign_key "status_stats", "statuses", on_delete: :cascade | |
add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", name: "fk_c7fa917661", on_delete: :nullify | |
add_foreign_key "statuses", "accounts", name: "fk_9bda1543f7", on_delete: :cascade | |
+ add_foreign_key "statuses", "groups", on_delete: :cascade | |
add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify | |
add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade | |
add_foreign_key "statuses_tags", "statuses", on_delete: :cascade | |
@@ -1168,30 +1299,30 @@ | |
add_index "account_summaries", ["account_id"], name: "index_account_summaries_on_account_id", unique: true | |
create_view "follow_recommendations", materialized: true, sql_definition: <<-SQL | |
- SELECT t0.account_id, | |
+ SELECT t0.account_id, | |
sum(t0.rank) AS rank, | |
array_agg(t0.reason) AS reason | |
- FROM ( SELECT account_summaries.account_id, | |
+ FROM ( SELECT account_summaries.account_id, | |
((count(follows.id))::numeric / (1.0 + (count(follows.id))::numeric)) AS rank, | |
'most_followed'::text AS reason | |
- FROM (((follows | |
- JOIN account_summaries ON ((account_summaries.account_id = follows.target_account_id))) | |
- JOIN users ON ((users.account_id = follows.account_id))) | |
- LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = follows.target_account_id))) | |
+ FROM (((follows | |
+ JOIN account_summaries ON ((account_summaries.account_id = follows.target_account_id))) | |
+ JOIN users ON ((users.account_id = follows.account_id))) | |
+ LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = follows.target_account_id))) | |
WHERE ((users.current_sign_in_at >= (now() - 'P30D'::interval)) AND (account_summaries.sensitive = false) AND (follow_recommendation_suppressions.id IS NULL)) | |
GROUP BY account_summaries.account_id | |
- HAVING (count(follows.id) >= 5) | |
+ HAVING (count(follows.id) >= 5) | |
UNION ALL | |
- SELECT account_summaries.account_id, | |
+ SELECT account_summaries.account_id, | |
(sum((status_stats.reblogs_count + status_stats.favourites_count)) / (1.0 + sum((status_stats.reblogs_count + status_stats.favourites_count)))) AS rank, | |
'most_interactions'::text AS reason | |
- FROM (((status_stats | |
- JOIN statuses ON ((statuses.id = status_stats.status_id))) | |
- JOIN account_summaries ON ((account_summaries.account_id = statuses.account_id))) | |
- LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = statuses.account_id))) | |
+ FROM (((status_stats | |
+ JOIN statuses ON ((statuses.id = status_stats.status_id))) | |
+ JOIN account_summaries ON ((account_summaries.account_id = statuses.account_id))) | |
+ LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = statuses.account_id))) | |
WHERE ((statuses.id >= (((date_part('epoch'::text, (now() - 'P30D'::interval)) * (1000)::double precision))::bigint << 16)) AND (account_summaries.sensitive = false) AND (follow_recommendation_suppressions.id IS NULL)) | |
GROUP BY account_summaries.account_id | |
- HAVING (sum((status_stats.reblogs_count + status_stats.favourites_count)) >= (5)::numeric)) t0 | |
+ HAVING (sum((status_stats.reblogs_count + status_stats.favourites_count)) >= (5)::numeric)) t0 | |
GROUP BY t0.account_id | |
ORDER BY (sum(t0.rank)) DESC; | |
SQL | |
Only in truth-new/opensource/db: seeds | |
diff -ru truth-old/opensource/db/seeds.rb truth-new/opensource/db/seeds.rb | |
--- truth-old/opensource/db/seeds.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/db/seeds.rb 2024-04-01 14:59:13 | |
@@ -1,4 +1,4 @@ | |
-Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push') | |
+app = Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push') | |
# TODO: Replace these values with configuration values, but... its public information and doesn't really matter. | |
Doorkeeper::Application.where(uid: 'ZFgr4TjEEojuvyXsJWPh3PZgsN5NzmkwPlPTozNmT7U').first_or_create(name: 'Soapbox FE', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push', secret: 'OjvJska0Sa2Il-xxly4VB9YF8T2I0dwXu3mfkabpY_o') | |
@@ -6,9 +6,55 @@ | |
account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain) | |
account.save! | |
+Dir[File.join(Rails.root, 'db', 'seeds/*', '*.sql')].sort.each do |seed| | |
+ ActiveRecord::Base.connection.execute File.read(File.expand_path(seed)) | |
+end | |
-if Rails.env.development? | |
- admin = Account.where(username: 'admin').first_or_initialize(username: 'admin') | |
+if Rails.env.development? || ENV['REVIEW_APP'] == 'yes' | |
+ admin = Account.where(username: 'admin').first_or_initialize(username: 'admin') | |
admin.save(validate: false) | |
- User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true).save! | |
+ user = User.where(email: "[email protected]").first_or_initialize(email: "[email protected]", password: 'truthsocial', password_confirmation: 'truthsocial', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true) | |
+ user.save! | |
+ OauthAccessToken.create!(application: app, | |
+ resource_owner_id: user.id, | |
+ scopes: Doorkeeper.configuration.optional_scopes, | |
+ expires_in: Doorkeeper.configuration.access_token_expires_in, | |
+ use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?) | |
end | |
+ | |
+group_statuses_feature_id = Configuration::Feature.find_or_create_by(name: 'group_statuses').feature_id | |
+Configuration::FeatureSetting.find_or_create_by(feature_id: group_statuses_feature_id, name: 'rate_limit_duplicate_group_status_enabled', value_type: 'boolean', value: 'false') | |
+ | |
+# Add seed data for review apps | |
+if ENV['REVIEW_APP'] == 'yes' | |
+ password = ENV['REVIEW_APP_PASSWORD'] || 'truthsocial' | |
+ | |
+ # 10 regular accounts | |
+ 10.times do |i| | |
+ Fabricate.build(:user, email: "user#{i}@truthsocial.com", password: password) do | |
+ account { Fabricate(:account, username: "user#{i}") } | |
+ end.save | |
+ end | |
+ | |
+ # A "whale" account | |
+ Fabricate.build(:user, email: "[email protected]", password: password) do | |
+ account { Fabricate(:account, username: "whale", whale: true) } | |
+ end.save | |
+ | |
+ # Waitlisted user | |
+ Fabricate.build(:user, email: "[email protected]", password: password, approved: false) do | |
+ account { Fabricate(:account, username: "waitlisted") } | |
+ end.save | |
+ | |
+ # Suspended user | |
+ Fabricate.build(:user, email: "[email protected]", password: password) do | |
+ account { Fabricate(:account, username: "banned", suspended: true) } | |
+ end.save | |
+ | |
+ # User with 2FA enabled | |
+ Fabricate.build(:user, email: "[email protected]", password: password, otp_required_for_login: true) do | |
+ account { Fabricate(:account, username: "2fa", suspended: true) } | |
+ end.save | |
+end | |
+ | |
+ | |
Only in truth-new/opensource/db: structure.sql | |
diff -ru truth-old/opensource/lib/active_record/database_tasks_extensions.rb truth-new/opensource/lib/active_record/database_tasks_extensions.rb | |
--- truth-old/opensource/lib/active_record/database_tasks_extensions.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/active_record/database_tasks_extensions.rb 2023-05-05 13:42:02 | |
@@ -1,6 +1,6 @@ | |
# frozen_string_literal: true | |
-require_relative '../mastodon/snowflake' | |
+require_relative '../mastodon/materialized_views' | |
module ActiveRecord | |
module Tasks | |
@@ -9,11 +9,10 @@ | |
define_method(:load_schema) do |db_config, *args| | |
ActiveRecord::Base.establish_connection(db_config) | |
- Mastodon::Snowflake.define_timestamp_id | |
original_load_schema.bind(self).call(db_config, *args) | |
- Mastodon::Snowflake.ensure_id_sequences_exist | |
+ Mastodon::MaterializedViews.initialize | |
end | |
end | |
end | |
diff -ru truth-old/opensource/lib/exceptions.rb truth-new/opensource/lib/exceptions.rb | |
--- truth-old/opensource/lib/exceptions.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/exceptions.rb 2024-04-05 09:07:34 | |
@@ -19,6 +19,18 @@ | |
class RateLimitExceededError < Error; end | |
+ class HostileRateLimitExceededError < Error; end | |
+ | |
+ class UnprocessableEntityError < Error; end | |
+ | |
+ class UnprocessableAssertion < Error; end | |
+ | |
+ class AttestationError < Error; end | |
+ | |
+ class ReceiptVerificationError < Error; end | |
+ | |
+ class RumbleVideoUploadError < Error; end | |
+ | |
class UnexpectedResponseError < Error | |
attr_reader :response | |
@@ -32,4 +44,18 @@ | |
end | |
end | |
end | |
+end | |
+ | |
+module Tv | |
+ class Error < StandardError; end | |
+ | |
+ class LoginError < Error; end | |
+ | |
+ class SignUpError < Error; end | |
+ | |
+ class MissingAccountError < Error; end | |
+ | |
+ class MissingSessionError < Error; end | |
+ | |
+ class GetProfilesError < Error; end | |
end | |
Only in truth-new/opensource/lib: http | |
diff -ru truth-old/opensource/lib/mastodon/accounts_cli.rb truth-new/opensource/lib/mastodon/accounts_cli.rb | |
--- truth-old/opensource/lib/mastodon/accounts_cli.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/mastodon/accounts_cli.rb 2024-04-01 14:59:13 | |
@@ -77,7 +77,7 @@ | |
def create(username) | |
account = Account.new(username: username) | |
password = SecureRandom.hex | |
- user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true) | |
+ user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true, sign_up_city_id: 1, sign_up_country_id: 1) | |
if options[:reattach] | |
account = Account.find_local(username) || Account.new(username: username) | |
@@ -87,7 +87,13 @@ | |
say('Use --force to reattach it anyway and delete the other user') | |
return | |
elsif account.user.present? | |
- DeleteAccountService.new.call(account, reserve_email: false) | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'mastodon_cli_create', | |
+ reserve_email: false, | |
+ skip_activitypub: true, | |
+ ) | |
end | |
end | |
@@ -192,7 +198,13 @@ | |
end | |
say("Deleting user with #{account.statuses_count} statuses, this might take a while...") | |
- DeleteAccountService.new.call(account, reserve_email: false) | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'mastodon_cli_delete', | |
+ reserve_email: false, | |
+ skip_activitypub: true, | |
+ ) | |
say('OK', :green) | |
end | |
@@ -304,7 +316,13 @@ | |
end | |
if [404, 410].include?(code) | |
- DeleteAccountService.new.call(account, reserve_username: false) unless options[:dry_run] | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'mastodon_cli_cull', | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ ) unless options[:dry_run] | |
1 | |
else | |
# Touch account even during dry run to avoid getting the account into the window again | |
diff -ru truth-old/opensource/lib/mastodon/cache_cli.rb truth-new/opensource/lib/mastodon/cache_cli.rb | |
--- truth-old/opensource/lib/mastodon/cache_cli.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/mastodon/cache_cli.rb 2024-04-01 14:59:13 | |
@@ -31,7 +31,7 @@ | |
def recount(type) | |
case type | |
when 'accounts' | |
- processed, = parallelize_with_progress(Account.local.includes(:account_stat)) do |account| | |
+ processed, = parallelize_with_progress(Account.local.includes(:account_follower, :account_following, :account_status)) do |account| | |
account_stat = account.account_stat | |
account_stat.following_count = account.active_relationships.count | |
account_stat.followers_count = account.passive_relationships.count | |
@@ -40,7 +40,7 @@ | |
account_stat.save if account_stat.changed? | |
end | |
when 'statuses' | |
- processed, = parallelize_with_progress(Status.includes(:status_stat)) do |status| | |
+ processed, = parallelize_with_progress(Status.includes(:status_favourite, :status_reply, :status_reblog)) do |status| | |
status_stat = status.status_stat | |
status_stat.replies_count = status.replies.where.not(visibility: :direct).count | |
status_stat.reblogs_count = status.reblogs.count | |
diff -ru truth-old/opensource/lib/mastodon/domains_cli.rb truth-new/opensource/lib/mastodon/domains_cli.rb | |
--- truth-old/opensource/lib/mastodon/domains_cli.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/mastodon/domains_cli.rb 2024-04-01 14:59:13 | |
@@ -40,7 +40,16 @@ | |
end | |
processed, = parallelize_with_progress(scope) do |account| | |
- DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless options[:dry_run] | |
+ unless options[:dry_run] | |
+ DeleteAccountService.new.call( | |
+ account, | |
+ DeleteAccountService::DELETED_BY_SERVICE, | |
+ deletion_type: 'mastodon_cli_purge', | |
+ reserve_username: false, | |
+ skip_activitypub: true, | |
+ skip_side_effects: true, | |
+ ) | |
+ end | |
end | |
DomainBlock.where(domain: domains).destroy_all unless options[:dry_run] | |
Only in truth-new/opensource/lib/mastodon: materialized_views.rb | |
diff -ru truth-old/opensource/lib/mastodon/redis_config.rb truth-new/opensource/lib/mastodon/redis_config.rb | |
--- truth-old/opensource/lib/mastodon/redis_config.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/mastodon/redis_config.rb 2024-04-01 14:59:13 | |
@@ -35,9 +35,9 @@ | |
expires_in: 10.minutes, | |
namespace: cache_namespace, | |
reconnect_attempts: 1, | |
- error_handler: -> (method:, returning:, exception:) { | |
+ error_handler: ->(method:, returning:, exception:) { | |
Rails.logger.error "redis_error | method: #{method}, returning: #{returning}, exception: #{exception}" | |
- } | |
+ }, | |
}.freeze | |
REDIS_SIDEKIQ_PARAMS = { | |
diff -ru truth-old/opensource/lib/mastodon/search_cli.rb truth-new/opensource/lib/mastodon/search_cli.rb | |
--- truth-old/opensource/lib/mastodon/search_cli.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/mastodon/search_cli.rb 2023-05-05 13:42:02 | |
@@ -62,11 +62,7 @@ | |
progress.title = 'Estimating workload ' | |
# Estimate the amount of data that has to be imported first | |
- indices.each do |index| | |
- index.types.each do |type| | |
- progress.total = (progress.total || 0) + type.adapter.default_scope.count | |
- end | |
- end | |
+ progress.total = indices.sum { |index| index.adapter.default_scope.count } | |
# Now import all the actual data. Mind that unlike chewy:sync, we don't | |
# fetch and compare all record IDs from the database and the index to | |
@@ -78,65 +74,66 @@ | |
batch_size = 1_000 | |
slice_size = (batch_size / options[:concurrency]).ceil | |
- index.types.each do |type| | |
- type.adapter.default_scope.reorder(nil).find_in_batches(batch_size: batch_size) do |batch| | |
- futures = [] | |
+ index.adapter.default_scope.reorder(nil).find_in_batches(batch_size: batch_size) do |batch| | |
+ futures = [] | |
- batch.each_slice(slice_size) do |records| | |
- futures << Concurrent::Future.execute(executor: pool) do | |
- if !progress.total.nil? && progress.progress + records.size > progress.total | |
- # The number of items has changed between start and now, | |
- # since there is no good way to predict the final count from | |
- # here, just change the progress bar to an indeterminate one | |
+ batch.each_slice(slice_size) do |records| | |
+ futures << Concurrent::Future.execute(executor: pool) do | |
+ if !progress.total.nil? && progress.progress + records.size > progress.total | |
+ # The number of items has changed between start and now, | |
+ # since there is no good way to predict the final count from | |
+ # here, just change the progress bar to an indeterminate one | |
- progress.total = nil | |
- end | |
+ progress.total = nil | |
+ end | |
- grouped_records = nil | |
- bulk_body = nil | |
- index_count = 0 | |
- delete_count = 0 | |
+ grouped_records = nil | |
+ bulk_body = nil | |
+ index_count = 0 | |
+ delete_count = 0 | |
- ActiveRecord::Base.connection_pool.with_connection do | |
- grouped_records = type.adapter.send(:grouped_objects, records) | |
- bulk_body = Chewy::Type::Import::BulkBuilder.new(type, **grouped_records).bulk_body | |
+ ActiveRecord::Base.connection_pool.with_connection do | |
+ grouped_records = records.to_a.group_by do |record| | |
+ index.adapter.send(:delete_from_index?, record) ? :delete : :to_index | |
end | |
- index_count = grouped_records[:index].size if grouped_records.key?(:index) | |
- delete_count = grouped_records[:delete].size if grouped_records.key?(:delete) | |
+ bulk_body = Chewy::Index::Import::BulkBuilder.new(index, **grouped_records).bulk_body | |
+ end | |
- # The following is an optimization for statuses specifically, since | |
- # we want to de-index statuses that cannot be searched by anybody, | |
- # but can't use Chewy's delete_if logic because it doesn't use | |
- # crutches and our searchable_by logic depends on them | |
- if type == StatusesIndex::Status | |
- bulk_body.map! do |entry| | |
- if entry[:index] && entry.dig(:index, :data, 'searchable_by').blank? | |
- index_count -= 1 | |
- delete_count += 1 | |
+ index_count = grouped_records[:to_index].size if grouped_records.key? | |
+ delete_count = grouped_records[:delete].size if grouped_records.key?(:delete) | |
- { delete: entry[:index].except(:data) } | |
- else | |
- entry | |
- end | |
+ # The following is an optimization for statuses specifically, since | |
+ # we want to de-index statuses that cannot be searched by anybody, | |
+ # but can't use Chewy's delete_if logic because it doesn't use | |
+ # crutches and our searchable_by logic depends on them | |
+ if index == StatusesIndex | |
+ bulk_body.map! do |entry| | |
+ if entry[:index] && entry.dig(:to_index, :data, 'searchable_by').blank? | |
+ index_count -= 1 | |
+ delete_count += 1 | |
+ | |
+ { delete: entry[:index].except(:data) } | |
+ else | |
+ entry | |
end | |
end | |
+ end | |
- Chewy::Type::Import::BulkRequest.new(type).perform(bulk_body) | |
+ Chewy::Index::Import::BulkRequest.new(index).perform(bulk_body) | |
- progress.progress += records.size | |
+ progress.progress += records.size | |
- added.increment(index_count) | |
- removed.increment(delete_count) | |
+ added.increment(index_count) | |
+ removed.increment(delete_count) | |
- sleep 1 | |
- rescue => e | |
- progress.log pastel.red("Error importing #{index}: #{e}") | |
- end | |
+ sleep 1 | |
+ rescue => e | |
+ progress.log pastel.red("Error importing #{index}: #{e}") | |
end | |
- | |
- futures.map(&:value) | |
end | |
+ | |
+ futures.map(&:value) | |
end | |
end | |
diff -ru truth-old/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb truth-new/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb | |
--- truth-old/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb 2024-04-01 14:59:13 | |
@@ -27,6 +27,21 @@ | |
def type_from_file_command | |
@type_from_file_command ||= FileCommandContentTypeDetector.new(@file.path).detect | |
end | |
+ | |
+ def calculated_content_type | |
+ return @calculated_content_type if defined?(@calculated_content_type) | |
+ | |
+ @calculated_content_type = type_from_file_command.chomp | |
+ | |
+ # The `file` command fails to recognize some MP3 files as such | |
+ @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel == 'audio/mpeg' | |
+ @calculated_content_type | |
+ end | |
+ | |
+ def type_from_marcel | |
+ @type_from_marcel ||= Marcel::MimeType.for Pathname.new(@file.path), | |
+ name: @file.path | |
+ end | |
end | |
end | |
diff -ru truth-old/opensource/lib/paperclip/transcoder.rb truth-new/opensource/lib/paperclip/transcoder.rb | |
--- truth-old/opensource/lib/paperclip/transcoder.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/paperclip/transcoder.rb 2024-04-01 14:59:13 | |
@@ -18,36 +18,43 @@ | |
def make | |
metadata = VideoMetadataExtractor.new(@file.path) | |
- unless metadata.valid? | |
- log("Unsupported file #{@file.path}") | |
- return File.open(@file.path) | |
- end | |
+ raise Paperclip::Error, "Error while transcoding #{@file.path}: unsupported file" unless metadata.valid? | |
- update_attachment_type(metadata) | |
+ # This method changes the attachment type to gifv | |
+ # if it is a video with no sound. Don't do that... | |
+ # it is stupid and it breaks unit tests. | |
+ # update_attachment_type(metadata) | |
update_options_from_metadata(metadata) | |
destination = Tempfile.new([@basename, @format ? ".#{@format}" : '']) | |
destination.binmode | |
@output_options = @convert_options[:output]&.dup || {} | |
- @input_options = @convert_options[:input]&.dup || {} | |
+ @input_options = @convert_options[:input]&.dup || {} | |
case @format.to_s | |
when /jpg$/, /jpeg$/, /png$/, /gif$/ | |
@input_options['ss'] = @time | |
- @output_options['f'] = 'image2' | |
+ @output_options['f'] = 'image2' | |
@output_options['vframes'] = 1 | |
- when 'mp4' | |
- @output_options['acodec'] = 'aac' | |
- @output_options['strict'] = 'experimental' | |
+ when /mp4$/, /mov$/ | |
+ if metadata.audio_codec != 'aac' | |
+ @output_options['acodec'] = 'aac' | |
+ end | |
end | |
+ @output_options['strict'] = 'experimental' | |
command_arguments, interpolations = prepare_command(destination) | |
begin | |
command = Terrapin::CommandLine.new('ffmpeg', command_arguments.join(' '), logger: Paperclip.logger) | |
+ timer_start = Time.zone.now | |
command.run(interpolations) | |
+ timer_end = Time.zone.now | |
+ if passthrough_encoding? | |
+ Prometheus::ApplicationExporter.observe_duration(:video_passthrough_encoding, (timer_end - timer_start).in_milliseconds) | |
+ end | |
rescue Terrapin::ExitStatusError => e | |
raise Paperclip::Error, "Error while transcoding #{@basename}: #{e}" | |
rescue Terrapin::CommandNotFoundError | |
@@ -59,9 +66,13 @@ | |
private | |
+ def passthrough_encoding? | |
+ @output_options['c:v'] == 'copy' | |
+ end | |
+ | |
def prepare_command(destination) | |
- command_arguments = ['-nostdin'] | |
- interpolations = {} | |
+ command_arguments = ['-nostdin'] | |
+ interpolations = {} | |
interpolation_keys = 0 | |
@input_options.each_pair do |key, value| | |
@@ -87,11 +98,18 @@ | |
[command_arguments, interpolations] | |
end | |
- def update_options_from_metadata(metadata) | |
- return unless @passthrough_options && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace) | |
+ def update_options_from_metadata(_metadata) | |
+ # always do passthrough encoding if we have the passthrough_options | |
+ # don't filter it by limiting it to quicktime/webm or a particular color palette | |
+ return unless @passthrough_options # && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace) | |
- @format = @passthrough_options[:options][:format] || @format | |
- @time = @passthrough_options[:options][:time] || @time | |
+ # When doing passthrough encoding, the output format should be the same as the current format | |
+ # don't set it it anything else or we will be doing a conversion | |
+ @format = @current_format # @passthrough_options[:options][:format] || @format | |
+ if @format == '.qt' | |
+ @format = 'mp4' | |
+ end | |
+ @time = @passthrough_options[:options][:time] || @time | |
@convert_options = @passthrough_options[:options][:convert_options].dup | |
end | |
Only in truth-old/opensource/lib/proto: account_updated_pb.rb | |
Only in truth-old/opensource/lib/proto: card_created_pb.rb | |
Only in truth-old/opensource/lib/proto: card_joined_pb.rb | |
Only in truth-old/opensource/lib/proto: report_created_pb.rb | |
Only in truth-new/opensource/lib/proto/serializers: account_created_event.rb | |
Only in truth-new/opensource/lib/proto/serializers: account_deleted_event.rb | |
diff -ru truth-old/opensource/lib/proto/serializers/account_updated_event.rb truth-new/opensource/lib/proto/serializers/account_updated_event.rb | |
--- truth-old/opensource/lib/proto/serializers/account_updated_event.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/proto/serializers/account_updated_event.rb 2024-04-01 14:59:13 | |
@@ -1,10 +1,6 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/account_updated_pb.rb" | |
- | |
class AccountUpdatedEvent | |
include RoutingHelper | |
- | |
- EVENT_KEY = "truth_events:v1:account:updated".freeze | |
def initialize(account, fields_changed) | |
@account = account | |
Only in truth-new/opensource/lib/proto/serializers: asset_created_event.rb | |
diff -ru truth-old/opensource/lib/proto/serializers/card_joined_event.rb truth-new/opensource/lib/proto/serializers/card_joined_event.rb | |
--- truth-old/opensource/lib/proto/serializers/card_joined_event.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/proto/serializers/card_joined_event.rb 2024-04-01 14:59:13 | |
@@ -1,9 +1,7 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/card_joined_pb.rb" | |
class CardJoinedEvent | |
include RoutingHelper | |
- EVENT_KEY = "truth_events:v1:card:joined".freeze | |
def initialize(card) | |
@card = card | |
Only in truth-new/opensource/lib/proto/serializers: chat_message_report_created_event.rb | |
Only in truth-new/opensource/lib/proto/serializers: feed_event.rb | |
Only in truth-new/opensource/lib/proto/serializers: group_event.rb | |
Only in truth-new/opensource/lib/proto/serializers: group_report_created_event.rb | |
diff -ru truth-old/opensource/lib/proto/serializers/report_created_event.rb truth-new/opensource/lib/proto/serializers/report_created_event.rb | |
--- truth-old/opensource/lib/proto/serializers/report_created_event.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/proto/serializers/report_created_event.rb 2024-04-01 14:59:13 | |
@@ -1,9 +1,7 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/report_created_pb.rb" | |
class ReportCreatedEvent | |
include RoutingHelper | |
- EVENT_KEY = "truth_events:v1:report:created".freeze | |
def initialize(report) | |
@report = report | |
@@ -22,16 +20,14 @@ | |
ReportCreated.new( | |
id: report.id, | |
account_id: report.account_id, | |
- account_username: report.account_username, | |
target_account_id: report.target_account_id, | |
- target_account_username: report.target_account_username, | |
comment: report.comment[0..254], | |
- status_ids: report.status_ids, | |
rule_ids: report.rule_ids, | |
status_id: report.status_id, | |
image_ids: report.image_ids, | |
video_ids: report.video_ids, | |
- report_set_id: report.report_set_id | |
+ report_set_id: report.report_set_id, | |
+ group_id: report.group_id | |
) | |
end | |
end | |
Only in truth-new/opensource/lib/proto/serializers: session_updated_event.rb | |
diff -ru truth-old/opensource/lib/proto/serializers/status_created_event.rb truth-new/opensource/lib/proto/serializers/status_created_event.rb | |
--- truth-old/opensource/lib/proto/serializers/status_created_event.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/proto/serializers/status_created_event.rb 2024-04-01 14:59:13 | |
@@ -1,12 +1,11 @@ | |
# frozen_string_literal: true | |
-require "./lib/proto/status_created_pb.rb" | |
class StatusCreatedEvent | |
include RoutingHelper | |
- EVENT_KEY = "truth_events:v1:status:created".freeze | |
- def initialize(status) | |
+ def initialize(status, ip_address) | |
@status = status | |
+ @ip_address = ip_address | |
end | |
def serialize | |
@@ -15,14 +14,16 @@ | |
private | |
- attr_reader :status | |
+ attr_reader :status, :ip_address | |
def protobuf | |
StatusCreated.new( | |
id: status.id, | |
account_id: status.account_id, | |
text: status.text, | |
- media_attachments: media_attachment_protobufs | |
+ media_attachments: media_attachment_protobufs, | |
+ ip_address: ip_address, | |
+ group_id: status.group_id | |
) | |
end | |
@@ -37,7 +38,7 @@ | |
end | |
def url(media_attachment) | |
- if media_attachment.not_processed? | |
+ if media_attachment.type != "video" && media_attachment.not_processed? | |
nil | |
elsif media_attachment.needs_redownload? | |
media_proxy_url(media_attachment.id, :original) | |
Only in truth-new/opensource/lib/proto/serializers: status_removed_event.rb | |
Only in truth-old/opensource/lib/proto: status_created_pb.rb | |
Only in truth-new/opensource/lib: rmq_consumers | |
diff -ru truth-old/opensource/lib/sanitize_ext/sanitize_config.rb truth-new/opensource/lib/sanitize_ext/sanitize_config.rb | |
--- truth-old/opensource/lib/sanitize_ext/sanitize_config.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/lib/sanitize_ext/sanitize_config.rb 2024-04-01 14:59:13 | |
@@ -93,26 +93,26 @@ | |
] | |
) | |
- MASTODON_OEMBED ||= freeze_config merge( | |
- RELAXED, | |
- elements: RELAXED[:elements] + %w(audio embed iframe source video), | |
+ MASTODON_OEMBED ||= freeze_config( | |
+ elements: %w(audio embed iframe source video), | |
- attributes: merge( | |
- RELAXED[:attributes], | |
- 'audio' => %w(controls), | |
- 'embed' => %w(height src type width), | |
+ attributes: { | |
+ 'audio' => %w(controls), | |
+ 'embed' => %w(height src type width), | |
'iframe' => %w(allowfullscreen frameborder height scrolling src width), | |
'source' => %w(src type), | |
- 'video' => %w(controls height loop width), | |
- 'div' => [:data] | |
- ), | |
+ 'video' => %w(controls height loop width), | |
+ }, | |
- protocols: merge( | |
- RELAXED[:protocols], | |
- 'embed' => { 'src' => HTTP_PROTOCOLS }, | |
+ protocols: { | |
+ 'embed' => { 'src' => HTTP_PROTOCOLS }, | |
'iframe' => { 'src' => HTTP_PROTOCOLS }, | |
- 'source' => { 'src' => HTTP_PROTOCOLS } | |
- ) | |
+ 'source' => { 'src' => HTTP_PROTOCOLS }, | |
+ }, | |
+ | |
+ add_attributes: { | |
+ 'iframe' => { 'sandbox' => 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms' }, | |
+ } | |
) | |
end | |
end | |
Only in truth-new/opensource/lib: sidekiq_unique_jobs | |
Only in truth-new/opensource/lib/tasks: accounts.rake | |
Only in truth-new/opensource/lib/tasks: app_attest.rake | |
Only in truth-new/opensource/lib/tasks: chats.rake | |
Only in truth-new/opensource/lib/tasks: feeds.rake | |
Only in truth-new/opensource/lib/tasks: groups.rake | |
Only in truth-new/opensource/lib/tasks: images.rake | |
Only in truth-new/opensource/lib/tasks: password_history.rake | |
Only in truth-new/opensource/lib/tasks: trending_statuses.rake | |
Only in truth-new/opensource: node_modules | |
diff -ru truth-old/opensource/package.json truth-new/opensource/package.json | |
--- truth-old/opensource/package.json 2022-06-08 09:15:38 | |
+++ truth-new/opensource/package.json 2024-04-13 09:51:41 | |
@@ -69,6 +69,13 @@ | |
"@gamestdio/websocket": "^0.3.2", | |
"@github/webauthn-json": "^0.5.7", | |
"@rails/ujs": "^6.1.3", | |
+ "@types/cors": "^2.8.12", | |
+ "@types/express": "^4.17.14", | |
+ "@types/js-yaml": "^4.0.5", | |
+ "@types/lodash": "^4.14.190", | |
+ "@types/npmlog": "^4.1.4", | |
+ "@types/throng": "^5.0.4", | |
+ "@types/uuid": "^8.3.4", | |
"array-includes": "^3.1.3", | |
"arrow-key-navigation": "^1.2.0", | |
"autoprefixer": "^9.8.6", | |
@@ -83,6 +90,7 @@ | |
"classnames": "^2.3.1", | |
"color-blend": "^3.0.1", | |
"compression-webpack-plugin": "^6.1.1", | |
+ "cors": "^2.8.5", | |
"cross-env": "^7.0.3", | |
"css-loader": "^5.2.6", | |
"cssnano": "^4.1.11", | |
@@ -107,10 +115,12 @@ | |
"is-nan": "^1.3.2", | |
"js-yaml": "^4.1.0", | |
"lodash": "^4.17.21", | |
+ "lossless-json": "^1.0.5", | |
"mark-loader": "^0.1.6", | |
"marky": "^1.2.2", | |
"mini-css-extract-plugin": "^1.6.0", | |
"mkdirp": "^1.0.4", | |
+ "newrelic": "^9.0.2", | |
"npmlog": "^4.1.2", | |
"object-assign": "^4.1.1", | |
"object-fit-images": "^3.2.3", | |
@@ -123,6 +133,8 @@ | |
"promise.prototype.finally": "^3.1.2", | |
"prop-types": "^15.5.10", | |
"punycode": "^2.1.0", | |
+ "puppeteer": "^21.3.5", | |
+ "puppeteer-cluster": "^0.23.0", | |
"react": "^16.14.0", | |
"react-dom": "^16.14.0", | |
"react-hotkeys": "^1.1.4", | |
@@ -160,16 +172,17 @@ | |
"tesseract.js": "^2.1.1", | |
"throng": "^4.0.0", | |
"tiny-queue": "^0.2.1", | |
+ "ts-node": "^10.9.1", | |
"twitter-text": "3.1.0", | |
- "uuid": "^8.3.1", | |
+ "typescript": "^4.8.4", | |
+ "uuid": "^9.0.0", | |
"webpack": "^4.46.0", | |
"webpack-assets-manifest": "^4.0.6", | |
"webpack-bundle-analyzer": "^4.4.2", | |
"webpack-cli": "^3.3.12", | |
"webpack-merge": "^5.7.3", | |
"wicg-inert": "^3.1.1", | |
- "ws": "^7.4.6", | |
- "lossless-json": "^1.0.5" | |
+ "ws": "^7.4.6" | |
}, | |
"devDependencies": { | |
"@testing-library/jest-dom": "^5.13.0", | |
@@ -182,6 +195,7 @@ | |
"eslint-plugin-promise": "~5.1.0", | |
"eslint-plugin-react": "~7.24.0", | |
"jest": "^26.6.3", | |
+ "nodemon": "^2.0.20", | |
"raf": "^3.4.1", | |
"react-intl-translations-manager": "^5.0.3", | |
"react-test-renderer": "^16.14.0", | |
Only in truth-new/opensource/public: accounts | |
Only in truth-new/opensource/public: ads.txt | |
Binary files truth-old/opensource/public/android-chrome-192x192.png and truth-new/opensource/public/android-chrome-192x192.png differ | |
Binary files truth-old/opensource/public/apple-touch-icon.png and truth-new/opensource/public/apple-touch-icon.png differ | |
Only in truth-new/opensource/public: assets | |
Only in truth-new/opensource/public: docs.css | |
diff -ru truth-old/opensource/public/embed.js truth-new/opensource/public/embed.js | |
--- truth-old/opensource/public/embed.js 2022-06-08 09:15:38 | |
+++ truth-new/opensource/public/embed.js 2024-04-01 14:59:13 | |
@@ -1,6 +1,11 @@ | |
+// @ts-check | |
+ | |
(function() { | |
'use strict'; | |
+ /** | |
+ * @param {() => void} loaded | |
+ */ | |
var ready = function(loaded) { | |
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { | |
loaded(); | |
@@ -10,25 +15,46 @@ | |
}; | |
ready(function() { | |
- var iframes = []; | |
+ /** @type {Map<number, HTMLIFrameElement>} */ | |
+ var iframes = new Map(); | |
window.addEventListener('message', function(e) { | |
var data = e.data || {}; | |
- if (data.type !== 'setHeight' || !iframes[data.id]) { | |
+ if (typeof data !== 'object' || data.type !== 'setHeight' || !iframes.has(data.id)) { | |
return; | |
} | |
- iframes[data.id].height = data.height; | |
+ var iframe = iframes.get(data.id); | |
+ | |
+ if ('source' in e && iframe.contentWindow !== e.source) { | |
+ return; | |
+ } | |
+ | |
+ var hasVideo = iframe.classList.contains('truthsocial-video'); | |
+ | |
+ iframe.height = hasVideo ? data.height + 165 : data.height; | |
}); | |
- [].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function(iframe) { | |
- iframe.scrolling = 'no'; | |
- iframe.style.overflow = 'hidden'; | |
+ [].forEach.call(document.querySelectorAll('iframe.truthsocial-embed'), function(iframe) { | |
+ // select unique id for each iframe | |
+ var id = 0, failCount = 0, idBuffer = new Uint32Array(1); | |
+ while (id === 0 || iframes.has(id)) { | |
+ id = crypto.getRandomValues(idBuffer)[0]; | |
+ failCount++; | |
+ if (failCount > 100) { | |
+ // give up and assign (easily guessable) unique number if getRandomValues is broken or no luck | |
+ id = -(iframes.size + 1); | |
+ break; | |
+ } | |
+ } | |
- iframes.push(iframe); | |
+ iframes.set(id, iframe); | |
- var id = iframes.length - 1; | |
+ iframe.scrolling = 'no'; | |
+ iframe.style.overflow = 'hidden'; | |
+ iframe.style.borderRadius = '8px'; | |
+ iframe.style.border = '1px solid rgb(237 237 239)'; | |
iframe.onload = function() { | |
iframe.contentWindow.postMessage({ | |
Binary files truth-old/opensource/public/favicon.ico and truth-new/opensource/public/favicon.ico differ | |
Binary files truth-old/opensource/public/favicon.png and truth-new/opensource/public/favicon.png differ | |
Only in truth-new/opensource/public: groups | |
Only in truth-new/opensource/public/headers: groups | |
Binary files truth-old/opensource/public/icons/icon-114x114.png and truth-new/opensource/public/icons/icon-114x114.png differ | |
Binary files truth-old/opensource/public/icons/icon-120x120.png and truth-new/opensource/public/icons/icon-120x120.png differ | |
Binary files truth-old/opensource/public/icons/icon-144x144.png and truth-new/opensource/public/icons/icon-144x144.png differ | |
Binary files truth-old/opensource/public/icons/icon-152x152.png and truth-new/opensource/public/icons/icon-152x152.png differ | |
Binary files truth-old/opensource/public/icons/icon-180x180.png and truth-new/opensource/public/icons/icon-180x180.png differ | |
Binary files truth-old/opensource/public/icons/icon-192x192.png and truth-new/opensource/public/icons/icon-192x192.png differ | |
Binary files truth-old/opensource/public/icons/icon-512x512.png and truth-new/opensource/public/icons/icon-512x512.png differ | |
Binary files truth-old/opensource/public/icons/icon-57x57.png and truth-new/opensource/public/icons/icon-57x57.png differ | |
Binary files truth-old/opensource/public/icons/icon-72x72.png and truth-new/opensource/public/icons/icon-72x72.png differ | |
Binary files truth-old/opensource/public/icons/icon-76x76.png and truth-new/opensource/public/icons/icon-76x76.png differ | |
Only in truth-new/opensource/public/icons: icon-maskable-128x128.png | |
Only in truth-new/opensource/public/icons: icon-maskable-192x192.png | |
Only in truth-new/opensource/public/icons: icon-maskable-384x384.png | |
Only in truth-new/opensource/public/icons: icon-maskable-48x48.png | |
Only in truth-new/opensource/public/icons: icon-maskable-512x512.png | |
Only in truth-new/opensource/public/icons: icon-maskable-72x72.png | |
Only in truth-new/opensource/public/icons: icon-maskable-96x96.png | |
Only in truth-new/opensource/public/icons: icon-maskable.png | |
Binary files truth-old/opensource/public/icons/icon.png and truth-new/opensource/public/icons/icon.png differ | |
Only in truth-old/opensource/public: mask-icon.svg | |
Binary files truth-old/opensource/public/mstile-150x150.png and truth-new/opensource/public/mstile-150x150.png differ | |
Only in truth-new/opensource/public: packs | |
Only in truth-new/opensource/public: packs-test | |
diff -ru truth-old/opensource/public/robots.txt truth-new/opensource/public/robots.txt | |
--- truth-old/opensource/public/robots.txt 2022-06-08 09:15:38 | |
+++ truth-new/opensource/public/robots.txt 2024-04-01 14:59:14 | |
@@ -1,2 +0,0 @@ | |
-User-agent: * | |
-Disallow: / | |
\ No newline at end of file | |
Only in truth-new/opensource/public: swagger-initializer.js | |
Only in truth-new/opensource/public: swagger-ui-bundle.js | |
Only in truth-new/opensource/public: swagger-ui-standalone-preset.js | |
Only in truth-new/opensource/public: swagger-ui.js | |
Only in truth-new/opensource/public: tv | |
Only in truth-new/opensource: renovate.json | |
Only in truth-old/opensource: rspec.xml | |
diff -ru truth-old/opensource/spec/controllers/accounts_controller_spec.rb truth-new/opensource/spec/controllers/accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/accounts_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -377,7 +377,7 @@ | |
it 'renders account' do | |
json = body_as_json | |
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) | |
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary) | |
end | |
context 'in authorized fetch mode' do | |
@@ -411,7 +411,7 @@ | |
it 'renders account' do | |
json = body_as_json | |
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) | |
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary) | |
end | |
end | |
@@ -435,7 +435,7 @@ | |
it 'renders account' do | |
json = body_as_json | |
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) | |
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary) | |
end | |
context 'in authorized fetch mode' do | |
@@ -459,7 +459,7 @@ | |
it 'renders account' do | |
json = body_as_json | |
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) | |
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary) | |
end | |
end | |
end | |
Only in truth-old/opensource/spec/controllers/activitypub: inboxes_controller_spec.rb | |
Only in truth-old/opensource/spec/controllers/activitypub: outboxes_controller_spec.rb | |
diff -ru truth-old/opensource/spec/controllers/activitypub/replies_controller_spec.rb truth-new/opensource/spec/controllers/activitypub/replies_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/activitypub/replies_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/activitypub/replies_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -35,7 +35,7 @@ | |
Fabricate(:status, account: remote_account, thread: status, visibility: :public, uri: remote_reply_id) if remote_reply_id | |
end | |
- describe 'GET #index' do | |
+ xdescribe 'GET #index' do | |
context 'with no signature' do | |
subject(:response) { get :index, params: { account_username: status.account.username, status_id: status.id } } | |
subject(:body) { body_as_json } | |
diff -ru truth-old/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb truth-new/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -47,7 +47,7 @@ | |
it 'removes a status' do | |
allow(RemovalWorker).to receive(:perform_async) | |
subject.call | |
- expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true) | |
+ expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: false) | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/admin/statuses_controller_spec.rb truth-new/opensource/spec/controllers/admin/statuses_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/admin/statuses_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/admin/statuses_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -65,7 +65,7 @@ | |
it 'removes a status' do | |
allow(RemovalWorker).to receive(:perform_async) | |
subject.call | |
- expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true) | |
+ expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: false) | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb truth-new/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -43,7 +43,6 @@ | |
user.reload | |
expect(user.otp_enabled?).to eq false | |
- expect(user.webauthn_enabled?).to eq false | |
expect(response).to redirect_to(admin_accounts_path) | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/oembed_controller_spec.rb truth-new/opensource/spec/controllers/api/oembed_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/oembed_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/oembed_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -5,15 +5,29 @@ | |
let(:alice) { Fabricate(:account, username: 'alice') } | |
let(:status) { Fabricate(:status, text: 'Hello world', account: alice) } | |
+ let(:group) { Fabricate(:group, display_name: 'Lorem Ipsum', note: 'Note', statuses_visibility: 'everyone', owner_account: alice ) } | |
+ let!(:membership) { group.memberships.create!(account: alice, role: :owner) } | |
+ let!(:group_status) { Status.create!(account: alice, text: 'test', group: group, visibility: :group) } | |
describe 'GET #show' do | |
before do | |
request.host = Rails.configuration.x.local_domain | |
- get :show, params: { url: short_account_status_url(alice, status) }, format: :json | |
end | |
it 'returns http success' do | |
+ get :show, params: { url: short_account_status_url(alice, status) }, format: :json | |
expect(response).to have_http_status(200) | |
+ end | |
+ | |
+ it 'returns http success for public group statuses' do | |
+ get :show, params: { url: short_account_status_url(alice, group_status) }, format: :json | |
+ expect(response).to have_http_status(200) | |
+ end | |
+ | |
+ it 'returns 404 nout found for private group statuses' do | |
+ group.update(statuses_visibility: 'members_only') | |
+ get :show, params: { url: short_account_status_url(alice, group_status) }, format: :json | |
+ expect(response).to have_http_status(404) | |
end | |
end | |
-end | |
+end | |
\ No newline at end of file | |
diff -ru truth-old/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -14,12 +14,12 @@ | |
describe 'GET #setup_totp' do | |
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
- let(:scopes) { 'write:security' } | |
+ let(:scopes) { 'write:security read' } | |
before do | |
get :setup_totp | |
end | |
- | |
+ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
end | |
@@ -33,7 +33,7 @@ | |
describe 'POST #confirm_totp' do | |
let(:code) { '123456' } | |
let(:new_otp_secret) { User.generate_otp_secret(32) } | |
- let(:scopes) { 'write:security' } | |
+ let(:scopes) { 'write:security write' } | |
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), otp_secret: new_otp_secret) } | |
context 'with correct password and an incorrect code' do | |
@@ -60,7 +60,7 @@ | |
describe 'GET #backup_codes' do | |
let(:code) { '123456' } | |
let(:new_otp_secret) { User.generate_otp_secret(32) } | |
- let(:scopes) { 'write:security' } | |
+ let(:scopes) { 'write:security read' } | |
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), otp_secret: new_otp_secret) } | |
it 'returns http success' do | |
@@ -79,7 +79,7 @@ | |
describe 'DELETE #delete_totp' do | |
let(:code) { '123456' } | |
let(:new_otp_secret) { User.generate_otp_secret(32) } | |
- let(:scopes) { 'write:security' } | |
+ let(:scopes) { 'write:security write' } | |
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), otp_secret: new_otp_secret, otp_required_for_login: true) } | |
it 'returns http success' do | |
diff -ru truth-old/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb truth-new/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -17,7 +17,7 @@ | |
post :change_password, params: { password: '123456789', new_password: new_password, new_password_confirmation: new_password } | |
end | |
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') } | |
let(:new_password) { 'testfoobar' } | |
describe 'POST #change_password' do | |
@@ -29,6 +29,10 @@ | |
user.reload | |
expect(user.valid_password?(new_password)).to be(true) | |
end | |
+ | |
+ it 'keeps the user logged in to their current session' do | |
+ expect(controller.current_user_id).not_to be_nil | |
+ end | |
end | |
end | |
@@ -47,18 +51,39 @@ | |
end | |
end | |
+ context 'with a previously used password' do | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') } | |
+ let(:new_password) { '123456789' } | |
+ | |
+ before do | |
+ request.headers['Accept-Language'] = 'es' | |
+ post :change_password, params: { password: new_password, new_password: new_password, new_password_confirmation: new_password } | |
+ end | |
+ | |
+ describe 'POST #change_password' do | |
+ it 'returns http forbidden' do | |
+ expect(response).to have_http_status(400) | |
+ expect(body_as_json[:error]).to eq I18n.t('users.previously_used_password') | |
+ expect(body_as_json[:error_code]).to eq 'PASSWORD_INVALID' | |
+ expect(body_as_json[:error_message]).to eq I18n.t('users.previously_used_password', locale: :es) | |
+ end | |
+ end | |
+ end | |
+ | |
context 'with a new passwords that don\'t match' do | |
before do | |
post :change_password, params: { password: '123456789', new_password: new_password, new_password_confirmation: 'bad_password' } | |
end | |
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') } | |
let(:new_password) { 'testfoobar' } | |
describe 'POST #change_password' do | |
it 'returns http forbidden' do | |
expect(response).to have_http_status(400) | |
expect(body_as_json[:error]).to eq('Password and password confirmation do not match.') | |
+ expect(body_as_json[:error_code]).to eq('PASSWORD_MISMATCH') | |
+ expect(body_as_json[:error_message]).to eq('Password and password confirmation do not match.') | |
end | |
end | |
end | |
@@ -72,7 +97,7 @@ | |
allow(UserMailer).to receive(:confirmation_instructions).and_return(double('email', deliver_later: nil)) | |
end | |
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') } | |
let(:new_email) { '[email protected]' } | |
context 'good email' do | |
@@ -108,7 +133,7 @@ | |
describe 'POST #delete_account' do | |
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) } | |
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) } | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') } | |
context 'with correct password' do | |
before do | |
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb 2024-04-12 16:28:08 | |
@@ -3,11 +3,17 @@ | |
describe Api::V1::Accounts::CredentialsController do | |
render_views | |
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'user')) } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
context 'with an oauth token' do | |
before do | |
+ _feature_1 = Fabricate(:feature_flag, name: 'feature_1', status: 'enabled') | |
+ _feature_2 = Fabricate(:feature_flag, name: 'feature_2', status: 'disabled') | |
+ feature_3 = Fabricate(:feature_flag, name: 'feature_3', status: 'account_based') | |
+ _feature_4 = Fabricate(:feature_flag, name: 'feature_4', status: 'account_based') | |
+ Fabricate(:account_feature, feature_flag: feature_3, account: user.account) | |
+ | |
allow(controller).to receive(:doorkeeper_token) { token } | |
end | |
@@ -24,6 +30,11 @@ | |
expect(body_as_json[:source][:email]).to eq(user.email) | |
end | |
+ it 'includes a user\'s unauth_visibility' do | |
+ get :show | |
+ expect(body_as_json[:source][:unauth_visibility]).to be true | |
+ end | |
+ | |
it 'includes a user\'s approval status' do | |
get :show | |
expect(body_as_json[:source][:approved]).to eq(user.approved) | |
@@ -34,12 +45,53 @@ | |
expect(body_as_json[:source][:sms_verified]).to eq(true) | |
end | |
+ it 'includes pleroma.accepts_chat_messages' do | |
+ get :show | |
+ expect(body_as_json[:pleroma][:accepts_chat_messages]).to be true | |
+ end | |
+ | |
+ it 'includes a user\'s integrity score' do | |
+ get :show | |
+ expect(body_as_json[:source][:integrity]).to eq 1 | |
+ end | |
+ | |
+ it 'includes a user\'s integrity_status' do | |
+ get :show | |
+ expect(body_as_json[:source][:integrity_status]).to eq [] | |
+ end | |
+ | |
+ it 'includes feature flag config for a user' do | |
+ get :show | |
+ expect(body_as_json[:features]).to eq({ feature_1: true, feature_2: false, feature_3: true, feature_4: false }) | |
+ end | |
+ | |
+ it 'includes receive_only_follow_mentions' do | |
+ get :show | |
+ expect(body_as_json[:source][:receive_only_follow_mentions]).to eq false | |
+ end | |
+ | |
+ it 'does not include a user\'s sms_last_four_digits' do | |
+ get :show | |
+ expect(body_as_json[:source][:sms_last_four_digits]).to be nil | |
+ end | |
+ | |
context 'when a user has an sms saved' do | |
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), sms: '123-123-1222') } | |
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), sms: '+12312312222') } | |
+ | |
it 'includes if a user has verified their sms' do | |
get :show | |
expect(body_as_json[:source][:sms_verified]).to eq(true) | |
end | |
+ | |
+ it 'includes a user\'s sms_last_four_digits' do | |
+ get :show | |
+ expect(body_as_json[:source][:sms_last_four_digits]).to eq '2222' | |
+ end | |
+ | |
+ it 'returns sms_country' do | |
+ get :show | |
+ expect(body_as_json[:source][:sms_country]).to eq("US") | |
+ end | |
end | |
context 'when a user does not have an sms saved and is not approved by sms' do | |
@@ -59,6 +111,114 @@ | |
expect(body_as_json[:source][:sms_verified]).to eq(false) | |
end | |
end | |
+ | |
+ context 'when user_sms_reverification_required is true' do | |
+ before do | |
+ request.user_agent = "TruthSocialAndroid/okhttp/5.0.0-alpha.7" | |
+ end | |
+ | |
+ it 'returns "re_verify" status' do | |
+ user.create_user_sms_reverification_required | |
+ get :show | |
+ expect(body_as_json[:source][:integrity_status]).to eq %w(favourite status chat_message reblog) | |
+ end | |
+ | |
+ it 'returns "re_verify" status if last_verified_at is outside of the allowable time' do | |
+ user.create_user_sms_reverification_required | |
+ verification = DeviceVerification.create!(remote_ip: '0.0.0.0', details: {}, platform_id: 2) | |
+ OauthAccessTokens::IntegrityCredential.create!(verification: verification, token: token, user_agent: 'UserAgent', last_verified_at: Time.now - 2.hours) | |
+ | |
+ get :show | |
+ | |
+ expect(body_as_json[:source][:integrity_status]).to eq %w(favourite status chat_message reblog) | |
+ end | |
+ | |
+ it 'returns "verified"(empty array) for integrity_status' do | |
+ user.create_user_sms_reverification_required | |
+ verification = DeviceVerification.create!(remote_ip: '0.0.0.0', details: {}, platform_id: 2) | |
+ OauthAccessTokens::IntegrityCredential.create!(verification: verification, token: token, user_agent: 'UserAgent', last_verified_at: Time.now) | |
+ | |
+ get :show | |
+ | |
+ expect(body_as_json[:source][:integrity_status]).to eq [] | |
+ end | |
+ | |
+ it 'returns "verified" status if not android client' do | |
+ user.create_user_sms_reverification_required | |
+ request.user_agent = "TruthSocial/83 CFNetwork/1121.2.2 Darwin/19.3.0" | |
+ | |
+ get :show | |
+ | |
+ expect(body_as_json[:source][:integrity_status]).to eq [] | |
+ end | |
+ end | |
+ | |
+ describe '#TV' do | |
+ context 'when the request is made with the required user agent' do | |
+ before do | |
+ stub_const('Api::V1::Accounts::CredentialsController::TV_REQUIRED_IOS_VERSION', 200) | |
+ request.headers.merge!(HTTP_USER_AGENT: 'TruthSocial/201 CFNetwork/1410.0.3 Darwin/22.6.0') | |
+ allow(TvAccountsCreateWorker).to receive(:perform_async) | |
+ end | |
+ | |
+ context 'when there isnt a TV account created' do | |
+ it 'it calls the worker for creating a new account' do | |
+ get :show | |
+ expect(TvAccountsCreateWorker).to have_received(:perform_async).with(user.account.id, token.id) | |
+ end | |
+ end | |
+ | |
+ context 'when there is a TV account created with a missing pprofile_id' do | |
+ before do | |
+ tv_account = Fabricate(:tv_account, account: user.account, p_profile_id: nil) | |
+ end | |
+ | |
+ it 'it calls the worker for creating a new account' do | |
+ get :show | |
+ expect(TvAccountsCreateWorker).to have_received(:perform_async).with(user.account.id, token.id) | |
+ end | |
+ end | |
+ | |
+ context 'when there is a TV account created, but there isnt a tv session' do | |
+ before do | |
+ allow(TvAccountsLoginWorker).to receive(:perform_async) | |
+ tv_account = Fabricate(:tv_account, account: user.account) | |
+ end | |
+ | |
+ it 'it calls the worker for login to an exsting account' do | |
+ get :show | |
+ expect(TvAccountsLoginWorker).to have_received(:perform_async).with(user.account.id, token.id) | |
+ end | |
+ end | |
+ | |
+ context 'when there is a TV account created, and there is a tv session' do | |
+ before do | |
+ allow(TvAccountsLoginWorker).to receive(:perform_async) | |
+ allow(TvAccountsCreateWorker).to receive(:perform_async) | |
+ tv_account = Fabricate(:tv_account, account: user.account) | |
+ tv_device_session = Fabricate(:tv_device_session, doorkeeper_access_token: token) | |
+ end | |
+ | |
+ it 'it doesnt call the workers for tv accounts' do | |
+ get :show | |
+ expect(TvAccountsLoginWorker).not_to have_received(:perform_async) | |
+ expect(TvAccountsCreateWorker).not_to have_received(:perform_async) | |
+ end | |
+ end | |
+ end | |
+ | |
+ context 'when the request is made without the required user agent' do | |
+ before do | |
+ allow(TvAccountsLoginWorker).to receive(:perform_async) | |
+ allow(TvAccountsCreateWorker).to receive(:perform_async) | |
+ end | |
+ it 'it doesnt call the workers for tv accounts' do | |
+ get :show | |
+ expect(TvAccountsLoginWorker).not_to have_received(:perform_async) | |
+ expect(TvAccountsCreateWorker).not_to have_received(:perform_async) | |
+ end | |
+ end | |
+ end | |
end | |
describe 'PATCH #update' do | |
@@ -79,22 +239,40 @@ | |
privacy: 'unlisted', | |
sensitive: true, | |
}, | |
- pleroma_settings_store: { scott: "baio" } | |
+ pleroma_settings_store: { scott: 'baio' }, | |
+ accepting_messages: true, | |
+ unauth_visibility: false, | |
+ feeds_onboarded: true, | |
+ tv_onboarded: true, | |
+ show_nonmember_group_statuses: false, | |
+ receive_only_follow_mentions: true | |
} | |
end | |
it 'leaves pleroma_settings_store alone if not provided' do | |
user.account.reload | |
- expect(user.account.settings_store).to eq({ "scott" => "baio" }) | |
+ expect(user.account.settings_store).to eq({ 'scott' => 'baio' }) | |
patch :update, params: { | |
- display_name: "Alice IS Dead", | |
+ display_name: 'Alice IS Dead', | |
} | |
user.account.reload | |
- expect(user.account.settings_store).to eq({ "scott" => "baio" }) | |
+ expect(user.account.settings_store).to eq({ 'scott' => 'baio' }) | |
end | |
+ it 'accepts "accepts_chat_messages" param' do | |
+ user.account.reload | |
+ expect(user.account.accepting_messages).to eq(true) | |
+ | |
+ patch :update, params: { | |
+ accepts_chat_messages: false, | |
+ } | |
+ | |
+ user.account.reload | |
+ expect(user.account.accepting_messages).to eq(false) | |
+ end | |
+ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
end | |
@@ -107,15 +285,40 @@ | |
expect(user.account.avatar).to exist | |
expect(user.account.header).to exist | |
expect(user.setting_default_privacy).to eq('unlisted') | |
- # TODO @features This setting is not user configurable | |
+ # TODO: @features This setting is not user configurable | |
# expect(user.setting_default_sensitive).to eq(true) | |
- expect(user.account.settings_store).to eq({ "scott" => "baio"}) | |
+ expect(user.account.settings_store).to eq({ 'scott' => 'baio' }) | |
expect(user.account.bot?).to eq(false) | |
end | |
it 'queues up an account update distribution' do | |
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(user.account_id) | |
end | |
+ | |
+ it 'updates unauth_visibility for user' do | |
+ expect(body_as_json[:source][:unauth_visibility]).to eq false | |
+ expect(user.reload.unauth_visibility).to eq false | |
+ end | |
+ | |
+ it 'sets feeds_onboarded for account' do | |
+ expect(body_as_json[:source][:feeds_onboarded]).to eq true | |
+ expect(user.account.reload.feeds_onboarded).to eq true | |
+ end | |
+ | |
+ it 'sets tv_onboarded for account' do | |
+ expect(body_as_json[:source][:tv_onboarded]).to eq true | |
+ expect(user.account.reload.tv_onboarded).to eq true | |
+ end | |
+ | |
+ it 'sets show_nonmember_group_statuses for account' do | |
+ expect(body_as_json[:source][:show_nonmember_group_statuses]).to eq false | |
+ expect(user.account.reload.show_nonmember_group_statuses).to eq false | |
+ end | |
+ | |
+ it 'sets receive_only_follow_mentions for account' do | |
+ expect(body_as_json[:source][:receive_only_follow_mentions]).to eq true | |
+ expect(user.account.reload.receive_only_follow_mentions).to eq true | |
+ end | |
end | |
describe 'with empty source list' do | |
@@ -129,7 +332,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
end | |
- end | |
+ end | |
describe 'with invalid data' do | |
before do | |
@@ -140,6 +343,20 @@ | |
expect(response).to have_http_status(:unprocessable_entity) | |
end | |
end | |
+ | |
+ describe 'with empty header' do | |
+ before do | |
+ patch :update, params: { | |
+ header: '', | |
+ }, as: :json | |
+ end | |
+ | |
+ it 'returns http success' do | |
+ user.account.reload | |
+ expect(response).to have_http_status(200) | |
+ expect(user.account.header_file_name).to eq nil | |
+ end | |
+ end | |
end | |
describe 'GET #chat_token' do | |
@@ -157,14 +374,14 @@ | |
it 'includes the token' do | |
token = body_as_json[:token] | |
decoded_token = JWT.decode token, ENV['MATRIX_SIGNING_KEY'], true, { algorithm: 'HS256' } | |
- sub = decoded_token.first["sub"] | |
- exp = Time.at(decoded_token.first["exp"]) | |
- iat = Time.at(decoded_token.first["iat"]) | |
- nbf = Time.at(decoded_token.first["nbf"]) | |
- expect(sub).to eq('alice') | |
- expect(exp).to be >= Time.now().weeks_since(3) | |
- expect(iat).to be <= Time.now() | |
- expect(nbf).to be <= Time.now() | |
+ sub = decoded_token.first['sub'] | |
+ exp = Time.at(decoded_token.first['exp']) | |
+ iat = Time.at(decoded_token.first['iat']) | |
+ nbf = Time.at(decoded_token.first['nbf']) | |
+ expect(sub).to eq('user') | |
+ expect(exp).to be >= Time.now.weeks_since(3) | |
+ expect(iat).to be <= Time.now | |
+ expect(nbf).to be <= Time.now | |
end | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -29,34 +29,84 @@ | |
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) | |
end | |
- it 'paginates in ascending order' do | |
- follower = [] | |
- 4.times do |i| | |
- follower[i] = Fabricate(:account) | |
- follower[i].follow!(account) | |
+ context 'for the current user' do | |
+ before do | |
+ current_user = Fabricate(:user, account: account) | |
+ current_user_token = Fabricate(:accessible_access_token, resource_owner_id: current_user.id, scopes: 'read:accounts') | |
+ allow(controller).to receive(:doorkeeper_token) { current_user_token } | |
end | |
- get :index, params: { account_id: account.id, limit: 2} | |
- expect(body_as_json.size).to eq 2 | |
- expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) | |
- min_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last | |
- | |
- get :index, params: { account_id: account.id, limit: 2, min_id: min_id} | |
- expect(body_as_json.size).to eq 2 | |
- expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[0].id.to_s, follower[1].id.to_s]) | |
- min_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last | |
+ it 'paginates in descending order' do | |
+ follower = [] | |
+ 4.times do |i| | |
+ follower[i] = Fabricate(:account) | |
+ follower[i].follow!(account) | |
+ end | |
- get :index, params: { account_id: account.id, limit: 2, min_id: min_id} | |
- expect(body_as_json.size).to eq 2 | |
- expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[2].id.to_s, follower[3].id.to_s]) | |
+ get :index, params: { account_id: account.id, limit: 2 } | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[3].id.to_s, follower[2].id.to_s]) | |
+ max_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last | |
+ | |
+ get :index, params: { account_id: account.id, limit: 2, max_id: max_id} | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[1].id.to_s, follower[0].id.to_s]) | |
+ max_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last | |
+ | |
+ get :index, params: { account_id: account.id, limit: 2, max_id: max_id} | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([bob.id.to_s, alice.id.to_s]) | |
+ | |
+ end | |
end | |
+ context 'for a different user' do | |
+ before do | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ @follower = [] | |
+ 4.times do |i| | |
+ @follower[i] = Fabricate(:account) | |
+ @follower[i].follow!(account) | |
+ end | |
+ end | |
+ it 'gets the first page of results in ascending order' do | |
+ get :index, params: { account_id: account.id, limit: 2 } | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) | |
+ end | |
+ | |
+ it 'returns results from the first page, instead of the second' do | |
+ get :index, params: { account_id: account.id, limit: 2, min_id: bob.id} | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) | |
+ end | |
+ | |
+ it 'returns results from the first page with max_id as well' do | |
+ get :index, params: { account_id: account.id, limit: 2, max_id: @follower[2].id} | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) | |
+ end | |
+ | |
+ it 'does not recieve pagination headers' do | |
+ get :index, params: { account_id: account.id, limit: 2 } | |
+ expect(response.headers).not_to have_key('Link') | |
+ end | |
+ end | |
+ | |
it 'does not return blocked users' do | |
user.account.block!(bob) | |
get :index, params: { account_id: account.id, limit: 2 } | |
expect(body_as_json.size).to eq 1 | |
expect(body_as_json[0][:id]).to eq alice.id.to_s | |
+ end | |
+ | |
+ it 'does return suspended users' do | |
+ bob.suspend! | |
+ get :index, params: { account_id: account.id, limit: 2 } | |
+ | |
+ expect(body_as_json.size).to eq 2 | |
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s]) | |
end | |
context 'when requesting user is blocked' do | |
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb 2024-04-05 09:07:34 | |
@@ -11,8 +11,20 @@ | |
end | |
describe 'GET #index' do | |
- let(:simon) { Fabricate(:user, email: '[email protected]', account: Fabricate(:account, username: 'simon')).account } | |
- let(:lewis) { Fabricate(:user, email: '[email protected]', account: Fabricate(:account, username: 'lewis')).account } | |
+ let(:simon) do | |
+ Fabricate( | |
+ :user, | |
+ email: '[email protected]', | |
+ account: Fabricate(:account, username: 'simon') | |
+ ).account | |
+ end | |
+ let(:lewis) do | |
+ Fabricate( | |
+ :user, | |
+ email: '[email protected]', | |
+ account: Fabricate(:account, username: 'lewis') | |
+ ).account | |
+ end | |
before do | |
user.account.follow!(simon) | |
@@ -52,21 +64,27 @@ | |
expect(json).to be_a Enumerable | |
expect(json.first[:id]).to eq simon.id.to_s | |
expect(json.first[:following]).to be true | |
- expect(json.first[:showing_reblogs]).to be true | |
+ expect(json.first[:showing_reblogs]).to be false | |
expect(json.first[:followed_by]).to be false | |
expect(json.first[:muting]).to be false | |
- expect(json.first[:requested]).to be false | |
- expect(json.first[:domain_blocking]).to be false | |
+ expect(json.first[:note]).to eq '' | |
expect(json.second[:id]).to eq lewis.id.to_s | |
expect(json.second[:following]).to be false | |
expect(json.second[:showing_reblogs]).to be false | |
expect(json.second[:followed_by]).to be true | |
expect(json.second[:muting]).to be false | |
- expect(json.second[:requested]).to be false | |
- expect(json.second[:domain_blocking]).to be false | |
+ expect(json.second[:note]).to eq '' | |
end | |
+ it 'returns hardcoded JSON with correct data' do | |
+ json = body_as_json | |
+ expect(json.second[:requested]).to eq false | |
+ expect(json.second[:domain_blocking]).to eq false | |
+ expect(json.second[:endorsed]).to eq false | |
+ expect(json.second[:showing_reblogs]).to eq false | |
+ end | |
+ | |
it 'returns JSON with correct data on cached requests too' do | |
get :index, params: { id: [simon.id] } | |
@@ -74,7 +92,7 @@ | |
expect(json).to be_a Enumerable | |
expect(json.first[:following]).to be true | |
- expect(json.first[:showing_reblogs]).to be true | |
+ expect(json.first[:showing_reblogs]).to be false | |
end | |
it 'returns JSON with correct data after change too' do | |
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -16,5 +16,19 @@ | |
expect(response).to have_http_status(200) | |
end | |
+ | |
+ it 'inserts next pagination link header' do | |
+ account1 = Fabricate(:account, username: 'query', accepting_messages: true) | |
+ account2 = Fabricate(:account, username: 'query1', accepting_messages: true) | |
+ account3 = Fabricate(:account, username: 'query2', accepting_messages: true) | |
+ | |
+ account1.follow!(user.account) | |
+ account2.follow!(user.account) | |
+ account3.follow!(user.account) | |
+ | |
+ get :show, params: { q: 'query', followers: true, limit: 2 } | |
+ | |
+ expect(response.headers['Link'].links.first.href).to include api_v1_accounts_search_url(limit: 2, followers: true, q: 'query', offset: 2) | |
+ end | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/accounts_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -30,6 +30,10 @@ | |
expect(response).to have_http_status(200) | |
end | |
+ it 'returns pleroma.accepts_chat_messages' do | |
+ expect(body_as_json[:pleroma][:accepts_chat_messages]).to be true | |
+ end | |
+ | |
it_behaves_like 'forbidden for wrong scope', 'write:statuses' | |
end | |
@@ -60,6 +64,15 @@ | |
expect(user.account.following?(other_account)).to be true | |
end | |
+ it 'applies HostileRateLimiter to hostile accounts' do | |
+ user.account.update!(trust_level: Account::TRUST_LEVELS[:hostile]) | |
+ expect(Follow.where(account_id: user.account_id).count).to eq(1) | |
+ | |
+ post :follow, params: { id: Fabricate(:account).id } | |
+ expect(response).to have_http_status(200) | |
+ expect(Follow.where(account_id: user.account_id).count).to eq(1) | |
+ end | |
+ | |
it_behaves_like 'forbidden for wrong scope', 'read:accounts' | |
end | |
@@ -79,6 +92,14 @@ | |
it 'creates a follow request relation between user and target user' do | |
expect(user.account.requested?(other_account)).to be true | |
+ end | |
+ | |
+ it 'respects rate limit' do | |
+ 299.times do | |
+ post :follow, params: { id: Fabricate(:account).id } | |
+ end | |
+ post :follow, params: { id: Fabricate(:account).id } | |
+ expect(response).to have_http_status(429) | |
end | |
it_behaves_like 'forbidden for wrong scope', 'read:accounts' | |
Only in truth-new/opensource/spec/controllers/api/v1/admin/accounts: statuses_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin/accounts: webauthn_credentials_controller_spec.rb | |
diff -ru truth-old/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb 2024-04-05 09:07:34 | |
@@ -52,10 +52,57 @@ | |
it_behaves_like 'forbidden for wrong role', 'user' | |
it 'returns http success' do | |
- expect(body_as_json[0][:id].to_i).to eq(user.account.id) | |
+ first_account = body_as_json.first | |
+ expect(first_account[:id].to_i).to eq(user.account.id) | |
+ expect(first_account[:advertiser]).to be false | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(first_account) | |
end | |
end | |
+ | |
+ context 'with advertisers' do | |
+ let(:advertisers) do | |
+ [ | |
+ { username: 'Mary' }, | |
+ { username: 'Joe' }, | |
+ ] | |
+ end | |
+ let(:advertisers_beyond_date_range) do | |
+ [ | |
+ { username: 'Frank', travel_days_ago: 31 }, | |
+ ] | |
+ end | |
+ | |
+ before do | |
+ (advertisers + advertisers_beyond_date_range).each do |user_data| | |
+ travel_to Time.zone.now - (user_data[:travel_days_ago] || 0).days do | |
+ u = Fabricate(:user, role: role, sms: '234-555-2344', account: Fabricate(:account, username: user_data[:username])) | |
+ s = Fabricate(:status, account: u.account) | |
+ Fabricate(:ad, status: s) | |
+ end | |
+ end | |
+ | |
+ get :index | |
+ end | |
+ | |
+ it_behaves_like 'forbidden for wrong scope', 'write:statuses' | |
+ it_behaves_like 'forbidden for wrong role', 'user' | |
+ | |
+ it 'has 2 records that are advertisers within 30 days' do | |
+ expect(response).to have_http_status(200) | |
+ body_as_json.each do |account| | |
+ expect_to_be_an_admin_account(account) | |
+ end | |
+ expect(body_as_json.count).to eq 4 | |
+ | |
+ # | |
+ # non-advertisers = Alice | |
+ # advertisers = Mary, Joe | |
+ # advertiser beyond date range = Frank | |
+ # | |
+ expect(body_as_json.select { |r| r[:advertiser] }.count).to eq 2 | |
+ end | |
+ end | |
end | |
describe 'GET #show' do | |
@@ -68,11 +115,12 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
end | |
describe 'PATCH #update' do | |
- let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2 ) } | |
+ let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2) } | |
let(:account1) { user1.account } | |
let(:sms) { '123123123' } | |
@@ -84,11 +132,12 @@ | |
it_behaves_like 'forbidden for wrong role', 'user' | |
context 'with a user that is ready_to_approve' do | |
- let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2 ) } | |
+ let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2) } | |
let(:account1) { user1.account } | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'updates the users sms record and sets them to approved' do | |
@@ -99,7 +148,7 @@ | |
end | |
context 'with a user that is not ready_to_approve' do | |
- let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 0 ) } | |
+ let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 0) } | |
let(:account1) { user1.account } | |
it 'updates the users sms record' do | |
@@ -125,7 +174,7 @@ | |
email: '[email protected]', | |
password: '12345678', | |
approved: 'true', | |
- role: 'moderator' | |
+ role: 'moderator', | |
} | |
) | |
end | |
@@ -136,7 +185,7 @@ | |
it 'does not send Waitlisted email' do | |
expect(ActionMailer::Base.deliveries.count).to eq(1) | |
- expect(ActionMailer::Base.deliveries[0].subject).to eq(I18n.t('notification_mailer.user_approved.web.subject')) | |
+ expect(ActionMailer::Base.deliveries[0].subject).to eq(I18n.t('notification_mailer.user_approved.title', name: 'bob')) | |
end | |
it 'marks moderators as undiscoverable' do | |
@@ -156,7 +205,7 @@ | |
verified: verified, | |
email: '[email protected]', | |
password: '12345678', | |
- role: 'user' | |
+ role: 'user', | |
} | |
) | |
end | |
@@ -178,7 +227,7 @@ | |
end | |
context 'approved param included set to false' do | |
- let(:user_46) { Fabricate(:user, approved: false ) } | |
+ let(:user_46) { Fabricate(:user, approved: false) } | |
before do | |
user_46 | |
post( | |
@@ -190,7 +239,7 @@ | |
email: '[email protected]', | |
password: '12345678', | |
role: 'moderator', | |
- approved: 'false' | |
+ approved: 'false', | |
} | |
) | |
end | |
@@ -206,13 +255,27 @@ | |
it 'sets the users waitlist position' do | |
user = User.find_by(email: '[email protected]') | |
- expect(user.waitlist_position).to eq(11343) | |
+ expect(user.waitlist_position).to eq(11_343) | |
end | |
end | |
context 'confirmed param included' do | |
+ let!(:policy) { Fabricate(:policy, version: '1.0.0') } | |
+ | |
before do | |
- post :create, params: { username: 'bob', sms: '234-555-2344', approved: approved, verified: verified, email: '[email protected]', password: '12345678', confirmed: 'true', role: 'moderator' } | |
+ post( | |
+ :create, | |
+ params: { | |
+ username: 'bob', | |
+ sms: '234-555-2344', | |
+ approved: approved, | |
+ verified: verified, | |
+ email: '[email protected]', | |
+ password: '12345678', | |
+ confirmed: 'true', | |
+ role: 'moderator', | |
+ } | |
+ ) | |
end | |
it_behaves_like 'forbidden for wrong scope', 'write:statuses' | |
@@ -229,6 +292,7 @@ | |
expect(account.user.functional?).to be true | |
expect(account.user.confirmed?).to be true | |
expect(account.verified?).to be false | |
+ expect(account.feeds_onboarded).to be true | |
expect(account.user.moderator).to be true | |
end | |
@@ -236,6 +300,7 @@ | |
account = Account.find_by(username: 'bob') | |
expect(account.user.sms).to eq('234-555-2344') | |
expect(account.user.email).to eq('[email protected]') | |
+ expect(account.user.policy.id).to eq(policy.id) | |
end | |
it 'approves the new user' do | |
@@ -262,13 +327,178 @@ | |
context 'when confirmed param is missing' do | |
before do | |
- post :create, params: { username: 'bob', sms: '234-555-2344', approved: approved, verified: verified, email: '[email protected]', password: '12345678', role: 'moderator' } | |
+ post( | |
+ :create, | |
+ params: { | |
+ username: 'bob', | |
+ sms: '234-555-2344', | |
+ approved: approved, | |
+ verified: verified, | |
+ email: '[email protected]', | |
+ password: '12345678', | |
+ role: 'moderator', | |
+ } | |
+ ) | |
end | |
it 'does not confirm the new user' do | |
account = Account.find_by(username: 'bob') | |
expect(account.user.confirmed?).to be false | |
end | |
end | |
+ | |
+ context 'when geo params are included' do | |
+ let(:params) do | |
+ { username: 'steve', | |
+ sms: '234-555-2344', | |
+ verified: verified, | |
+ email: '[email protected]', | |
+ password: '12345678', | |
+ approved: 'true', | |
+ city_name: 'Timbuktu', | |
+ country_code: 'XX', | |
+ country_name: 'XX', | |
+ region_code: 'RR', | |
+ region_name: 'Region' } | |
+ end | |
+ | |
+ it 'creates a new country if one does not exist' do | |
+ post :create, params: params | |
+ | |
+ expect(Country.last.code).to eq 'XX' | |
+ end | |
+ | |
+ it 'creates a new region if one does not exist' do | |
+ post :create, params: params | |
+ | |
+ expect(Region.last.code).to eq 'RR' | |
+ end | |
+ | |
+ it 'creates a new state if one does not exist' do | |
+ post :create, params: params | |
+ | |
+ city = City.find_by(name: 'Timbuktu') | |
+ account = Account.find_by(username: 'steve') | |
+ expect(account.user.sign_up_city_id).to eq city.id | |
+ end | |
+ end | |
+ | |
+ context 'when geo params are not included' do | |
+ it 'defaults to the city with id = 1' do | |
+ post( | |
+ :create, | |
+ params: { | |
+ username: 'steve', | |
+ sms: '234-555-2344', | |
+ verified: verified, | |
+ email: '[email protected]', | |
+ password: '12345678', | |
+ approved: 'true', | |
+ } | |
+ ) | |
+ | |
+ account = Account.find_by(username: 'steve') | |
+ expect(account.user.country.id).to eq 1 | |
+ expect(account.user.sign_up_city_id).to eq 1 | |
+ end | |
+ end | |
+ | |
+ context 'when registration_token is present' do | |
+ let(:registration_token) { Base64.strict_encode64(SecureRandom.random_bytes(32)) } | |
+ let!(:verification) do | |
+ DeviceVerification.create!(platform_id: 2, | |
+ remote_ip: '0.0.0.0', | |
+ details: { | |
+ registration_token: registration_token, | |
+ }) | |
+ end | |
+ | |
+ context 'when android client' do | |
+ before do | |
+ @challenge = RegistrationService.new(token: registration_token, platform: 'android', new_otc: true).call[:one_time_challenge] | |
+ @registration = Registration.find_by(token: registration_token) | |
+ request.headers['registration_token'] = registration_token | |
+ end | |
+ | |
+ it 'should delete registration records for android device registrant' do | |
+ post :create, params: { | |
+ username: 'bob', | |
+ sms: '234-555-2344', | |
+ verified: verified, | |
+ email: '[email protected]', | |
+ password: '12345678', | |
+ role: 'moderator', | |
+ approved: 'false', | |
+ geoip_country_code: 'US', | |
+ token: @registration.token, | |
+ } | |
+ | |
+ verification = DeviceVerification.find_by("details ->> 'registration_token' = '#{@registration.token}'") | |
+ expect(response).to have_http_status(200) | |
+ expect { @registration.reload }.to raise_error ActiveRecord::RecordNotFound | |
+ expect(RegistrationOneTimeChallenge.find_by(registration_id: @registration.id)).to be_nil | |
+ expect(OneTimeChallenge.find_by(challenge: @challenge)).to be_nil | |
+ expect(verification.details['user_id']).to be_present | |
+ end | |
+ end | |
+ | |
+ context 'when ios client' do | |
+ let(:credential) { Fabricate(:webauthn_credential, external_id: 'EXTERNAL ID') } | |
+ | |
+ before do | |
+ @challenge = RegistrationService.new(token: registration_token, platform: 'ios', new_otc: true).call[:one_time_challenge] | |
+ @registration = Registration.find_by(token: registration_token) | |
+ @rwc = RegistrationWebauthnCredential.create!(registration: @registration, webauthn_credential: credential) | |
+ request.headers['registration_token'] = registration_token | |
+ end | |
+ | |
+ it 'should transfer webauthn credentials to new user and delete registration records for ios device registrant' do | |
+ email = '[email protected]' | |
+ | |
+ post :create, params: { | |
+ username: 'bob', | |
+ sms: '234-555-2344', | |
+ verified: verified, | |
+ email: email, | |
+ password: '12345678', | |
+ role: 'moderator', | |
+ approved: 'false', | |
+ geoip_country_code: 'US', | |
+ token: @registration.token, | |
+ } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect { @registration.reload }.to raise_error ActiveRecord::RecordNotFound | |
+ expect { @rwc.reload }.to raise_error ActiveRecord::RecordNotFound | |
+ user = User.find_by!(email: email) | |
+ expect(credential.reload.user_id).to eq user.id | |
+ expect(@registration.registration_webauthn_credential).to be_nil | |
+ expect(@registration.registration_one_time_challenge).to be_nil | |
+ end | |
+ | |
+ context 'credential error' do | |
+ let(:credential) { Fabricate(:webauthn_credential, external_id: 'EXTERNAL ID', user: Fabricate(:user)) } | |
+ | |
+ it 'should return a 422 is webauthn credential is already affiliated with an account' do | |
+ email = '[email protected]' | |
+ | |
+ post :create, params: { | |
+ username: 'bob', | |
+ sms: '234-555-2344', | |
+ verified: verified, | |
+ email: email, | |
+ password: '12345678', | |
+ role: 'moderator', | |
+ approved: 'false', | |
+ geoip_country_code: 'US', | |
+ token: @registration.token, | |
+ } | |
+ | |
+ expect(response).to have_http_status(422) | |
+ expect(body_as_json[:errors]).to eq 'Webauthn Credential is already associated with an account' | |
+ end | |
+ end | |
+ end | |
+ end | |
end | |
describe 'POST #approve' do | |
@@ -348,6 +578,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'removes user' do | |
@@ -366,6 +597,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'enables user' do | |
@@ -384,6 +616,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'unsuspends account' do | |
@@ -402,6 +635,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'unsuspends account' do | |
@@ -420,6 +654,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'unsensitives account' do | |
@@ -438,6 +673,7 @@ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
+ expect_to_be_an_admin_account(body_as_json) | |
end | |
it 'unsilences account' do | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: bulk_account_actions_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: chat_messages_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: groups | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: groups_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: links_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: policies_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: registrations_controller_spec.rb | |
diff -ru truth-old/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -3,11 +3,11 @@ | |
RSpec.describe Api::V1::Admin::StatusesController, type: :controller do | |
render_views | |
- let(:role) { 'admin' } | |
- let(:user) { Fabricate(:user, role: role, sms: '234-555-2344', account: Fabricate(:account, username: 'alice')) } | |
+ let(:role) { 'admin' } | |
+ let(:account) { Fabricate(:account, username: 'alice') } | |
+ let(:user) { Fabricate(:user, role: role, sms: '234-555-2344', account: account) } | |
let(:scopes) { 'admin:read admin:write' } | |
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
- let(:account) { Fabricate(:user).account } | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
let(:status) { Fabricate(:status, account: user.account) } | |
before do | |
@@ -19,6 +19,119 @@ | |
it 'returns http success' do | |
get :index, params: { ids: [status.id] } | |
expect(response).to have_http_status(200) | |
+ end | |
+ end | |
+ end | |
+ | |
+ describe 'POST #privatize' do | |
+ it 'returns http success and sets visibility to self' do | |
+ post :privatize, params: { status_id: status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json[:visibility]).to eq('self') | |
+ end | |
+ end | |
+ | |
+ describe 'POST #publicize' do | |
+ context 'normal status' do | |
+ it 'returns http success and sets visibility to public' do | |
+ post :publicize, params: { status_id: status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json[:visibility]).to eq('public') | |
+ end | |
+ end | |
+ | |
+ context 'group status' do | |
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) } | |
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) } | |
+ | |
+ before do | |
+ group.memberships.create!(group: group, account: account, role: :owner) | |
+ end | |
+ | |
+ it 'returns http success and sets visibility to group' do | |
+ post :publicize, params: { status_id: group_status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json[:visibility]).to eq('group') | |
+ end | |
+ end | |
+ end | |
+ | |
+ describe 'POST #discard' do | |
+ it 'returns http success and updates statistics' do | |
+ status.reload | |
+ Procedure.process_account_status_statistics_queue | |
+ expect(status.deleted_at).to be_nil | |
+ expect(AccountStatusStatistic.find_by(account_id: user.account_id).statuses_count).to eq(1) | |
+ post :discard, params: { status_id: status.id } | |
+ Procedure.process_account_status_statistics_queue | |
+ expect(response).to have_http_status(200) | |
+ expect(AccountStatusStatistic.find_by(account_id: user.account_id)).to be_nil | |
+ end | |
+ | |
+ context 'group status' do | |
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) } | |
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) } | |
+ | |
+ before do | |
+ group.memberships.create!(group: group, account: account, role: :owner) | |
+ end | |
+ | |
+ it 'returns http success and discards the group status' do | |
+ post :discard, params: { status_id: group_status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect{ Status.find(group_status.id) }.to raise_exception ActiveRecord::RecordNotFound | |
+ end | |
+ end | |
+ end | |
+ | |
+ describe 'POST #undiscard' do | |
+ before do | |
+ status.discard! | |
+ end | |
+ it 'returns http success and un-discards the status' do | |
+ post :undiscard, params: { status_id: status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(status.reload.discarded?).to eq(false) | |
+ end | |
+ | |
+ context 'group status' do | |
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) } | |
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) } | |
+ | |
+ before do | |
+ group.memberships.create!(group: group, account: account, role: :owner) | |
+ group_status.discard! | |
+ end | |
+ | |
+ it 'returns http success and un-discards the group status' do | |
+ post :undiscard, params: { status_id: group_status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(group_status.reload.discarded?).to eq(false) | |
+ end | |
+ end | |
+ end | |
+ | |
+ describe 'POST #sensitize' do | |
+ context 'normal status' do | |
+ it 'returns http success and sets sensitive to true' do | |
+ post :sensitize, params: { status_id: status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json[:sensitive]).to eq(true) | |
+ end | |
+ end | |
+ | |
+ context 'group status' do | |
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) } | |
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) } | |
+ | |
+ before do | |
+ group.memberships.create!(group: group, account: account, role: :owner) | |
+ end | |
+ | |
+ it 'returns http success and sets sensitive to true' do | |
+ post :sensitize, params: { status_id: group_status.id } | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json[:sensitive]).to eq(true) | |
end | |
end | |
end | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: tags_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: trending_groups_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: trending_statuses | |
diff -ru truth-old/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -5,69 +5,115 @@ | |
let(:role) { 'admin' } | |
let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) } | |
+ let(:trending_account1) { Fabricate(:account, username: 'john') } | |
+ let(:trending_account2) { Fabricate(:account, username: 'bob') } | |
+ let(:trending_account3) { Fabricate(:account, username: 'gary') } | |
+ let(:trending_account4) { Fabricate(:account, username: 'greg') } | |
+ let(:trending_account5) { Fabricate(:account, username: 'steve') } | |
+ let(:trending_account6) { Fabricate(:account, username: 'phil') } | |
let(:scopes) { 'admin:read admin:write' } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
let(:status) { Fabricate(:status, account: user.account, text: "It is a great post") } | |
- | |
- before do | |
- allow(controller).to receive(:doorkeeper_token) { token } | |
+ let(:statuses) do | |
+ [ | |
+ Fabricate(:status, account: trending_account1), | |
+ Fabricate(:status, account: trending_account1), | |
+ Fabricate(:status,account: trending_account2), | |
+ Fabricate(:status, account: trending_account2), | |
+ Fabricate(:status, account: trending_account3), | |
+ Fabricate(:status, account: trending_account3), | |
+ Fabricate(:status, account: trending_account4), | |
+ Fabricate(:status, account: trending_account4), | |
+ Fabricate(:status, account: trending_account5), | |
+ Fabricate(:status, account: trending_account5), | |
+ Fabricate(:status, account: trending_account6), | |
+ Fabricate(:status, account: trending_account6), | |
+ ] | |
end | |
context '#index' do | |
describe 'GET #index' do | |
- before do | |
- Fabricate(:favourite, account: user.account, status: status) | |
+ it 'should return 403 when not an admin' do | |
get :index | |
+ expect(response).to have_http_status(403) | |
end | |
- | |
- it 'returns http success' do | |
- expect(response).to have_http_status(200) | |
- end | |
- | |
- it 'returns the correct statuses' do | |
- expect(body_as_json.length).to eq(1) | |
- end | |
end | |
- describe 'GET #index trending=true' do | |
+ describe 'GET #index' do | |
before do | |
- Fabricate(:trending, status: status, user: user) | |
- get :index, params: { trending: true } | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ relation = Status.all | |
+ allow(relation).to receive(:[]).and_return(statuses) | |
+ allow(Status).to receive(:trending_statuses).and_return(relation) | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
end | |
- it 'returns http success' do | |
+ let(:trending_statuses) { Status.trending_statuses } | |
+ | |
+ it 'returns http success and trending list' do | |
+ get :index | |
+ | |
expect(response).to have_http_status(200) | |
+ expect(body_as_json.length).to eq(10) | |
end | |
- it 'returns the correct statuses' do | |
- expect(body_as_json.length).to eq(1) | |
+ it 'should return page two with appropriate headers' do | |
+ get :index, params: { page: 2 } | |
+ | |
+ last_statuses = trending_statuses.last(2) | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json.length).to eq(2) | |
+ expect(body_as_json.pluck(:id)).to eq([last_statuses.first.id.to_s, last_statuses.last.id.to_s]) | |
+ expect(response.headers['x-page-size']).to eq(10) | |
+ expect(response.headers['x-page']).to eq("2") | |
+ expect(response.headers['x-total']).to eq(2) | |
+ expect(response.headers['x-total-pages']).to eq(2) | |
end | |
end | |
end | |
- context "#update" do | |
- it "creates a trending" do | |
- expect { put :update, params: { id: status.id } }.to change { Trending.count }.by(1) | |
+ describe 'PUT #include' do | |
+ it 'should return 403 when not an admin' do | |
+ get :index | |
+ expect(response).to have_http_status(403) | |
end | |
- it "returns http 204" do | |
- put :update, params: { id: status.id } | |
- expect(response).to have_http_status(204) | |
+ it 'should return a 404 if status id is non-existent' do | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ put :include, params: { id: 'BAD' } | |
+ | |
+ expect(response).to have_http_status(404) | |
end | |
+ | |
+ it 'should make a status re-eligible for trending list' do | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ TrendingStatusExcludedStatus.create(status_id: status.id) | |
+ put :include, params: { id: status.id } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect(TrendingStatusExcludedStatus.count).to eq(0) | |
+ end | |
end | |
- context '#destroy' do | |
- before do | |
- Fabricate(:trending, status: status, user: user) | |
+ describe 'PUT #exclude' do | |
+ it 'should return 403 when not an admin' do | |
+ get :index | |
+ expect(response).to have_http_status(403) | |
end | |
- it "destroys a trending" do | |
- expect { delete :destroy, params: { id: status.id } }.to change { Trending.count }.by(-1) | |
+ it 'should return a 404 if status id is non-existent' do | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ put :include, params: { id: 'BAD' } | |
+ | |
+ expect(response).to have_http_status(404) | |
end | |
- it "returns http 204" do | |
- delete :destroy, params: { id: status.id } | |
- expect(response).to have_http_status(204) | |
+ it 'should exclude a status from trending list' do | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ put :exclude, params: { id: status.id } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect(TrendingStatusExcludedStatus.first.status_id).to eq(status.id) | |
end | |
end | |
end | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: trending_tags_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: truth | |
Only in truth-new/opensource/spec/controllers/api/v1/admin: tv | |
diff -ru truth-old/opensource/spec/controllers/api/v1/conversations_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/conversations_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/conversations_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/conversations_controller_spec.rb 2024-04-05 09:07:34 | |
@@ -5,7 +5,7 @@ | |
let!(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
- let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) } | |
+ let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob', created_at: Time.now - 10.days)) } | |
before do | |
acct = Fabricate(:account, username: "ModerationAI") | |
@@ -18,7 +18,8 @@ | |
let(:scopes) { 'read:statuses' } | |
before do | |
- PostStatusService.new.call(other.account, text: 'Hey @alice', visibility: 'direct', mentions: ['alice']) | |
+ status = PostStatusService.new.call(other.account, text: 'Hey @alice', visibility: 'direct', mentions: ['alice']) | |
+ ProcessMentionsService.create_notification(status, status.mentions.first) | |
end | |
it 'returns http success' do | |
Only in truth-new/opensource/spec/controllers/api/v1: feeds_controller_spec.rb | |
Only in truth-new/opensource/spec/controllers/api/v1: groups | |
Only in truth-new/opensource/spec/controllers/api/v1: groups_controller_spec.rb | |
diff -ru truth-old/opensource/spec/controllers/api/v1/instances_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/instances_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/instances_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/instances_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -17,12 +17,61 @@ | |
get :show | |
expect(response).to have_http_status(200) | |
+ expect(body_as_json[:uri]).to be_an_instance_of String | |
+ expect(body_as_json[:title]).to be_an_instance_of String | |
+ expect(body_as_json[:short_description]).to be_an_instance_of String | |
+ expect(body_as_json[:description]).to be_an_instance_of String | |
+ expect(body_as_json[:email]).to be_an_instance_of String | |
+ expect(body_as_json[:version]).to be_an_instance_of String | |
+ expect(body_as_json[:urls]).to be_an_instance_of Hash | |
+ expect(body_as_json[:thumbnail]).to be_an_instance_of String | |
+ expect(body_as_json[:languages]).to be_an_instance_of Array | |
+ expect(body_as_json[:registrations]).to be_boolean | |
+ expect(body_as_json[:approval_required]).to be_boolean | |
+ expect(body_as_json[:invites_enabled]).to be_boolean | |
+ expect(body_as_json[:feature_quote]).to be_boolean | |
+ expect(body_as_json[:rules]).to be_an_instance_of Array | |
+ expect(body_as_json[:configuration]).to be_an_instance_of Hash | |
+ expect(body_as_json[:configuration][:statuses][:max_characters]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:statuses][:max_media_attachments]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:statuses][:characters_reserved_per_url]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:media_attachments][:supported_mime_types]).to be_an_instance_of Array | |
+ expect(body_as_json[:configuration][:media_attachments][:image_size_limit]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:media_attachments][:image_matrix_limit]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:media_attachments][:video_size_limit]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:media_attachments][:video_frame_rate_limit]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:media_attachments][:video_matrix_limit]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:media_attachments][:video_duration_limit]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:chats][:max_characters]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:chats][:max_messages_per_minute]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:chats][:max_media_attachments]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:polls][:max_options]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:polls][:max_characters_per_option]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:polls][:min_expiration]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:polls][:max_expiration]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:ads][:algorithm]).to have_key(:name) | |
+ expect(body_as_json[:configuration][:ads][:algorithm][:configuration]).to have_key(:frequency) | |
+ expect(body_as_json[:configuration][:ads][:algorithm][:configuration]).to have_key(:phase_min) | |
+ expect(body_as_json[:configuration][:ads][:algorithm][:configuration]).to have_key(:phase_max) | |
+ expect(body_as_json[:configuration][:groups][:max_characters_name]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:groups][:max_characters_description]).to be_an_instance_of Integer | |
+ expect(body_as_json[:configuration][:groups][:max_admins_allowed]).to be_an_instance_of Integer | |
end | |
it 'returns compat version string' do | |
get :show | |
- expect(response.body).to include('compatible; TruthSocial') | |
+ json = body_as_json | |
+ expect(json[:version]).to eq '3.4.1 (compatible; TruthSocial 1.0.0)' | |
+ end | |
+ | |
+ it 'returns +unreleased on staging' do | |
+ stub_const('ENV', ENV.to_hash.merge('IS_STAGING' => 'true')) | |
+ | |
+ get :show | |
+ | |
+ json = body_as_json | |
+ expect(json[:version]).to eq '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)' | |
end | |
end | |
end | |
Only in truth-old/opensource/spec/controllers/api/v1: ma1sd | |
diff -ru truth-old/opensource/spec/controllers/api/v1/notifications_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/notifications_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/notifications_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/notifications_controller_spec.rb 2024-04-05 09:07:34 | |
@@ -5,13 +5,13 @@ | |
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
- let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) } | |
+ let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob', created_at: Time.now - 10.days)) } | |
let(:third) { Fabricate(:user, account: Fabricate(:account, username: 'carol')) } | |
before do | |
- acct = Fabricate(:account, username: "ModerationAI") | |
+ acct = Fabricate(:account, username: 'ModerationAI') | |
Fabricate(:user, admin: true, account: acct) | |
- stub_request(:post, ENV["MODERATION_TASK_API_URL"]).to_return(status: 200, body: request_fixture('moderation-response-0.txt')) | |
+ stub_request(:post, ENV['MODERATION_TASK_API_URL']).to_return(status: 200, body: request_fixture('moderation-response-0.txt')) | |
allow(controller).to receive(:doorkeeper_token) { token } | |
end | |
@@ -52,15 +52,31 @@ | |
describe 'GET #index' do | |
let(:scopes) { 'read:notifications' } | |
+ let(:group) { Fabricate(:group, display_name: 'Group', note: 'Note', owner_account: user.account) } | |
before do | |
+ group.memberships.create!(account: user.account, role: :owner) | |
+ group.memberships.create!(account: other.account) | |
first_status = PostStatusService.new.call(user.account, text: 'Test') | |
@reblog_of_first_status = ReblogService.new.call(other.account, first_status) | |
mentioning_status = PostStatusService.new.call(other.account, text: 'Hello @alice', mentions: ['alice']) | |
@mention_from_status = mentioning_status.mentions.first | |
+ group_mentioning_status = PostStatusService.new.call(other.account, text: 'Hello @alice group_mention', mentions: ['alice'], group: group, visibility: 'group') | |
+ @group_mention_from_status = group_mentioning_status.mentions.first | |
+ group_status = PostStatusService.new.call(user.account, text: 'Hello other this is a group_mention', group: group, visibility: 'group') | |
+ mentioning_self_status = PostStatusService.new.call(other.account, text: 'Hello again @alice', mentions: ['alice']) | |
+ [first_status,mentioning_status, group_mentioning_status, group_status, mentioning_self_status].each { |status| PostDistributionService.new.distribute_to_author_and_followers(status) } | |
+ [ | |
+ [mentioning_status, @mention_from_status], | |
+ [group_mentioning_status, @group_mention_from_status], | |
+ [mentioning_self_status, mentioning_self_status.mentions.first] | |
+ ].each { |status, mention| ProcessMentionsService.create_notification(status, mention) } | |
@favourite = FavouriteService.new.call(other.account, first_status) | |
@second_favourite = FavouriteService.new.call(third.account, first_status) | |
+ ReblogService.new.call(other.account, group_status) | |
+ FavouriteService.new.call(other.account, group_status) | |
@follow = FollowService.new.call(other.account, user.account) | |
+ mentioning_self_status.update(visibility: 'self') | |
end | |
describe 'with no options' do | |
@@ -73,53 +89,49 @@ | |
end | |
it 'includes reblog' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status) | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'reblog' | |
end | |
it 'includes mention' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@mention_from_status) | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'mention' | |
end | |
it 'includes favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@favourite) | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'favourite' | |
end | |
it 'includes follow' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@follow) | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'follow' | |
end | |
- end | |
- describe 'from specified user' do | |
- before do | |
- get :index, params: { account_id: third.account.id } | |
+ it 'includes group_mention' do | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'group_mention' | |
end | |
- it 'returns http success' do | |
- expect(response).to have_http_status(200) | |
+ it 'includes group_favourite' do | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'group_favourite' | |
end | |
- it 'includes favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@second_favourite) | |
+ it 'includes group_reblog' do | |
+ expect(body_as_json.map { |x| x[:type] }).to include 'group_reblog' | |
end | |
+ end | |
- it 'excludes favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@favourite) | |
+ describe 'with account_id param' do | |
+ before do | |
+ get :index, params: { account_id: third.account.id } | |
end | |
- it 'excludes mention' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status) | |
+ it 'returns http success' do | |
+ expect(response).to have_http_status(200) | |
end | |
- it 'excludes reblog' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@reblog_of_first_status) | |
+ it 'returns only notifications from specified user' do | |
+ expect(body_as_json.map { |x| x[:account][:id] }.uniq).to eq [third.account.id.to_s] | |
end | |
- | |
- it 'excludes follow' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@follow) | |
- end | |
end | |
- describe 'from nonexistent user' do | |
+ describe 'with invalid account_id param' do | |
before do | |
get :index, params: { account_id: 'foo' } | |
end | |
@@ -128,54 +140,58 @@ | |
expect(response).to have_http_status(200) | |
end | |
- it 'excludes favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@favourite) | |
+ it 'returns nothing' do | |
+ expect(body_as_json.size).to eq 0 | |
end | |
+ end | |
- it 'excludes second favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@second_favourite) | |
+ describe 'with excluded_types param' do | |
+ before do | |
+ get :index, params: { exclude_types: %w(mention) } | |
end | |
- it 'excludes mention' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status) | |
+ it 'returns http success' do | |
+ expect(response).to have_http_status(200) | |
end | |
- it 'excludes reblog' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@reblog_of_first_status) | |
+ it 'returns everything but excluded type' do | |
+ expect(body_as_json.size).to_not eq 0 | |
+ expect(body_as_json.map { |x| x[:type] }.uniq).to_not include 'mention' | |
end | |
- | |
- it 'excludes follow' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@follow) | |
- end | |
end | |
- describe 'with excluded mentions' do | |
+ describe 'with types param' do | |
before do | |
- get :index, params: { exclude_types: ['mention'] } | |
+ get :index, params: { types: %w(mention) } | |
end | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
end | |
- it 'includes reblog' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status) | |
+ it 'returns only requested type' do | |
+ expect(body_as_json.map { |x| x[:type] }.uniq).to eq ['mention'] | |
end | |
+ end | |
- it 'excludes mention' do | |
- expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status) | |
+ describe 'with self status' do | |
+ before do | |
+ get :index, params: { types: %w(mention) } | |
end | |
- it 'includes favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@favourite) | |
+ it 'does not return the self status' do | |
+ expect(body_as_json.select {|n| n[:type] == 'mention' }.length).to eq 1 | |
end | |
+ end | |
- it 'includes third favourite' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@second_favourite) | |
+ describe 'pagination headers' do | |
+ before do | |
+ get :index, params: { limit: 4 } | |
end | |
- it 'includes follow' do | |
- expect(assigns(:notifications).map(&:activity)).to include(@follow) | |
+ it 'returns next and prev links' do | |
+ expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq "http://test.host/api/v1/notifications?limit=4&max_id=#{body_as_json.last[:id]}" | |
+ expect(response.headers['Link'].find_link(['rel', 'prev']).href).to eq "http://test.host/api/v1/notifications?limit=4&min_id=#{body_as_json.first[:id]}" | |
end | |
end | |
end | |
Only in truth-new/opensource/spec/controllers/api/v1: pleroma | |
diff -ru truth-old/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -13,7 +13,7 @@ | |
let(:poll) { Fabricate(:poll) } | |
before do | |
- post :create, params: { poll_id: poll.id, choices: %w(1) } | |
+ post :create, params: { poll_id: poll.id, choices: [poll.options.last.option_number.to_s] } | |
end | |
it 'returns http success' do | |
@@ -21,14 +21,19 @@ | |
end | |
it 'creates a vote' do | |
- vote = poll.votes.where(account: user.account).first | |
+ vote = PollVote.where(poll_id: poll.id, account: user.account).first | |
expect(vote).to_not be_nil | |
- expect(vote.choice).to eq 1 | |
+ expect(vote.option_number).to eq 1 | |
end | |
- it 'updates poll tallies' do | |
- expect(poll.reload.cached_tallies).to eq [0, 1] | |
+ it 'updates the stats' do | |
+ Procedure.process_poll_option_statistics_queue | |
+ expect(StatisticPollOption.where(poll_id: poll.id, option_number: 0).first).to eq nil | |
+ expect(StatisticPollOption.where(poll_id: poll.id, option_number: 1).first.votes).to eq 1 | |
+ | |
+ expect(StatisticPoll.where(poll_id: poll.id).first.votes).to eq 1 | |
+ expect(StatisticPoll.where(poll_id: poll.id).first.voters).to eq 1 | |
end | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -5,10 +5,13 @@ | |
describe Api::V1::Push::SubscriptionsController do | |
render_views | |
- let(:user) { Fabricate(:user) } | |
- let(:second_user) { Fabricate(:user) } | |
+ let(:user) { Fabricate(:user, id: 1) } | |
+ let!(:second_user) { Fabricate(:user) } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'push') } | |
- let(:second_token) { Fabricate(:accessible_access_token, resource_owner_id: second_user.id, scopes: 'push') } | |
+ let(:first_token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'push') } | |
+ let!(:second_token) { Fabricate(:accessible_access_token, resource_owner_id: second_user.id, scopes: 'push') } | |
+ let(:mobile_device_token) { 'SgQsFj5JCkcoMrMt2WHoPTSTCkQCkQ/STCkSTCkQQSTCkQSTgQsFj5JCkcoMrMt2WHoPCkQSTSTCkQCkQ=' } | |
+ let(:endpoint) { 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX' } | |
before do | |
allow(controller).to receive(:doorkeeper_token) { token } | |
@@ -17,7 +20,7 @@ | |
let(:create_payload) do | |
{ | |
subscription: { | |
- endpoint: 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX', | |
+ endpoint: endpoint, | |
keys: { | |
p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=', | |
auth: 'eH_C8rq2raXqlcBVDa1gLg==', | |
@@ -29,7 +32,7 @@ | |
let(:create_mobile_payload) do | |
{ | |
subscription: { | |
- device_token: 'SgQsFj5JCkcoMrMt2WHoPTSTCkQCkQ/STCkSTCkQQSTCkQSTgQsFj5JCkcoMrMt2WHoPCkQSTSTCkQCkQ=', | |
+ device_token: mobile_device_token, | |
platform: 1, | |
environment: 1 | |
} | |
@@ -54,6 +57,34 @@ | |
}.with_indifferent_access | |
end | |
+ let(:update_payload) do | |
+ { | |
+ subscription: { | |
+ device_token: mobile_device_token, | |
+ platform: 1, | |
+ environment: 1, | |
+ endpoint: endpoint, | |
+ keys: { | |
+ p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=', | |
+ auth: 'eH_C8rq2raXqlcBVDa1gLg==', | |
+ }, | |
+ }, | |
+ data: { | |
+ policy: 'all', | |
+ | |
+ alerts: { | |
+ follow: true, | |
+ follow_request: true, | |
+ favourite: false, | |
+ reblog: true, | |
+ mention: false, | |
+ poll: true, | |
+ status: false, | |
+ } | |
+ } | |
+ }.with_indifferent_access | |
+ end | |
+ | |
describe 'POST #create' do | |
context 'with web notifications' do | |
before do | |
@@ -91,8 +122,19 @@ | |
expect(push_subscription.access_token_id).to eq token.id | |
end | |
- it 'removes subscriptions with the same device_id' do | |
- allow(controller).to receive(:doorkeeper_token) { second_token } | |
+ context do | |
+ it 'does not remove subscriptions with the same device_id and different user_id' do | |
+ user2 = Fabricate(:user) | |
+ token2 = Fabricate(:accessible_access_token, resource_owner_id: user2.id, scopes: 'push') | |
+ allow(controller).to receive(:current_user) { user2 } | |
+ allow(controller).to receive(:doorkeeper_token) { token2 } | |
+ post :create, params: create_mobile_payload | |
+ expect(Web::PushSubscription.where(platform: create_mobile_payload[:subscription][:platform]).count).to eq 2 | |
+ end | |
+ end | |
+ | |
+ it 'removes subscriptions with the same device_id and user_id' do | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
post :create, params: create_mobile_payload | |
expect(Web::PushSubscription.where(platform: create_mobile_payload[:subscription][:platform]).count).to eq 1 | |
end | |
@@ -122,18 +164,45 @@ | |
end | |
describe 'PUT #update' do | |
- before do | |
- post :create, params: create_payload | |
- put :update, params: alerts_payload | |
+ context 'subscription record exists' do | |
+ before do | |
+ post :create, params: create_payload | |
+ put :update, params: alerts_payload.merge({subscription: {device_token: 'updated_device_token'}}) | |
+ end | |
+ | |
+ it 'changes alert settings, and updates the device token' do | |
+ push_subscription = Web::PushSubscription.find_by(endpoint: create_payload[:subscription][:endpoint]) | |
+ | |
+ expect(push_subscription.data['policy']).to eq(alerts_payload[:data][:policy]) | |
+ | |
+ expect(push_subscription.device_token).to eq('updated_device_token') | |
+ | |
+ %w(follow follow_request favourite reblog mention poll status).each do |type| | |
+ expect(push_subscription.data['alerts'][type]).to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) | |
+ end | |
+ end | |
end | |
- it 'changes alert settings' do | |
- push_subscription = Web::PushSubscription.find_by(endpoint: create_payload[:subscription][:endpoint]) | |
+ context 'deleted subscription record' do | |
+ it 'creates a new subscription if one does not exist' do | |
+ put :update, params: update_payload | |
+ expect(response).to have_http_status(200) | |
+ end | |
- expect(push_subscription.data['policy']).to eq(alerts_payload[:data][:policy]) | |
+ it 'if same device token and user but different access token' do | |
+ Web::PushSubscription.create!( | |
+ device_token: mobile_device_token, | |
+ platform: 1, | |
+ environment: 1, | |
+ user_id: user.id, | |
+ access_token_id: token.id | |
+ ) | |
- %w(follow follow_request favourite reblog mention poll status).each do |type| | |
- expect(push_subscription.data['alerts'][type]).to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) | |
+ allow(controller).to receive(:doorkeeper_token) { first_token } | |
+ | |
+ put :update, params: update_payload | |
+ expect(response).to have_http_status(200) | |
+ expect(Web::PushSubscription.where(device_token: mobile_device_token).count).to eq 1 | |
end | |
end | |
end | |
Only in truth-new/opensource/spec/controllers/api/v1: push_notifications | |
Only in truth-new/opensource/spec/controllers/api/v1: recommendations | |
diff -ru truth-old/opensource/spec/controllers/api/v1/reports_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/reports_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/reports_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/reports_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -5,8 +5,12 @@ | |
RSpec.describe Api::V1::ReportsController, type: :controller do | |
render_views | |
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
+ let(:account) { Fabricate(:account, username: 'alice') } | |
+ let(:user) { Fabricate(:user, account: account) } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } | |
+ let(:group) { Fabricate(:group, display_name: 'test group', note: 'groupy group', owner_account: account) } | |
+ let!(:group_membership) { Fabricate(:group_membership, group: group, account: account, role: 'owner') } | |
+ let(:group_status) { Fabricate(:status, group: group, visibility: 'group', account: account) } | |
before do | |
allow(controller).to receive(:doorkeeper_token) { token } | |
@@ -17,18 +21,120 @@ | |
let!(:status) { Fabricate(:status) } | |
let!(:admin) { Fabricate(:user, admin: true) } | |
- before do | |
- allow(AdminMailer).to receive(:new_report).and_return(double('email', deliver_later: nil)) | |
- post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } | |
+ context 'normal account' do | |
+ before do | |
+ allow(AdminMailer).to receive(:new_report).and_return(double('email', deliver_later: nil)) | |
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } | |
+ end | |
+ | |
+ it 'creates a report' do | |
+ expect(status.reload.account.targeted_reports).not_to be_empty | |
+ expect(response).to have_http_status(200) | |
+ end | |
+ | |
+ it 'respects rate limit' do | |
+ 399.times do | |
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } | |
+ end | |
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } | |
+ expect(response).to have_http_status(422) | |
+ end | |
+ | |
+ it 'doesn`t create a duplicate report' do | |
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } | |
+ expect(response).to have_http_status(422) | |
+ end | |
end | |
- it 'creates a report' do | |
- expect(status.reload.account.targeted_reports).not_to be_empty | |
- expect(response).to have_http_status(200) | |
+ context 'hostile account' do | |
+ it 'subject to HostileRateLimiter' do | |
+ user.account.update!(trust_level: Account::TRUST_LEVELS[:hostile]) | |
+ expect(user.account.trust_level).to eq(Account::TRUST_LEVELS[:hostile]) | |
+ expect(Report.where(account_id: user.account.id).count).to eq(0) | |
+ | |
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' } | |
+ expect(response).to have_http_status(200) | |
+ expect(Report.where(account_id: user.account_id).count).to eq(0) | |
+ end | |
end | |
- # it 'sends e-mails to admins' do | |
- # expect(AdminMailer).to have_received(:new_report).with(admin.account, Report) | |
- # end | |
+ context 'chat messages' do | |
+ let(:recipient) { Fabricate(:account, username: 'theirs') } | |
+ let(:recipient_user) { Fabricate(:user, account: recipient) } | |
+ | |
+ it 'creates a report with chat messages' do | |
+ chat = Chat.create(owner_account_id: account.id, members: [recipient.id]) | |
+ message = JSON.parse(ChatMessage.create_by_function!({ | |
+ account_id: recipient.id, | |
+ token: nil, | |
+ idempotency_key: nil, | |
+ chat_id: chat.chat_id, | |
+ content: Faker::Lorem.characters(number: 15), | |
+ media_attachment_ids: nil | |
+ })) | |
+ | |
+ post :create, params: { message_ids: [message['id']], account_id: account.id, comment: 'reasons' } | |
+ | |
+ reports = Report.where(account_id: account.id) | |
+ expect(response).to have_http_status(200) | |
+ expect(reports.count).to eq(1) | |
+ | |
+ expect(reports.last.message_ids).to eq([message['id'].to_i]) | |
+ end | |
+ end | |
+ | |
+ context 'group statuses' do | |
+ let(:non_member) { Fabricate(:user, account: Fabricate(:account, username: 'non_member')) } | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: non_member.id, scopes: scopes) } | |
+ | |
+ it 'creates a report for statuses in a group' do | |
+ post :create, params: { status_ids: [group_status.id], account_id: group_status.account.id, comment: 'reasons' } | |
+ | |
+ expect(group_status.account.targeted_reports).not_to be_empty | |
+ expect(group_status.account.targeted_reports.first.group_id).to eq group.id | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json[:id]).to be_an_instance_of String | |
+ expect(body_as_json[:action_taken]).to be_boolean | |
+ end | |
+ | |
+ it "should return http forbidden if it's a private group status and the user is not a member" do | |
+ group.members_only! | |
+ | |
+ post :create, params: { status_ids: [group_status.id], account_id: group_status.account.id, comment: 'reasons' } | |
+ | |
+ expect(response).to have_http_status(403) | |
+ end | |
+ end | |
+ | |
+ context 'groups' do | |
+ before do | |
+ post :create, params: { group_id: group.id, comment: 'reasons' } | |
+ end | |
+ | |
+ it 'reports a group' do | |
+ expect(account.targeted_reports.first.group_id).to eq group.id | |
+ expect(response).to have_http_status(200) | |
+ end | |
+ end | |
+ | |
+ context 'external ads' do | |
+ let(:ts_advertising_account) { Fabricate(:account, username: 'ts_advertising') } | |
+ | |
+ before do | |
+ stub_const('ENV', ENV.to_hash.merge('TS_ADVERTISTING_ACCOUNT_ID' => ts_advertising_account.id)) | |
+ post :create, params: { external_ad_url: 'https://example.com', external_ad_media_url: 'https://example.com', external_ad_description: 'Example ad' } | |
+ end | |
+ | |
+ it 'reports an external ad' do | |
+ expect(ts_advertising_account.targeted_reports.first.external_ad_id).to_not be_nil | |
+ expect(response).to have_http_status(200) | |
+ end | |
+ | |
+ it 'creates an external ad record' do | |
+ expect(ExternalAd.last.ad_url).to eq 'https://example.com' | |
+ expect(ExternalAd.last.media_url).to eq 'https://example.com' | |
+ expect(ExternalAd.last.description).to eq 'Example ad' | |
+ end | |
+ end | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -25,7 +25,7 @@ | |
it 'returns http success' do | |
get :index, params: { status_id: status.id, limit: 2 } | |
expect(response).to have_http_status(200) | |
- expect(response.headers['Link'].links.size).to eq(2) | |
+ expect(response.headers['Link'].links.size).to eq(1) | |
end | |
it 'returns accounts who favorited the status' do | |
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -5,7 +5,7 @@ | |
describe Api::V1::Statuses::FavouritesController do | |
render_views | |
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_ip: '0.0.0.0') } | |
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:favourites', application: app) } | |
@@ -17,11 +17,11 @@ | |
describe 'POST #create' do | |
let(:status) { Fabricate(:status, account: user.account) } | |
- before do | |
- post :create, params: { status_id: status.id } | |
- end | |
- | |
context 'with public status' do | |
+ before do | |
+ post :create, params: { status_id: status.id } | |
+ end | |
+ | |
it 'returns http success' do | |
expect(response).to have_http_status(200) | |
end | |
@@ -47,9 +47,136 @@ | |
let(:status) { Fabricate(:status, visibility: :private) } | |
it 'returns http not found' do | |
+ post :create, params: { status_id: status.id } | |
expect(response).to have_http_status(404) | |
end | |
end | |
+ | |
+ context 'when current_sign_in_ip is nil' do | |
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_ip: nil) } | |
+ | |
+ it 'returns http success' do | |
+ post :create, params: { status_id: status.id } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json).to be_empty | |
+ end | |
+ end | |
+ | |
+ context 'App Integrity' do | |
+ let(:date) { (Time.now.utc.to_f * 1000).to_i } | |
+ let(:alg_and_enc) { {alg: "A256KW", enc: "A256GCM"}.to_json } | |
+ let(:hashed_token) { OpenSSL::Digest.digest('SHA256', "INTEGRITY_TOKEN") } | |
+ let(:integrity_token) { Base64.encode64(alg_and_enc + hashed_token) } | |
+ let(:decoded_assertion) do | |
+ { | |
+ v: 0, | |
+ p: 2, | |
+ date: date, | |
+ integrity_token: integrity_token, | |
+ } | |
+ end | |
+ let(:x_tru_assertion) { Base64.strict_encode64(decoded_assertion.to_json) } | |
+ let(:nonce) { OpenSSL::Digest.digest('SHA256', "NONCE") } | |
+ let(:client_data) { { date: date, request: Base64.urlsafe_encode64(nonce) }.to_json } | |
+ let(:verdict_nonce) { Base64.urlsafe_encode64(OpenSSL::Digest.digest('SHA256', client_data)) } | |
+ let(:verdict) do | |
+ [ | |
+ { | |
+ "requestDetails"=> { | |
+ "requestPackageName"=> "PACKAGE_NAME", | |
+ "timestampMillis"=> "TIMESTAMP", | |
+ "nonce"=> verdict_nonce | |
+ }, | |
+ "appIntegrity"=> { | |
+ "appRecognitionVerdict"=> "UNRECOGNIZED_VERSION", | |
+ "packageName"=> "PACKAGE_NAME", | |
+ "certificateSha256Digest"=> ["DIGEST"], | |
+ "versionCode"=> "VERSION CODE" | |
+ }, | |
+ "deviceIntegrity"=> { | |
+ "deviceRecognitionVerdict"=> %w[MEETS_BASIC_INTEGRITY MEETS_DEVICE_INTEGRITY MEETS_STRONG_INTEGRITY] | |
+ }, | |
+ "accountDetails"=> { | |
+ "appLicensingVerdict"=> "LICENSED" | |
+ } | |
+ }, | |
+ { | |
+ "alg"=> "ES256" | |
+ } | |
+ ] | |
+ end | |
+ | |
+ before do | |
+ request.headers['x-tru-assertion'] = x_tru_assertion | |
+ request.headers['x-tru-date'] = date | |
+ request.user_agent = "TruthSocialAndroid/okhttp/5.0.0-alpha.7" | |
+ | |
+ allow_any_instance_of(AndroidDeviceCheck::IntegrityService).to receive(:decrypt_token).and_return(verdict) | |
+ end | |
+ | |
+ it 'should validate and store a device verification record' do | |
+ canonical_instance = instance_double(CanonicalRequestService, canonical_string: 'NONCE', canonical_headers: {}) | |
+ allow(CanonicalRequestService).to receive(:new).and_return(canonical_instance) | |
+ allow(canonical_instance).to receive(:call).and_return(nonce) | |
+ | |
+ post :create, params: { status_id: status.id } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ device_verification = DeviceVerification.find_by("details ->> 'integrity_token' = '#{integrity_token}'") | |
+ device_verification_user = DeviceVerificationUser.find_by(verification: device_verification) | |
+ expect(device_verification.details['integrity_errors']).to be_empty | |
+ expect(device_verification_user.user_id).to eq(user.id) | |
+ dvf = DeviceVerificationFavourite.find_by(verification_id: device_verification.id) | |
+ expect(dvf.favourite.status_id.to_s).to eq body_as_json[:id] | |
+ end | |
+ end | |
+ | |
+ context 'App Attest' do | |
+ let(:assertion) { { 'signature' => "SIGNATURE", 'authenticatorData' => 'AUTHENTICATOR_DATA' } } | |
+ let(:credential) { | |
+ user.webauthn_credentials.create( | |
+ nickname: 'SecurityKeyNickname', | |
+ external_id: 'EXTERNAL_ID', | |
+ public_key: "PUBLIC_KEY", | |
+ sign_count: 0 | |
+ ) | |
+ } | |
+ let(:date) { (Time.now.utc.to_f * 1000).to_i } | |
+ let(:decoded_assertion) do | |
+ { | |
+ id: credential.external_id, | |
+ v: 0, | |
+ p: 1, | |
+ date: date, | |
+ assertion: Base64.strict_encode64(assertion.to_cbor), | |
+ } | |
+ end | |
+ | |
+ | |
+ let(:assertion_response) { OpenStruct.new(authenticator_data: { sign_count: 1 }) } | |
+ let(:x_tru_assertion) { Base64.strict_encode64(decoded_assertion.to_json) } | |
+ | |
+ before do | |
+ allow(WebAuthn::AuthenticatorAssertionResponse).to receive(:new).and_return(assertion_response) | |
+ allow_any_instance_of(IosDeviceCheck::AssertionService).to receive(:valid_assertion?).and_return(true) | |
+ | |
+ request.headers['x-tru-assertion'] = x_tru_assertion | |
+ request.headers['x-tru-date'] = date | |
+ request.user_agent = "TruthSocial/83 CFNetwork/1121.2.2 Darwin/19.3.0" | |
+ end | |
+ | |
+ it 'should validate assertion and store a device verification record' do | |
+ post :create, params: { status_id: status.id } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ device_verification = DeviceVerification.find_by("details ->> 'external_id' = '#{credential.external_id}'") | |
+ device_verification_user = DeviceVerificationUser.find_by(verification: device_verification) | |
+ expect(device_verification_user.user_id).to eq(user.id) | |
+ dvf = DeviceVerificationFavourite.find_by(verification_id: device_verification.id) | |
+ expect(dvf.favourite.status_id.to_s).to eq body_as_json[:id] | |
+ end | |
+ end | |
end | |
describe 'POST #destroy' do | |
@@ -58,6 +185,7 @@ | |
before do | |
FavouriteService.new.call(user.account, status) | |
+ Procedure.process_status_favourite_statistics_queue | |
post :destroy, params: { status_id: status.id } | |
end | |
@@ -87,6 +215,7 @@ | |
before do | |
FavouriteService.new.call(user.account, status) | |
+ Procedure.process_status_favourite_statistics_queue | |
status.account.block!(user.account) | |
post :destroy, params: { status_id: status.id } | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -46,5 +46,62 @@ | |
expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil | |
end | |
end | |
+ | |
+ describe 'GET #index' do | |
+ let(:user2) { Fabricate(:user, account: Fabricate(:account, username: 'john')) } | |
+ let(:status) { Fabricate(:status, account: user.account) } | |
+ let(:status2) { Fabricate(:status, account: user.account) } | |
+ let(:status3) { Fabricate(:status, account: user.account) } | |
+ let(:status4) { Fabricate(:status, account: user2.account) } | |
+ | |
+ before do | |
+ user.account.mute_conversation!(status.conversation) | |
+ user.account.mute_conversation!(status2.conversation) | |
+ user.account.mute_conversation!(status3.conversation) | |
+ user2.account.mute_conversation!(status4.conversation) | |
+ end | |
+ | |
+ context 'with valid oauth scopes' do | |
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:mutes', application: app) } | |
+ | |
+ it 'returns http success' do | |
+ get :index, params: { limit: 3 } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json.size).to eq 3 | |
+ expect(body_as_json.pluck(:id)).to match_array [status.id.to_s, status2.id.to_s, status3.id.to_s] | |
+ expect(response.headers['Link']).to be_nil | |
+ end | |
+ | |
+ it 'returns correct link header' do | |
+ get :index, params: { limit: 2 } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json.size).to eq 2 | |
+ expect(body_as_json.pluck(:id)).to match_array [status3.id.to_s, status2.id.to_s] | |
+ expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq api_v1_mutes_url(limit: 2, offset: 2) | |
+ end | |
+ end | |
+ | |
+ context 'with invalid oauth scopes' do | |
+ it 'returns http forbidden' do | |
+ get :index | |
+ | |
+ expect(response).to have_http_status(403) | |
+ expect(body_as_json[:error]).to eq "This action is outside the authorized scopes" | |
+ end | |
+ end | |
+ | |
+ context 'without oauth token' do | |
+ it 'returns http forbidden' do | |
+ allow(controller).to receive(:doorkeeper_token) { nil } | |
+ | |
+ get :index | |
+ | |
+ expect(response).to have_http_status(403) | |
+ expect(body_as_json[:error]).to eq "Please log out and log back in." | |
+ end | |
+ end | |
+ end | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -7,7 +7,7 @@ | |
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') } | |
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') } | |
let(:alice) { Fabricate(:account) } | |
- let(:bob) { Fabricate(:account) } | |
+ let(:bob) { Fabricate(:account, user: Fabricate(:user)) } | |
context 'with an oauth token' do | |
before do | |
@@ -39,6 +39,39 @@ | |
get :index, params: { status_id: status.id, limit: 2 } | |
expect(body_as_json.size).to eq 1 | |
expect(body_as_json[0][:id]).to eq alice.id.to_s | |
+ end | |
+ | |
+ context 'with a group status' do | |
+ let!(:group) { Fabricate(:group, display_name: Faker::Lorem.characters(number: 5), note: Faker::Lorem.characters(number: 5), statuses_visibility: 'members_only', owner_account: user.account) } | |
+ | |
+ describe 'GET #index' do | |
+ before do | |
+ group.memberships.create!(account_id: user.account.id, role: :owner) | |
+ group.memberships.create!(account_id: bob.id, role: :user) | |
+ end | |
+ | |
+ it 'returns http success if user is a member of a private group' do | |
+ status = Fabricate(:status, account: user.account, visibility: :group, group: group) | |
+ Fabricate(:status, reblog_of_id: status.id, group: group, visibility: :group, account: bob) | |
+ | |
+ get :index, params: { status_id: status.id, visibility: :group } | |
+ | |
+ expect(response).to have_http_status(200) | |
+ expect(body_as_json.first[:id]).to eq bob.id.to_s | |
+ end | |
+ | |
+ it 'returns not found if user is not a member of the private group' do | |
+ status = Fabricate(:status, account: user.account, visibility: :group, group: group) | |
+ Fabricate(:status, reblog_of_id: status.id, group: group, visibility: :group, account: user.account) | |
+ group.memberships.find_by!(account_id: bob.id).destroy! | |
+ token = Fabricate(:accessible_access_token, resource_owner_id: bob.user.id, application: app, scopes: 'read:accounts') | |
+ allow(controller).to receive(:doorkeeper_token) { token } | |
+ | |
+ get :index, params: { status_id: status.id, visibility: :group } | |
+ | |
+ expect(response).to have_http_status(404) | |
+ end | |
+ end | |
end | |
end | |
end | |
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb | |
--- truth-old/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb 2022-06-08 09:15:38 | |
+++ truth-new/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb 2024-04-01 14:59:14 | |
@@ -5,9 +5,18 @@ | |
describe Api::V1::Statuses::ReblogsController do | |
render_views | |
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } | |
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_ip: '0.0.0.0') } | |
+ let(:user2) { Fabricate(:user, account: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment