Compare commits

...

491 commits
v1.5.0 ... main

Author SHA1 Message Date
Rafael Caricio d2075dcbd2
Apply clippy suggestions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-28 00:15:44 +02:00
Rafael Caricio 7281340423
Apply clippy suggestions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-28 00:01:24 +02:00
Rafael Caricio 406781570c
Fix fmt
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:35:08 +02:00
Rafael Caricio 205ea8c5b8
Fix fmt
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:30:41 +02:00
Rafael Caricio 9944095959
Fix CI
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:29:27 +02:00
Rafael Caricio 13d6fa25bc
Fix formatting
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:28:32 +02:00
Rafael Caricio 456d0789fb
Better CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:27:00 +02:00
Rafael Caricio f73a05439d
Remove println
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:24:50 +02:00
Rafael Caricio 4df257d024
Noticed the branch was wrong.. 🤦
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:24:08 +02:00
Rafael Caricio 44004de576
OK, run CI always
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:23:03 +02:00
Rafael Caricio 93be6f8559
Fix imports
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:20:59 +02:00
Rafael Caricio 3860b4780d
CI fix loop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:46:23 +02:00
Rafael Caricio de6072026b
Fix missing import in tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:45:30 +02:00
Rafael Caricio 2b44c8e20d
Run test on Rust file changes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:41:40 +02:00
Rafael Caricio d8bea2c868
Fix test db setup
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:40:28 +02:00
Rafael Caricio 5d0b03c2a3
Only run cargo when Rust code is changed
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:13:05 +02:00
Rafael Caricio a2bc297f0e
Update readme 2023-04-27 22:00:02 +02:00
Rafael Caricio fe8380e359
Test CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 21:27:05 +02:00
Rafael Caricio 22883798b3
Don't notify when muted 2023-04-27 21:26:18 +02:00
Rafael Caricio cb61f4a86b
Allow mute accounts 2023-04-27 13:38:49 +02:00
Rafael Caricio bb28bf800d
Make API response compatible with what IceCubes iOS client expects 2023-04-26 22:10:34 +02:00
Rafael Caricio 60a27b5b11
Make generic errors carry more details 2023-04-26 12:55:42 +02:00
Rafael Caricio 0d77557ad6
Simplify deployment workflow 2023-04-26 12:54:48 +02:00
Rafael Caricio 1e40a42524
Support database connection via SSL
This is required to use managed Postgres databases. It is necessary to
use SSL connection to the remote host as the connection goes through the
open internet.
2023-04-26 12:07:36 +02:00
Rafael Caricio b7fafe6458
Rename to Fedimovies 2023-04-25 15:49:35 +02:00
Rafael Caricio 17d8c11726
Retoot reviews 2023-04-25 13:19:04 +02:00
Rafael Caricio b049b75873
TMDB API integration 2023-04-25 11:09:30 +02:00
Rafael Caricio 272d06897a
In this server all accounts are bots 2023-04-24 18:57:48 +02:00
Rafael Caricio 47529ff703
Apply cargo fmt 2023-04-24 17:35:32 +02:00
silverpill b4ff7abbc1
Bump version 2023-04-24 16:59:37 +02:00
silverpill 5906185154
Add federation.i2p_proxy_url configuration parameter 2023-04-24 16:59:26 +02:00
silverpill b3b62a9c7f
Make onion_proxy_url override proxy_url setting if request target is onion 2023-04-24 16:59:13 +02:00
silverpill b77d4a9bdf
Add replies and reposts to outbox collection 2023-04-24 16:59:01 +02:00
silverpill b6e7fa5d13
Support integrity proofs with DataIntegrityProof type 2023-04-24 16:58:05 +02:00
silverpill 05eeb5ae2a
Deserialize actor attachments into Value array 2023-04-24 16:57:14 +02:00
silverpill f41b205084
Add support for content warnings 2023-04-24 16:56:59 +02:00
silverpill 1302611731
Update actix 2023-04-24 16:56:47 +02:00
silverpill 469a5484a1
Update openssl crate 2023-04-24 16:55:20 +02:00
silverpill 7471c03ed1
Make /api/v1/accounts/{account_id}/follow work with form-data 2023-04-24 16:53:18 +02:00
silverpill e2ea58d33a
Make activity limit in outbox fetcher adjustable 2023-04-24 16:51:51 +02:00
silverpill a3f44cf678
Ignore errors when importing activities from outbox 2023-04-24 16:50:36 +02:00
silverpill 69caf0b5bc
Bump version 2023-04-24 16:50:02 +02:00
silverpill 01cefa6ea1
Disable spam detection when importing activities from outbox 2023-04-24 16:49:52 +02:00
silverpill 1092319f6e
Fix database query error in Create activity handler 2023-04-24 16:49:39 +02:00
silverpill f8df50934c
Explain database client errors 2023-04-24 16:49:22 +02:00
silverpill 7f6ebb89c0
Add read-outbox command 2023-04-24 16:49:08 +02:00
silverpill c022e0d320
Add actor validation to Update(Note) and Undo(Follow) handlers 2023-04-24 16:46:49 +02:00
silverpill b6abcf252a
Remove unused query_params parameter from send_request 2023-04-24 16:46:37 +02:00
silverpill 55c0b1eb6b
Re-fetch object if attributedTo value doesn't match actor of Create activity 2023-04-24 16:46:22 +02:00
silverpill 8daf566eb2
Add create-user command 2023-04-24 16:26:31 +02:00
silverpill 533ef48393
Check mention and link counts when creating post 2023-04-24 16:15:45 +02:00
silverpill 8533a892bf
Add emoji count check to profile data validator 2023-04-24 16:15:21 +02:00
Rafael Caricio ad3ea0e7ca
When running locally with .example.com domain use http 2023-04-24 16:10:50 +02:00
Rafael Caricio 83286b7522
No blockchain support 2023-04-24 16:10:25 +02:00
Rafael Caricio c5dbb0257f
No eth support 2023-04-24 16:10:08 +02:00
Rafael Caricio c0049e6d49
Delete some more 2023-04-09 01:34:07 +02:00
Rafael Caricio e5be1326de
Nop 2023-04-08 21:30:21 +02:00
Rafael Caricio 89e3f93592
Rm web3 lolz 2023-04-08 21:29:03 +02:00
Rafael Caricio 5ef024d923
Fuck blockchain 2023-04-08 21:20:12 +02:00
silverpill cdb728a70a Bump version 2023-04-06 23:54:11 +00:00
silverpill 8708abd9cd Reject unsolicited public posts 2023-04-06 23:00:08 +00:00
silverpill fc82c83421 Create API endpoint for managing client configurations 2023-04-06 21:59:57 +00:00
silverpill 01494f1770 Increase fetcher timeout to 15 seconds when processing search queries 2023-04-06 16:49:32 +00:00
silverpill 278950252e Refactor get_object_visibility function 2023-04-06 16:14:35 +00:00
silverpill 7c38c0a4d6 Increase object ID size limit to 2000 chars 2023-04-06 16:04:09 +00:00
silverpill e950189086 Validate emoji name length before saving to database 2023-04-06 15:54:26 +00:00
silverpill 970071a9f0 Validate object ID length before saving post to database 2023-04-06 15:54:21 +00:00
silverpill 20080333d0 Add missing CHECK constraints to database tables 2023-04-05 19:50:26 +00:00
silverpill b9fdb1ccf4 Allow custom emojis with image/webp media type 2023-04-05 19:38:00 +00:00
silverpill 9768fc6228 Send Update(Person) after adding alias 2023-04-05 19:38:00 +00:00
silverpill 9e5672929b Process incoming Move() activities in background 2023-04-05 19:37:59 +00:00
silverpill dcaa2227d2 Support account migration from Mastodon 2023-04-05 19:33:58 +00:00
silverpill b0bf3cf594 Populate alsoKnownAs property on actor object with declared aliases 2023-04-05 00:08:06 +00:00
silverpill 99f6c08e9a Create API endpoint for adding aliases 2023-04-04 23:58:36 +00:00
silverpill 13df9e0478 Create /api/v1/accounts/aliases/all API endpoint 2023-04-04 23:56:38 +00:00
silverpill 59e5f12016 Support calling /api/v1/accounts/search with "resolve" parameter 2023-04-02 22:23:19 +00:00
silverpill edebae0dc6 Validate actor aliases before saving into database 2023-04-02 21:58:44 +00:00
silverpill ebbde534af Update comrak crate 2023-04-02 21:58:40 +00:00
silverpill 300d2ef6f8 Increase maximum number of custom emojis per post to 50 2023-04-01 23:24:52 +00:00
silverpill 3c2d2d124b Bump version 2023-03-31 19:26:17 +00:00
silverpill dbca8183bb Order attachments by creation date when new post is created 2023-03-31 19:23:20 +00:00
silverpill 9a32fb9c80 Remove activity from queue if handler times out 2023-03-31 17:56:13 +00:00
silverpill 779d4e7287 Process queued background jobs before re-trying stalled 2023-03-31 17:43:13 +00:00
silverpill 6604ea8a2b Limit number of mentions and links in remote posts 2023-03-31 17:05:41 +00:00
silverpill 95daa94a97 Move contents of database and models modules to mitra-models crate 2023-03-31 00:20:19 +00:00
silverpill 19780c3b8a Bump version 2023-03-30 13:56:27 +00:00
silverpill 00ca54f9b4 Update comrak to v0.17.1 2023-03-30 13:44:22 +00:00
silverpill 006665f6fb Save Monero wallet after generating addresses or sending transactions 2023-03-30 13:22:25 +00:00
silverpill 348149bbaa Set fetcher timeout to 5 seconds when processing search queries 2023-03-28 07:57:28 +00:00
silverpill dd0c53c5e9 Add federation.fetcher_timeout and federation.deliverer_timeout configuration parameters 2023-03-28 11:27:14 +04:00
silverpill 378d94e7b8 Don't reopen monero wallet on each subscription monitor run 2023-03-28 00:11:48 +00:00
silverpill 8cfb2318a2 Order attachments by creation date 2023-03-27 20:50:48 +00:00
silverpill 462da87e9b Create DbActor type and use it to represent actor_profile.actor_json column value 2023-03-27 17:43:01 +00:00
silverpill 4f9a99e6f2 Use "aliases" property in Move() activity handler 2023-03-27 02:17:59 +04:00
silverpill eb1f815548 Use manually_approves_followers field in Account::from_profile 2023-03-27 02:17:59 +04:00
silverpill b85a0fb7ac Refactor import_post function 2023-03-26 01:12:03 +00:00
silverpill 5e1f441e8b Add limits.media.emoji_size_limit configuration parameter 2023-03-25 23:11:11 +00:00
silverpill 0521f1f731 Restart stalled background jobs 2023-03-25 20:23:19 +00:00
silverpill 5ba8b8d6ae Move microsyntax parsers to mastodon_api::statuses::microsyntax module 2023-03-25 18:19:10 +00:00
silverpill ac6491d030 Set database connection pool size in mitra::main 2023-03-25 17:34:50 +00:00
silverpill 08d7482f32 Increase remote emoji size limit to 500 kB 2023-03-25 12:24:33 +00:00
silverpill 5450ba8871 Set log size limit in monero-wallet-rpc config example 2023-03-25 12:18:22 +00:00
silverpill 399a632a88 Prune remote emojis in background 2023-03-25 12:18:22 +00:00
silverpill ef852d781e Move profile data cleaning code to validators::profiles module 2023-03-25 11:28:48 +00:00
silverpill 441850dd21 Allow emoji names containing hyphens 2023-03-25 11:28:48 +00:00
silverpill 73b576c643 Move normalize_hashtag function to activitypub::handlers::create 2023-03-25 11:28:48 +00:00
silverpill f5dd0a17c9 Move all validators to validators module 2023-03-25 11:28:48 +00:00
silverpill 37ab3dc456 Add prune-remote-emojis command 2023-03-25 11:26:52 +00:00
silverpill 76e85a3b7b Fix error in emoji update SQL query 2023-03-24 22:54:51 +00:00
silverpill 521c2cbe41 Move mention_to_address function to webfinger::types module 2023-03-23 19:19:17 +00:00
silverpill 3b5c8a4131 Remove Role::from_name function 2023-03-23 00:29:32 +00:00
silverpill 21135d7704 Move get_post_by_object_id to activitypub::fetcher::helpers module 2023-03-22 23:13:31 +00:00
silverpill dae9be1388 Bump version 2023-03-21 18:45:50 +00:00
silverpill 39ab6bbb13 Don't allow migration if user doesn't have identity proofs 2023-03-21 18:14:24 +00:00
silverpill cdb304a8b7 Update profile page URL template to match mitra-web 2023-03-21 16:48:14 +00:00
silverpill 848a0685de Add configuration option that disables federation 2023-03-21 16:17:44 +00:00
silverpill 608ec096cd Add /api/v1/instance/peers API endpoint 2023-03-20 17:11:00 +00:00
silverpill 28be8dbb31 Update Monero configuration guide 2023-03-19 20:31:34 +00:00
silverpill e3ee144889 Grant delete_any_post and delete_any_profile permissions to admin role 2023-03-19 19:21:31 +00:00
silverpill b80b827fde Document valid role names for set-role command 2023-03-19 19:10:51 +00:00
silverpill fcf63ff317 Revert "Use "git" protocol to access crates.io"
This reverts commit 133e1349cf.
2023-03-19 13:54:04 +04:00
silverpill 9a513c928f Add account_index parameter to Monero configuration 2023-03-18 19:02:54 +00:00
silverpill 7640598431 Replace DbActorProfile::actor_address with ActorAddress::from_profile 2023-03-18 18:29:56 +00:00
silverpill f76438b6f8 Move DbActorProfile::actor_id function to activitypub::identifiers 2023-03-18 18:29:45 +00:00
silverpill 306fd7b75b Move DbActorProfile::actor_url function to activitypub::identifiers 2023-03-18 18:29:34 +00:00
silverpill a515af1111 Move Post::object_id function to activitypub::identifiers 2023-03-18 18:29:15 +00:00
silverpill f27b2e13eb Add webclient redirection rule for /@username routes 2023-03-18 14:09:27 +00:00
silverpill a07d7ce34a Make webclient-to-object redirects work for remote profiles and posts 2023-03-18 13:38:46 +00:00
silverpill f037a4d58c Move media deletion helper to media module 2023-03-18 11:27:16 +00:00
silverpill b56e11e81d Add "aliases" column to actor profile table
It is used to store unverified aliases,
and potentially can be used for verified aliases too.
2023-03-17 20:27:50 +00:00
silverpill c80bfccd6a Add "manually_approves_followers" column to actor_profile table 2023-03-17 19:20:42 +00:00
silverpill 0b65e7473e Move DID types to mitra-utils crate 2023-03-16 17:59:45 +00:00
silverpill 1637e38ee4 Add fep-e232 feature flag 2023-03-15 20:05:21 +00:00
silverpill 773c36f3df Bump version 2023-03-15 12:03:06 +00:00
silverpill 43b56be722 Remove verify_eip191_identity_proof and verify_ed25519_signature functions 2023-03-14 23:28:12 +00:00
silverpill 1d4bb200d2 Move identity::signatures to json_signatures module 2023-03-14 20:53:25 +00:00
silverpill 8b8f1bb678 Ignore emojis with non-unique names in remote posts 2023-03-14 18:48:36 +00:00
silverpill a3a4579e03 Log invalid emoji names 2023-03-14 18:42:21 +00:00
silverpill c7fd3ddc83 Remove hardcoded upload size limit 2023-03-14 15:17:56 +00:00
silverpill c87c5da17c Rename SignatureType to ProofType 2023-03-13 21:29:48 +00:00
silverpill d4f701332f Use numbers to represent identity proof type in database 2023-03-13 21:29:40 +00:00
silverpill 8400241ab4 Add audio/ogg and audio/x-wav to the list of supported media types 2023-03-12 20:14:58 +00:00
silverpill 43d084de8d Enable audio and video uploads 2023-03-12 20:13:53 +00:00
silverpill cb7bc04b64 Change instance_description value in config example to multiline string 2023-03-12 18:45:08 +00:00
silverpill 2b6de17fb9 Save latest ethereum block number to database instead of file 2023-03-12 17:13:38 +00:00
silverpill 0e9879bacb Remove ability to switch from Ethereum devnet to another chain without resetting subscriptions
Assuming migration is over (migration started at 2eb7ec2f64).
2023-03-12 15:53:40 +00:00
silverpill 138c1e915e Allow ! after hashtags and mentions 2023-03-11 17:12:37 +00:00
silverpill 4f64b2c615 Update Tor federation guide 2023-03-11 17:09:02 +00:00
silverpill 133e1349cf Use "git" protocol to access crates.io 2023-03-11 16:53:21 +00:00
silverpill 268707a78a Disable post tokenization feature 2023-03-11 14:40:23 +00:00
silverpill b5365099a4 Store NFT monitor state in database 2023-03-11 10:33:52 +00:00
silverpill a300a822ec Create internal_property database table 2023-03-11 10:33:48 +00:00
silverpill 161d29e00e Bump version 2023-03-08 01:42:38 +00:00
silverpill 6d1ed4571b Remove crates with vulnerabilities 2023-03-07 23:57:00 +00:00
silverpill 50f31e96fc Add federation.onion_proxy_url configuration parameter 2023-03-07 14:53:24 +00:00
silverpill 94a5f3a3cd Implement NodeInfo 2.1 2023-03-06 19:50:12 +00:00
silverpill 0817177282 Move federation client builders to activitypub::http_client module 2023-03-06 18:50:55 +00:00
silverpill f17c9d9f76 Add CLI command for viewing unreachable actors 2023-03-06 00:53:57 +00:00
silverpill 522fd5bafa Refresh emoji caches when emoji is deleted 2023-03-05 19:42:46 +00:00
silverpill 452de34780 Support audio attachments 2023-03-05 19:29:29 +00:00
silverpill ba1c694294 Add "emojis" field to Mastodon API Account entity 2023-03-05 19:09:58 +00:00
silverpill 70c2d2aa25 Create profile_emoji database table 2023-03-04 21:12:02 +00:00
silverpill 2787efc83f Use .jpg extension for files with image/jpeg media type 2023-03-04 12:40:13 +00:00
silverpill bd53e147ca Save emojis attached to actor objects 2023-03-03 23:22:04 +00:00
silverpill 4204350375 Deserialize actor tag to Vec<Value> 2023-03-03 22:11:41 +00:00
silverpill 6335e216a9 Add registration.default_role configuration option 2023-03-03 18:14:02 +00:00
silverpill 0995186bc8 Move RegistrationConfig type to mitra-config::registration module 2023-03-03 17:25:22 +00:00
silverpill d93c2ca23d Add custom emojis section to FEDERATION.md 2023-03-02 18:39:54 +00:00
silverpill 1b1e2a1521 Allow to add notes to generated invite codes 2023-03-02 17:24:32 +00:00
silverpill 721238d897 Deserialize object tag to Vec<Value> 2023-02-28 17:02:29 +00:00
silverpill 849b201ab9 Deserialize actor attachments into Vec instead of Option<Vec> 2023-02-28 00:34:23 +00:00
silverpill 8d479ac15d Update README.md 2023-02-27 13:28:34 +00:00
silverpill cc51c2c647 Bump version 2023-02-27 01:00:44 +00:00
silverpill 82b6c4e7cf Add /api/v1/accounts/{account_id}/aliases API endpoint 2023-02-26 23:18:45 +00:00
silverpill baec22272d Follow FEP-e232 links when importing post 2023-02-26 23:04:56 +00:00
silverpill 49b0011a9c Fetch missing profiles before doing follower migration 2023-02-26 20:21:21 +00:00
silverpill e02ebebe02 Process follower migration request in background 2023-02-26 20:16:43 +00:00
silverpill 62069dc011 Increase number of delivery attempts and increase intervals between them 2023-02-26 20:16:39 +00:00
silverpill c201f3ea2b Store information about failed activity deliveries in database 2023-02-26 14:49:01 +00:00
silverpill e4254e7a3d Add "error" and "error_description" fields to Mastodon API error responses 2023-02-25 23:38:42 +00:00
silverpill 971b541826 Change path of user's Atom feed to /feeds/users/{username} 2023-02-25 21:25:25 +00:00
silverpill ca2e541ff5 Store delivery statuses in delivery job data
Preparing for migration to a new delivery queue mechanism.
2023-02-25 19:28:23 +00:00
silverpill 0f3c247069 Put activities generated by CLI commands in a queue 2023-02-25 19:28:23 +00:00
silverpill 49f51f44d8 Replace magic numbers in activitypub::queues module with constants 2023-02-25 19:28:19 +00:00
silverpill f5c012769f Prefer Group actor when doing webfinger query on Lemmy server 2023-02-24 13:12:38 +00:00
silverpill f66e0b812f Make webfinger response compatible with GNU Social account lookup 2023-02-24 11:15:44 +00:00
silverpill 56e0ed8f5d Add empty spoiler_text property to Mastodon API Status object 2023-02-24 00:26:39 +00:00
silverpill bacb8c8380 Prevent delete-extraneous-posts command from removing locally-linked posts 2023-02-23 21:43:37 +00:00
silverpill 59a5f7b4fc Add Tor instance installation doc 2023-02-23 19:49:04 +00:00
silverpill 21ea50279e Rename proxy_url config parameter to federation.proxy_url 2023-02-23 18:02:58 +00:00
silverpill 0245eb59a2 Set deliverer timeout to 30 seconds 2023-02-22 21:54:08 +00:00
silverpill 872fe8fef3 Set fetcher timeout to 3 minutes 2023-02-22 21:48:19 +00:00
silverpill d0b4d77519 Enable data pruning in default config 2023-02-22 20:10:29 +00:00
silverpill 7a3ec91581 Bump version 2023-02-22 17:56:48 +00:00
silverpill 227eef2d6b Add Content-Security-Policy and X-Content-Type-Options headers to all responses 2023-02-22 17:56:47 +00:00
silverpill 6fa4ed96c8 Add proxy_set_header directive for X-Forwarded-Proto header 2023-02-22 17:56:47 +00:00
silverpill e1e9851d5c Make media URLs in Mastodon API responses relative to current origin 2023-02-22 17:56:43 +00:00
silverpill c796cddff8 Change order of parameters in some functions 2023-02-22 17:30:31 +00:00
silverpill 2b5d4562aa Make activities pass JSON-LD validation 2023-02-22 17:30:28 +00:00
silverpill 50176b00cc Make actor objects pass JSON-LD validation 2023-02-22 17:29:36 +00:00
silverpill 2f621201f8 Accept webfinger requests where "resource" is instance actor ID 2023-02-19 22:18:14 +00:00
silverpill 21054de712 Make /api/v1/statuses endpoint compatible with Mastodon clients 2023-02-19 19:38:27 +00:00
silverpill e8ea52adba Move create_auth_error_handler function to http module 2023-02-19 17:10:29 +00:00
silverpill 10f2596830 Move config parser to mitra-config crate 2023-02-19 00:39:15 +00:00
silverpill 7d3c558ede Move utils to mitra-utils crate 2023-02-18 23:12:36 +00:00
silverpill bcef9bb989 Remove database-specific code from utils::caip2 module 2023-02-18 21:53:20 +00:00
silverpill f1972be8db Add generate_random_sequence() function 2023-02-18 21:29:27 +00:00
silverpill a9cb1c6a83 Add test for make_entry function 2023-02-18 19:48:17 +00:00
silverpill 1d16fb45a5 Change /api/v1/{status_id}/context response format to match Mastodon API 2023-02-13 00:28:34 +00:00
silverpill 23b44ce0db Add /api/v1/{status_id}/thread API endpoint 2023-02-12 23:24:28 +00:00
silverpill 9fd6724819 Add test for get_thread() function 2023-02-12 23:07:30 +00:00
silverpill 6945ded963 Group imports 2023-02-12 23:07:19 +00:00
silverpill bc3184cf77 Rename new_uuid function to generate_ulid 2023-02-12 22:04:31 +00:00
silverpill 80b9032cbc Fix mitra-cli crate version 2023-02-12 21:58:54 +00:00
silverpill c461fbc268 Re-export ammonia::clean_text from utils::html 2023-02-12 21:38:22 +00:00
silverpill 2acf50fa01 Move some functions from utils::files to media module 2023-02-12 19:10:33 +00:00
silverpill 0988c0c91e Improve ethereum address validation and move validator to ethereum::utils module 2023-02-12 18:26:39 +00:00
silverpill 44d013bc5e Update links to network stats
Removing the-federation.info because it shows incorrect results.
2023-02-12 01:25:37 +00:00
silverpill fdd3a22807 Support "authorization_code" OAuth grant type 2023-02-11 23:30:51 +00:00
silverpill cf69f1394a Add OAuth authorization page 2023-02-11 23:30:46 +00:00
silverpill 2d9a43b076 Add /api/v1/apps endpoint 2023-02-11 17:14:37 +00:00
silverpill 8958dca939 Document http_cors_allowlist configuration parameter 2023-02-11 00:02:40 +00:00
silverpill 47508e7969 Update IPFS guide 2023-02-10 22:07:06 +00:00
silverpill 4ace00736b Refactor search::helpers module 2023-02-10 17:14:34 +00:00
silverpill 0b8553d0c2 Allow instance_uri configuration value to contain URI scheme 2023-02-10 11:55:05 +00:00
silverpill 4ab95055d6 Bump version 2023-02-09 20:07:39 +00:00
silverpill cbc3f7b65a Fix permission error on subscription settings update 2023-02-09 19:56:51 +00:00
silverpill 344025ae2f Move ChainId to Currency conversion code to utils::caip2 module 2023-02-08 00:25:54 +00:00
silverpill eeae9e3ad7 Remove dependency on activitypub module from config::main 2023-02-07 18:28:19 +00:00
silverpill 8289aeaf41 Move parse_caip2_chain_id() to utils::caip2 module 2023-02-07 17:30:06 +00:00
silverpill 53d012c9d0 Use header name constants in activitypub::views module 2023-02-07 12:00:22 +00:00
silverpill 239bdcf97d Process Delete() and Undo() activities in background to preserve ordering 2023-02-07 00:03:41 +00:00
silverpill 4d271353c4 Bump version 2023-02-06 00:41:46 +00:00
silverpill 5d4cfab00a Replace ConversionError with ConfigError in config module 2023-02-05 21:30:51 +00:00
silverpill 831739d215 Ignore Announce(Delete) activities 2023-02-05 21:01:12 +00:00
silverpill ad1a658806 Implement automatic pruning of empty profiles 2023-02-05 20:28:55 +00:00
silverpill 6002e58425 Implement automatic pruning of remote posts 2023-02-05 13:37:41 +00:00
silverpill 0ab374e9ea Move periodic tasks to job_queue::periodic_tasks module 2023-02-05 12:11:30 +00:00
silverpill 3f89d97a5c Rename Task enum to PeriodicTask 2023-02-05 11:41:41 +00:00
silverpill b91e6e77b5 Add limits.posts.character_limit configuration parameter 2023-02-04 22:03:37 +00:00
silverpill 151b068d97 Make maximum size of media attachment configurable 2023-02-04 22:03:32 +00:00
silverpill 79404fdc71 Don't allow read-only users to manage subscriptions 2023-02-04 22:00:03 +00:00
silverpill 09b16599d9 Don't create invoice if recipient can't accept subscription payments 2023-02-04 22:00:03 +00:00
silverpill cc43adedcf Change max body size in nginx example config to match app limit 2023-02-04 22:00:03 +00:00
silverpill 4559f74881 Use "warn" log level for delivery errors 2023-02-04 22:00:03 +00:00
silverpill 8bb786c763 Set 10 minute timeout on background job that processes incoming activities
https://codeberg.org/silverpill/mitra/issues/24
2023-02-04 22:00:02 +00:00
silverpill e784476344 Add /api/v1/custom_emojis endpoint 2023-02-04 21:59:59 +00:00
silverpill 0b442f6a2c Ignore forwarded Like activities 2023-02-02 00:48:03 +00:00
silverpill 5a3ef41277 Append attachment URL to post content if attachment is too large 2023-01-31 19:03:03 +00:00
silverpill 01c894da9d Separate object URL handling from content validation 2023-01-31 19:03:03 +00:00
silverpill bc19a524c4 Replace post attachments and other related objects when processing Update(Note) activity 2023-01-31 18:47:34 +00:00
silverpill 86beb532e2 Refactor handle_note() function 2023-01-31 14:18:46 +00:00
silverpill f142bee72b Refactor create_post() function 2023-01-31 13:56:25 +00:00
silverpill 70455e5eeb Return "202 Accepted" when activity is accepted by inbox endpoint 2023-01-29 21:13:35 +00:00
silverpill f55431f8b8 Improve some error descriptions 2023-01-29 20:04:23 +00:00
silverpill 6f38eba80e Add https://w3id.org/security/data-integrity/v1 to JSON-LD context 2023-01-28 22:43:15 +00:00
silverpill 9e9207f748 Fix formatting in FEDERATION.md 2023-01-28 20:44:43 +00:00
silverpill 08c55cc71c Use proof suites with prefix Mitra 2023-01-28 19:47:44 +00:00
silverpill f0ae82c0db Bump version 2023-01-26 19:05:41 +00:00
silverpill 385a11d6a7 Don't retry activity if fetcher recursion limit has been reached 2023-01-26 13:57:46 +00:00
silverpill 1f9669ad7c Add set-role command 2023-01-26 00:00:17 +00:00
silverpill 01f956b6ce Add configuration option for automatic assigning of "read-only user" role after registration 2023-01-26 00:00:14 +00:00
silverpill 2ea14635d2 Implement role system
https://codeberg.org/silverpill/mitra/issues/25
2023-01-25 23:59:39 +00:00
silverpill 771f45baab Add registration.type configuration option 2023-01-25 23:47:36 +00:00
silverpill b82c2f3fc6 Add missing <link rel="alternate"> element to Atom feed entries 2023-01-25 22:24:45 +00:00
silverpill 9fc87803e5 Add missing <link rel="self"> element to Atom feeds 2023-01-25 22:22:56 +00:00
silverpill 780b165a8b Improve Atom feed XML formatting 2023-01-25 22:09:32 +00:00
silverpill f52a55a387 Add approval_required and invites_enabled flags to InstanceInfo object 2023-01-25 20:30:51 +00:00
silverpill ce8f597501 Move parse_env() and parse_config() to config::loader module 2023-01-25 20:30:51 +00:00
silverpill 8dfb040b5f Refactor parse_env() function 2023-01-25 20:30:51 +00:00
silverpill 200675464e Replace config.version field with constant 2023-01-25 20:30:51 +00:00
silverpill e78e3c5102 Increase length of logged inbox response 2023-01-25 20:30:43 +00:00
silverpill fe6d99c5b1 Drop support for "blockchain" setting 2023-01-25 04:41:03 +00:00
silverpill d128918bdb Add nginx config example with alternative frontend setup 2023-01-23 22:45:16 +00:00
silverpill cdd80f207b Update readme
Removed unsupported features.
2023-01-23 21:27:28 +00:00
silverpill b2198985c8 Bump version 2023-01-23 12:53:57 +00:00
silverpill e24f01a2b5 Allow custom emojis with image/apng media type 2023-01-22 23:52:17 +00:00
silverpill 42329328ec Create workspace and move mitractl to a separate crate 2023-01-22 20:50:28 +00:00
silverpill 203582f801 Add days_before_now() utility function 2023-01-22 20:40:05 +00:00
silverpill d09770913b Replace client-side tag URLs with collection IDs 2023-01-22 15:32:23 +00:00
silverpill be67972760 Add emojis to post previews 2023-01-21 23:00:21 +00:00
silverpill 97145efad9 Make delete-emoji command accept emoji name and hostname instead of ID 2023-01-21 22:56:52 +00:00
silverpill f6026293a5 Add test for create_attachment() 2023-01-21 22:56:52 +00:00
silverpill 75579eae4f Add support for emoji shortcodes 2023-01-21 22:56:46 +00:00
silverpill e8500b982b Add import-emoji command 2023-01-21 19:52:36 +00:00
silverpill b958b8fb4c Save sizes of media attachments and other files to database 2023-01-21 19:52:32 +00:00
silverpill e3b51d0752 Validate emoji name before saving 2023-01-21 12:41:37 +00:00
silverpill 99d45ee048 Create custom type for emoji images 2023-01-20 22:11:01 +00:00
silverpill 6c6eb731f9 Use usize type for file sizes 2023-01-20 01:00:15 +00:00
silverpill c26fc9235d Group imports in some modules 2023-01-19 22:00:04 +00:00
silverpill 05f6bb6091 Bump version 2023-01-18 20:20:26 +00:00
silverpill eb825bca04 Add fallback route for well-known paths 2023-01-18 18:25:40 +00:00
silverpill 2dda00e36c Fix .well-known paths returning 400 Bad Request errors 2023-01-18 17:47:23 +00:00
silverpill 441264f34c Allow emojis with image/gif media type 2023-01-18 16:45:51 +00:00
silverpill 6d6a41d3e5 Print HTTP socket address when starting HTTP server 2023-01-18 16:28:52 +00:00
silverpill 52112996c5 Try to fix federation with GNU Social 2023-01-18 13:26:04 +00:00
silverpill 6af5b8c24d Ignore Like() activity if local post doesn't exist 2023-01-18 01:14:21 +00:00
silverpill 01f56d9ef7 Use re-exported tokio_postgres::GenericClient trait 2023-01-18 01:01:45 +00:00
silverpill 0e68ea263c Increase max emoji size to 250 kB 2023-01-17 20:47:34 +00:00
silverpill fcb6554ebb Don't download HTML pages attached by GNU Social 2023-01-17 19:16:46 +00:00
silverpill 10c38400e4 Accept actor objects where value of "attachment" property is not an array 2023-01-17 18:04:16 +00:00
silverpill 6ba8434f40 Add delete-emoji command 2023-01-17 16:47:13 +00:00
silverpill 578629f8bd Add emoji array to Status object 2023-01-17 16:47:13 +00:00
silverpill 56e75895bd Download custom emojis contained in remote posts 2023-01-17 16:47:09 +00:00
silverpill 7b8a56dd8f Support "Public" audience identifier 2023-01-17 00:51:35 +00:00
silverpill 5064afd766 Change max actor image size to 5 MB 2023-01-16 20:34:24 +00:00
silverpill 5809cffa71 Prevent delete-extraneous-posts command from deleting post if there's a recent reply or repost 2023-01-16 13:01:56 +00:00
silverpill 73145a9af6 Add test for find_extraneous_posts() 2023-01-16 12:46:00 +00:00
silverpill 3ed610969e Add new types to supported media types list 2023-01-15 02:30:00 +00:00
silverpill 4d29c83365 Validate emoji tags 2023-01-15 02:14:02 +00:00
silverpill 143879caf9 Ignore deserialization errors when parsing object tags 2023-01-15 02:07:52 +00:00
silverpill a22ae40d8e Create SimpleTag and LinkTag types 2023-01-15 01:50:44 +00:00
silverpill 41fdb8abb0 Rename activitypub::activity module to activitypub::types 2023-01-14 23:31:38 +00:00
silverpill 72637fe80c Add mediaType property to images in actor object 2023-01-14 19:39:19 +00:00
silverpill 53138ea1c7 Don't fetch HTML pages attached by GNU Social 2023-01-14 19:38:28 +00:00
silverpill 7c07cd79bc Use "mediaType" property value to determine file extension when saving downloaded media 2023-01-14 02:10:06 +00:00
silverpill 85dbb6f392 Save downloaded media as "unknown" if its media type is not supported 2023-01-14 02:08:28 +00:00
silverpill 51cb72d142 Add test for get_note_author_id 2023-01-13 01:31:45 +00:00
silverpill 5c0672884a Log actor tags at debug level 2023-01-13 01:31:45 +00:00
silverpill cbc78f9532 Support "as:Public" audience identifier 2023-01-13 01:31:45 +00:00
silverpill 5c2685c785 Remove empty module 2023-01-13 01:31:45 +00:00
silverpill 143e6c2417 Accept webfinger requests where resource is actor ID 2023-01-13 01:31:45 +00:00
silverpill 48de6218eb Add webfinger test 2023-01-13 01:31:45 +00:00
silverpill 6d4a6806f4 Add Monero payout address validation 2023-01-13 01:31:45 +00:00
silverpill 56df3d82a0 Add /api/v1/settings/import_follows API endpoint 2023-01-13 01:31:41 +00:00
silverpill 7218864563 Don't stop activity processing on invalid local mentions 2023-01-11 21:55:40 +00:00
silverpill 2385601e12 Create get_or_import_profile_by_actor_address helper function 2023-01-11 21:55:35 +00:00
silverpill 0ede2093c5 Ensure get_profile_by_remote_actor_id returns profile with actor data 2023-01-11 21:44:42 +00:00
silverpill 22cf00fd98 Propagate database errors returned by importer in search_profiles_or_import() 2023-01-11 21:26:53 +00:00
silverpill 7247441693 Don't ignore Delete(Person) verification errors if database error subtype is not NotFound 2023-01-11 21:26:53 +00:00
silverpill 4c97246e3f Validate uploaded follower lists 2023-01-11 21:26:53 +00:00
silverpill 1511b5f22b Make ACTOR_ADDRESS_RE more strict 2023-01-11 21:26:52 +00:00
silverpill af332283ed Move ActorAddress type to webfinger::types module 2023-01-11 21:26:52 +00:00
silverpill c953d66c95 Return validation error on attempt to move followers from local actor 2023-01-11 21:26:52 +00:00
silverpill 5fec0c187d Sign Move() activity with server key 2023-01-11 21:26:48 +00:00
silverpill 8d41a94b94 Rename /api/v1/accounts/move_followers to /api/v1/settings/move_followers 2023-01-09 22:10:04 +00:00
silverpill 228299c5b7 Remove prepare_signed_update_person() function 2023-01-09 16:22:47 +00:00
silverpill ab4d17a29d Bump version 2023-01-07 23:10:57 +00:00
silverpill 94d99e81c4 Support MitraJcsRsaSignature2022 and MitraJcsEip191Signature2022 signature suites 2023-01-07 23:06:23 +00:00
silverpill 65496e5260 Write values of actor tags to log 2023-01-07 20:47:42 +00:00
silverpill 44ce9a73a2 Save media types of uploaded avatar and banner images 2023-01-07 16:05:15 +00:00
silverpill 682cf09835 Store avatar and banner metadata as JSON objects 2023-01-07 12:22:33 +00:00
silverpill 65072ca3c5 Rename Image type to ActorImage 2023-01-07 12:08:15 +00:00
silverpill 6c850b0197 Move fetch_actor_images to actors::helpers module 2023-01-07 12:07:53 +00:00
silverpill f35e8d806f Refactor AccountUpdateData.into_profile_data() method 2023-01-07 13:18:58 +04:00
silverpill 1663d22b19 Add "configuration" object to response of /api/v1/instance endpoint 2023-01-06 17:10:48 +00:00
silverpill fe395480eb Set limit on number of media files that can be attached to post 2023-01-06 16:55:38 +00:00
silverpill 3b85214daa Place monero settings above ethereum settings in config example 2023-01-06 16:22:36 +00:00
silverpill 7a4be5dd5a Remove save_validated_b64_file() function 2023-01-06 16:10:26 +00:00
silverpill 7539533b69 Remove ability to upload non-images using /api/v1/media endpoint 2023-01-06 16:05:07 +00:00
silverpill 68e464c813 Don't sniff media type in save_file() 2023-01-06 15:41:09 +00:00
silverpill 5b3aa2a24b Fix federation with GNU Social 2023-01-05 19:55:17 +00:00
silverpill 5c9aa0f148 Always put outgoing activities in a queue 2023-01-04 19:36:57 +00:00
silverpill 30857868a0 Write tag value to log if tag type is Emoji 2023-01-04 17:40:17 +00:00
silverpill 498be66d8b Change actor status to "unreachable" if delivery to inbox fails 2023-01-01 21:48:23 +00:00
silverpill bd158b0a1f Use background job to deliver Create(), Announce() and Like() activities 2023-01-01 20:25:20 +00:00
silverpill 534812efa2 Implement activity delivery queue 2023-01-01 19:45:41 +00:00
silverpill b392d9164b Keep actor IDs when constructing OutgoingActivity 2023-01-01 19:44:50 +00:00
silverpill 1d234bd679 Stop generating reachability report after delivery 2023-01-01 19:40:43 +00:00
silverpill b4f68aaec8 Rename IncomingActivity type 2022-12-31 13:28:25 +00:00
silverpill 0ecf682984 Move activity queue helpers to activitypub::queues module 2022-12-31 00:07:10 +00:00
silverpill 471442a22a Set limit on a number of requests in import_post 2022-12-29 17:32:38 +00:00
silverpill 7ccd29abf2 Add /api/v1/accounts/lookup Mastodon API endpoint 2022-12-28 00:53:58 +00:00
silverpill de9bc7f35e Rewrite mention_to_address and parse_acct_uri, remove unnecessary regexp 2022-12-28 00:18:45 +00:00
silverpill 477839345c Serve web client by default on new installations 2022-12-27 21:51:05 +00:00
silverpill ad887d3136 Update recommended nginx config 2022-12-27 21:15:19 +00:00
silverpill f21259b1f8 Fix changelog 2022-12-27 21:07:34 +00:00
silverpill e7b318c761 Make post and profile page redirections work in Mastodon 2022-12-27 20:29:44 +00:00
silverpill 672564bdad Bump version
v1.8.0
2022-12-24 18:53:47 +00:00
silverpill ee52983bb9 Try to fix rand dev-dependency 2022-12-24 18:53:21 +00:00
silverpill 7494d62858 Make post/profile page redirection work when web client service is enabled 2022-12-24 17:59:40 +00:00
silverpill dea6bcad2a Serve web client if configuration option is present 2022-12-24 17:20:16 +00:00
silverpill b14338d5f0 Move frontend module to web_client::urls 2022-12-24 17:09:44 +00:00
silverpill ce5577b5c2 Add security headers to nginx config example 2022-12-23 00:50:46 +00:00
silverpill 209f520d28 Don't treat SVG files as images 2022-12-22 21:42:14 +00:00
silverpill 7b69dc9219 Use InstanceInfo.description field for HTML content
Also added description_source field for storing Markdown source.
2022-12-22 20:47:19 +00:00
silverpill bf92cebe5c Write activity ID to log if repost already exists in database 2022-12-21 19:21:21 +00:00
silverpill c4ad98126a Use media type hint when processing uploaded media attachment 2022-12-21 18:44:48 +00:00
silverpill 81c0f5e2bd Stop accepting pre-rendered bio/fields 2022-12-21 13:38:29 +00:00
silverpill a23a555a05 Add API method for generating post previews
https://codeberg.org/silverpill/mitra/issues/12
2022-12-20 22:47:52 +00:00
silverpill f7d9173cce Create parse_microsyntaxes() helper 2022-12-20 15:38:07 +00:00
silverpill 748521b5ce Refactor create_view() 2022-12-20 00:59:57 +00:00
silverpill 20e965a655 Remove PostCreateData.clean() 2022-12-20 00:59:57 +00:00
silverpill 2232bf814c Limit the number of database queries in find_linked_posts() 2022-12-20 00:59:57 +00:00
silverpill 04e74a6e05 Ignore object link if referenced post doesn't exist
Made it behave similarly to mentions.
2022-12-20 00:59:57 +00:00
silverpill f2df270a82 Remove duplicate inboxes when constructing OutgoingActivity 2022-12-20 00:59:57 +00:00
silverpill 4d31b9f9f9 Reject activities from blocked instances before verifying signature 2022-12-20 00:59:57 +00:00
silverpill a2f7140164 Fix rendering of mentions in bio 2022-12-20 00:57:45 +00:00
silverpill ee7a61833d Render markdown contained in bio and profile metadata values 2022-12-18 21:25:25 +00:00
silverpill 43a70fb93f Drop support for links property in status data 2022-12-18 17:52:46 +00:00
silverpill 0465aaf0c7 Don't auto-mention author of a linked post 2022-12-18 17:52:46 +00:00
silverpill 9872e40399 Fix clippy warnings 2022-12-18 17:52:40 +00:00
silverpill 2b8063990a Ignore mentions and hashtags inside code blocks 2022-12-18 00:38:29 +00:00
silverpill 67b1729621 Add comment about HTML headings and Pleroma 2022-12-17 23:44:00 +00:00
silverpill a105710d14 Replace yanked crates 2022-12-15 22:15:48 +00:00
silverpill 5ea069ea6b Remove some duplicated dependencies 2022-12-15 22:14:51 +00:00
silverpill d0b826d1c0 Update chrono and remove time v0.1.44 from dependencies 2022-12-15 21:46:12 +00:00
silverpill 83193beabd Update monero-rpc to v0.3.2 2022-12-15 21:46:12 +00:00
silverpill a653b48547 Update clap to version 3.2 2022-12-15 21:46:12 +00:00
silverpill febb38a048 Update ed25519 crate 2022-12-15 21:46:10 +00:00
silverpill d3bad8d38d Use HashMap::from in scheduler module 2022-12-15 19:54:55 +00:00
silverpill 533cd81aa6 Switch to rust edition 2021 2022-12-15 19:54:51 +00:00
silverpill c8ff53a14c Bumo version 2022-12-14 19:44:18 +00:00
silverpill 8e08195b44 Increase max number of allowed extra fields for remote actors to 100 2022-12-14 16:46:30 +00:00
silverpill a14c719d25 Ignore Announce(Dislike) activities 2022-12-14 16:34:30 +00:00
silverpill 68f7b046a1 Support manuallyApprovesFollowers property on actor objects 2022-12-14 09:34:45 +00:00
silverpill 53d9b2efa8 Keep posts that contain links to local posts when running delete-extraneous-posts 2022-12-14 09:34:45 +00:00
silverpill f2575a0a79 Add test for delete_post() 2022-12-14 09:34:45 +00:00
silverpill d2e2b684e3 Refactor deliver_activity_worker() and prepare for queue serialization 2022-12-14 09:34:40 +00:00
silverpill fcab5b000a Ignore Announce() activity if repost already exists but with a different object ID 2022-12-13 19:42:57 +00:00
silverpill 1b588a86e0 Handle non-nested Undo(Follow) activities 2022-12-13 01:12:42 +00:00
silverpill f2037c9516 Save activity ID when processing remote follow request 2022-12-13 00:54:38 +00:00
silverpill b0f9b3537e Add activity_id column to follow_request table 2022-12-13 00:13:55 +00:00
silverpill 8c14b18d5b Extract integrity proof before fetching actor 2022-12-12 22:54:49 +00:00
silverpill 87bd8facc9 Update monero containers 2022-12-12 22:54:48 +00:00
silverpill a85eaabf2f Process Announce() and Update() activities in background jobs 2022-12-12 22:54:43 +00:00
silverpill 50b8ad9de4 Create type for deserializing Undo() activities and remove Activity type 2022-12-12 21:15:08 +00:00
silverpill bb033e11df Create type for deserializing Delete() activities 2022-12-12 21:04:18 +00:00
silverpill 73e0b10a10 Create types for deserializing Add() and Remove() activities 2022-12-12 20:58:23 +00:00
silverpill f50609e4e5 Create type for deserializing Update(Person) activities 2022-12-12 20:58:23 +00:00
silverpill 72eabce15b Create types for deserializing Follow(), Accept() and Reject() activities 2022-12-12 20:58:20 +00:00
silverpill 1efbf5a3fb Process Create() activities in background jobs 2022-12-12 15:29:40 +00:00
silverpill 2bf16b260e Add handle_activity() function 2022-12-12 00:31:29 +00:00
silverpill c28585ef92 Implement job queue 2022-12-12 00:00:45 +00:00
silverpill e7d9733f96 Move scheduler to job_queue module 2022-12-11 21:06:44 +00:00
silverpill 7498fc9dba Move create_remote_profile() and update_remote_profile() to actors::helpers module 2022-12-11 14:57:57 +00:00
silverpill 5307a28111 Create type for deserializing Like() activities 2022-12-10 20:51:21 +00:00
silverpill 515d158448 Explain account migrations 2022-12-10 20:51:21 +00:00
silverpill 212db48d75 Add find_aliases() helper function 2022-12-10 20:51:16 +00:00
silverpill 2714378f22 Don't auto-mention author of a linked post 2022-12-10 19:01:11 +00:00
silverpill 64dbd8ff26 Accept attachments with type Video
Video attachments are used in PixelFed.
2022-12-10 19:01:11 +00:00
silverpill cc728afb7d Remove unused fields from Object and Activity types 2022-12-10 19:01:11 +00:00
silverpill f21f72b96e Create type for deserializing Move() activities 2022-12-10 19:01:04 +00:00
silverpill b7a1803715 Create type for deserializing Announce() activities 2022-12-09 23:07:19 +00:00
silverpill 04ee3e5106 Move activity parsing to handlers 2022-12-09 21:16:53 +00:00
silverpill e247941695 Reorganize handlers and remove unneeded modules 2022-12-09 20:22:48 +00:00
silverpill e6be1cde2d Refactor handle_undo_follow() function 2022-12-08 22:58:48 +00:00
silverpill 84c93fecf1 Refactor receive_activity() function 2022-12-08 22:11:15 +00:00
silverpill 999dd7e608 Don't fetch actor when verifying JSON signature on Delete(Person) activity 2022-12-08 16:31:26 +00:00
silverpill cd6968b15a Fetch actor before verifying JSON signature 2022-12-08 16:10:29 +00:00
silverpill a71c4ccc89 Refactor ActivityPub authentication helpers 2022-12-08 16:09:10 +00:00
silverpill 06e172d9fa Never ignore invalid HTTP signatures 2022-12-08 00:47:18 +00:00
silverpill d3db42ec9e Rename modules 2022-12-07 21:00:54 +00:00
silverpill e0053f19c7 Add handler function for Create() activity 2022-12-07 19:46:49 +00:00
silverpill c218d3ebce Don't match object type in receive_activity() 2022-12-06 23:59:33 +00:00
silverpill 67313dbac7 Create unified handler for Undo() activities 2022-12-06 23:53:30 +00:00
silverpill 648a217971 Create unified handler for Update() activities 2022-12-06 23:48:24 +00:00
silverpill a3c3f97759 Rename modules 2022-12-06 23:29:59 +00:00
silverpill d3819c67e6 Create dedicated types for all activities and remove create_activity() function 2022-12-06 21:07:26 +00:00
silverpill e5c1be2f93 Transform incoming events into posts
Support for Event objects from Rebased.
2022-12-06 19:54:44 +00:00
silverpill 777dd74373 Use Follow type to create embedded activity when building Undo(Follow) 2022-12-05 23:03:37 +00:00
silverpill 7c3d669e9b Use plain object ID instead of embedded activity in Accept(Follow) 2022-12-05 22:59:31 +00:00
silverpill 51318046a8 Create dedicated types for building Follow(), Accept(Follow) and Undo(Follow) activities 2022-12-05 22:53:09 +00:00
silverpill ff745cfe64 Add tests for build_undo_follow() and build_move_person() 2022-12-05 22:50:40 +00:00
silverpill 10cd778f40 Replace generic parameter in OutgoingActivity struct with Value type 2022-12-05 18:08:15 +00:00
silverpill d1939b10d5 Move DatabaseError to database module 2022-12-03 22:57:09 +00:00
silverpill 4185cbefb0 Rename Pool type to DbPool 2022-12-03 21:30:24 +00:00
silverpill 11ed4c1e48 Remove unnecessary as_str() conversions 2022-12-03 21:11:28 +00:00
silverpill 9de45d0443 Update siwe crate to version 0.4.0 2022-12-03 18:52:12 +00:00
silverpill 37658b0612 Bump version 2022-12-03 18:03:48 +00:00
silverpill 56427a4535 Catch error if media request returns 4xx or 5xx 2022-12-03 11:19:08 +00:00
silverpill 28aea5b023 Always set User-Agent header when using fetcher 2022-12-03 11:19:01 +00:00
silverpill 4ec5cf0f9b Render instance description to HTML
https://codeberg.org/silverpill/mitra-web/issues/3
2022-12-01 22:04:46 +00:00
silverpill 581e72ce65 Read HTTP signature expiration date from (expires) pseudo-header 2022-11-29 17:33:59 +00:00
silverpill 7d204ab150 Remove unknown classes during HTML sanitization 2022-11-29 16:46:00 +00:00
silverpill e83a2676dd Bump version 2022-11-27 22:04:53 +00:00
silverpill a3ec1e7b58 Verify that actor alias exists before moving local followers 2022-11-27 22:04:53 +00:00
silverpill ff7c6724a0 Accept Move() activities where object is local actor 2022-11-27 19:54:07 +00:00
silverpill df8c206cf0 Accept Move() activities coming from "target" actor 2022-11-27 19:53:57 +00:00
silverpill 73ef78f021 Search for aliases by DID when processing Move(Person) activity 2022-11-27 13:47:52 +00:00
silverpill 4a42bcd369 Add API methods for creating user-signed Move() activities 2022-11-27 13:05:21 +00:00
silverpill 73a7668d18 Allow to connect to monero-wallet-rpc when it runs without --wallet-dir option 2022-11-27 13:04:47 +00:00
silverpill 12861a98b7 Use actor ID as a hint when identifying activity signer 2022-11-27 11:15:13 +00:00
silverpill 8dfd8bf0d7 Add ImportError to AuthenticationError enum 2022-11-27 10:53:51 +00:00
silverpill 7511832fa0 Rename /signed_update API method to /send_activity 2022-11-26 21:13:37 +00:00
silverpill a6032386da Use enum to represent activity parameters during the signing process 2022-11-26 20:46:46 +00:00
silverpill fbcba1b99d Refactor send_signed_update view 2022-11-26 01:00:42 +00:00
silverpill dd268634ef Move change_password API method to /api/v1/settings/change_password 2022-11-26 00:17:01 +00:00
silverpill fde8309bb9 Add check-expired-invoice command 2022-11-25 21:04:59 +00:00
silverpill 109168b5eb Move mitractl documentation to docs/mitractl.md 2022-11-25 18:31:35 +00:00
silverpill 5ff2d19837 Add expires_at field to Invoice object
Also increase timeout to 3 hours

https://codeberg.org/silverpill/mitra/issues/23
2022-11-25 15:04:46 +00:00
silverpill 2a9794f8f7 Add API methods for exporting followers and follows 2022-11-24 21:58:00 +00:00
silverpill 262b910638 Rename instance.host() to instance.hostname() 2022-11-24 13:27:10 +00:00
silverpill 473147ed04 Move signature type check to activitypub::authentication module 2022-11-23 16:11:25 +00:00
silverpill 3b092631ca Add signature type to the output of get_json_signature function 2022-11-23 14:56:29 +00:00
silverpill 301ade36a3 Use enum to define identity proof type 2022-11-23 01:04:55 +00:00
silverpill 3f3518001d Implement Display trait for ChainId and DidPkh 2022-11-22 22:41:23 +00:00
silverpill 241351c2bf Allow value of "icon" property to be empty object 2022-11-22 19:00:29 +00:00
silverpill 5712c9099c Allow to search for profile by actor ID 2022-11-22 00:19:46 +00:00
silverpill 1edcefee6d Accept Create() activities with object type Article and Video 2022-11-21 23:24:40 +00:00
silverpill 126c04febb Include local posts in search results 2022-11-21 22:06:06 +00:00
360 changed files with 19377 additions and 15082 deletions

21
.dockerignore Normal file
View file

@ -0,0 +1,21 @@
# flyctl launch added from .gitignore
**/.env.local
**/config.yaml
target
# other things
docs/*
fedimovies-*
scripts/*
src/*
# flyctl launch added from .idea/.gitignore
# Default ignored files
.idea/shelf
.idea/workspace.xml
# Editor-based HTTP Client requests
.idea/httpRequests
# Datasource local storage ignored files
.idea/dataSources
.idea/dataSources.local.xml
fly.toml

View file

@ -13,6 +13,9 @@ indent_size = 2
[*.yaml]
indent_size = 2
[*.toml]
indent_size = 2
[*.md]
indent_size = 2
max_line_length = off

4
.gitignore vendored
View file

@ -1,5 +1,9 @@
.env.local
config.yaml
/secret/*
/files/*
!/files/.gitkeep
/build/*
!/build/.gitkeep
/target
fly.toml

63
.woodpecker.yml Normal file
View file

@ -0,0 +1,63 @@
matrix:
RUST: [stable]
pipeline:
check-formatting:
image: rust
when:
branch: [ main ]
path:
include:
- .woodpecker.yml
- src/**/*.rs
- fedimovies-cli/**/*.rs
- fedimovies-config/**/*.rs
- fedimovies-models/**/*.rs
- fedimovies-utils/**/*.rs
environment:
- CARGO_TERM_COLOR=always
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
commands:
- rustup default $RUST
- rustup component add rustfmt
- cargo fmt --all -- --check
check-style:
image: rust
when:
branch: [ main ]
path:
include:
- .woodpecker.yml
- src/**/*.rs
- fedimovies-cli/**/*.rs
- fedimovies-config/**/*.rs
- fedimovies-models/**/*.rs
- fedimovies-utils/**/*.rs
environment:
- CARGO_TERM_COLOR=always
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
commands:
- rustup default $RUST
- rustup component add clippy
- cargo clippy --all-targets --all-features -- -D warnings
run-tests:
image: rust
when:
branch: [ main ]
path:
include:
- .woodpecker.yml
- src/**/*.rs
- fedimovies-cli/**/*.rs
- fedimovies-config/**/*.rs
- fedimovies-models/**/*.rs
- fedimovies-utils/**/*.rs
environment:
- CARGO_TERM_COLOR=always
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
commands:
- rustup default $RUST
- cargo test --all -- --nocapture

388
CHANGELOG.md Normal file
View file

@ -0,0 +1,388 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [1.22.0] - 2023-04-22
### Added
- Added support for content warnings.
- Support integrity proofs with `DataIntegrityProof` type.
- Add `federation.i2p_proxy_url` configuration parameter.
### Changed
- Ignore errors when importing activities from outbox.
- Make activity limit in outbox fetcher adjustable.
- Updated actix to latest version. MSRV changed to 1.57.
- Add replies and reposts to outbox collection.
### Fixed
- Make `/api/v1/accounts/{account_id}/follow` work with form-data.
- Make `onion_proxy_url` override `proxy_url` setting if request target is onion.
## [1.21.0] - 2023-04-12
### Added
- Added `create-user` command.
- Added `read-outbox` command.
### Changed
- Added emoji count check to profile data validator.
- Check mention and link counts when creating post.
- Re-fetch object if `attributedTo` value doesn't match `actor` of `Create` activity.
- Added actor validation to `Update(Note)` and `Undo(Follow)` handlers.
### Fixed
- Fixed database query error in `Create` activity handler.
## [1.20.0] - 2023-04-07
### Added
- Support calling `/api/v1/accounts/search` with `resolve` parameter.
- Created `/api/v1/accounts/aliases/all` API endpoint.
- Created API endpoint for adding aliases.
- Populate `alsoKnownAs` property on actor object with declared aliases.
- Support account migration from Mastodon.
- Created API endpoint for managing client configurations.
- Reject unsolicited public posts.
### Changed
- Increase maximum number of custom emojis per post to 50.
- Validate actor aliases before saving into database.
- Process incoming `Move()` activities in background.
- Allow custom emojis with `image/webp` media type.
- Increase object ID size limit to 2000 chars.
- Increase fetcher timeout to 15 seconds when processing search queries.
### Fixed
- Added missing `CHECK` constraints to database tables.
- Validate object ID length before saving post to database.
- Validate emoji name length before saving to database.
## [1.19.1] - 2023-03-31
### Changed
- Limit number of mentions and links in remote posts.
### Fixed
- Process queued background jobs before re-trying stalled.
- Remove activity from queue if handler times out.
- Order attachments by creation date when new post is created.
## [1.19.0] - 2023-03-30
### Added
- Added `prune-remote-emojis` command.
- Prune remote emojis in background.
- Added `limits.media.emoji_size_limit` configuration parameter.
- Added `federation.fetcher_timeout` and `federation.deliverer_timeout` configuration parameters.
### Changed
- Allow emoji names containing hyphens.
- Increased remote emoji size limit to 500 kB.
- Set fetcher timeout to 5 seconds when processing search queries.
### Fixed
- Fixed error in emoji update SQL query.
- Restart stalled background jobs.
- Order attachments by creation date.
- Don't reopen monero wallet on each subscription monitor run.
### Security
- Updated markdown parser to latest version.
## [1.18.0] - 2023-03-21
### Added
- Added `fep-e232` feature flag (disabled by default).
- Added `account_index` parameter to Monero configuration.
- Added `/api/v1/instance/peers` API endpoint.
- Added `federation.enabled` configuration parameter that can be used to disable federation.
### Changed
- Documented valid role names for `set-role` command.
- Granted `delete_any_post` and `delete_any_profile` permissions to admin role.
- Updated profile page URL template to match mitra-web.
### Fixed
- Make webclient-to-object redirects work for remote profiles and posts.
- Added webclient redirection rule for `/@username` routes.
- Don't allow migration if user doesn't have identity proofs.
## [1.17.0] - 2023-03-15
### Added
- Enabled audio and video uploads.
- Added `audio/ogg` and `audio/x-wav` to the list of supported media types.
### Changed
- Save latest ethereum block number to database instead of file.
- Removed hardcoded upload size limit.
### Deprecated
- Reading ethereum block number from `current_block` file.
### Removed
- Disabled post tokenization (can be re-enabled with `ethereum-extras` feature).
- Removed ability to switch from Ethereum devnet to another chain without resetting subscriptions.
### Fixed
- Allow `!` after hashtags and mentions.
- Ignore emojis with non-unique names in remote posts.
## [1.16.0] - 2023-03-08
### Added
- Allow to add notes to generated invite codes.
- Added `registration.default_role` configuration option.
- Save emojis attached to actor objects.
- Added `emojis` field to Mastodon API Account entity.
- Support audio attachments.
- Added CLI command for viewing unreachable actors.
- Implemented NodeInfo 2.1.
- Added `federation.onion_proxy_url` configuration parameter (enables proxy for requests to `.onion` domains).
### Changed
- Use .jpg extension for files with image/jpeg media type.
### Deprecated
- Deprecated `default_role_read_only_user` configuration option (replaced by `registration.default_role`).
## [1.15.0] - 2023-02-27
### Added
- Set fetcher timeout to 3 minutes.
- Set deliverer timeout to 30 seconds.
- Added `federation` parameter group to configuration.
- Add empty `spoiler_text` property to Mastodon API Status object.
- Added `error` and `error_description` fields to Mastodon API error responses.
- Store information about failed activity deliveries in database.
- Added `/api/v1/accounts/{account_id}/aliases` API endpoint.
### Changed
- Put activities generated by CLI commands in a queue instead of immediately sending them.
- Changed path of user's Atom feed to `/feeds/users/{username}`.
- Increase number of delivery attempts and increase intervals between them.
### Deprecated
- Deprecated `proxy_url` configuration parameter (replaced by `federation.proxy_url`).
- Deprecated Atom feeds at `/feeds/{username}`.
- Deprecated `message` field in Mastodon API error response.
### Fixed
- Prevent `delete-extraneous-posts` command from removing locally-linked posts.
- Make webfinger response compatible with GNU Social account lookup.
- Prefer `Group` actor when doing webfinger query on Lemmy server.
- Fetch missing profiles before doing follower migration.
- Follow FEP-e232 links when importing post.
## [1.14.0] - 2023-02-22
### Added
- Added `/api/v1/apps` endpoint.
- Added OAuth authorization page.
- Support `authorization_code` OAuth grant type.
- Documented `http_cors_allowlist` configuration parameter.
- Added `/api/v1/statuses/{status_id}/thread` API endpoint (replaces `/api/v1/statuses/{status_id}/context`).
- Accept webfinger requests where `resource` is instance actor ID.
- Added `proxy_set_header X-Forwarded-Proto $scheme;` directive to nginx config example.
- Add `Content-Security-Policy` and `X-Content-Type-Options` headers to all responses.
### Changed
- Allow `instance_uri` configuration value to contain URI scheme.
- Changed `/api/v1/statuses/{status_id}/context` response format to match Mastodon API.
- Changed status code of `/api/v1/statuses` response to 200 to match Mastodon API.
- Removed `add_header` directives for `Content-Security-Policy` and `X-Content-Type-Options` headers from nginx config example.
### Deprecated
- Deprecated protocol guessing on incoming requests (use `X-Forwarded-Proto` header).
### Fixed
- Fixed actor object JSON-LD validation errors.
- Fixed activity JSON-LD validation errors.
- Make media URLs in Mastodon API responses relative to current origin.
## [1.13.1] - 2023-02-09
### Fixed
- Fixed permission error on subscription settings update.
## [1.13.0] - 2023-02-06
### Added
- Replace post attachments and other related objects when processing `Update(Note)` activity.
- Append attachment URL to post content if attachment size exceeds limit.
- Added `/api/v1/custom_emojis` endpoint.
- Added `limits` parameter group to configuration.
- Made file size limit adjustable with `limits.media.file_size_limit` configuration option.
- Added `limits.posts.character_limit` configuration parameter (replaces `post_character_limit`).
- Implemented automatic pruning of remote posts and empty profiles (disabled by default).
### Changed
- Use proof suites with prefix `Mitra`.
- Added `https://w3id.org/security/data-integrity/v1` to JSON-LD context.
- Return `202 Accepted` when activity is accepted by inbox endpoint.
- Ignore forwarded `Like` activities.
- Set 10 minute timeout on background job that processes incoming activities.
- Use "warn" log level for delivery errors.
- Don't allow read-only users to manage subscriptions.
### Deprecated
- Deprecated `post_character_limit` configuration option.
### Fixed
- Change max body size in nginx example config to match app limit.
- Don't create invoice if recipient can't accept subscription payments.
- Ignore `Announce(Delete)` activities.
## [1.12.0] - 2023-01-26
### Added
- Added `approval_required` and `invites_enabled` flags to `/api/v1/instance` endpoint response.
- Added `registration.type` configuration option (replaces `registrations_open`).
- Implemented roles & permissions.
- Added "read-only user" role.
- Added configuration option for automatic assigning of "read-only user" role after registration.
- Added `set-role` command.
### Changed
- Don't retry activity if fetcher recursion limit has been reached.
### Deprecated
- `registrations_open` configuration option.
### Removed
- Dropped support for `blockchain` configuration parameter.
### Fixed
- Added missing `<link rel="self">` element to Atom feeds.
- Added missing `<link rel="alternate">` element to Atom feed entries.
## [1.11.0] - 2023-01-23
### Added
- Save sizes of media attachments and other files to database.
- Added `import-emoji` command.
- Added support for emoji shortcodes.
- Allowed custom emojis with `image/apng` media type.
### Changed
- Make `delete-emoji` command accept emoji name and hostname instead of ID.
- Replaced client-side tag URLs with collection IDs.
### Security
- Validate emoji name before saving.
## [1.10.0] - 2023-01-18
### Added
- Added `/api/v1/settings/move_followers` API endpoint (replaces `/api/v1/accounts/move_followers`).
- Added `/api/v1/settings/import_follows` API endpoint.
- Validation of Monero subscription payout address.
- Accept webfinger requests where `resource` is actor ID.
- Adeed support for `as:Public` and `Public` audience identifiers.
- Displaying custom emojis.
### Changed
- Save downloaded media as "unknown" if its media type is not supported.
- Use `mediaType` property value to determine file extension when saving downloaded media.
- Added `mediaType` property to images in actor object.
- Prevent `delete-extraneous-posts` command from deleting post if there's a recent reply or repost.
- Changed max actor image size to 5 MB.
### Removed
- `/api/v1/accounts/move_followers` API endpoint.
### Fixed
- Don't ignore `Delete(Person)` verification errors if database error subtype is not `NotFound`.
- Don't stop activity processing on invalid local mentions.
- Accept actor objects where `attachment` property value is not an array.
- Don't download HTML pages attached by GNU Social.
- Ignore `Like()` activity if local post doesn't exist.
- Fixed `.well-known` paths returning `400 Bad Request` errors.
## [1.9.0] - 2023-01-08
### Added
- Added `/api/v1/accounts/lookup` Mastodon API endpoint.
- Implemented activity delivery queue.
- Started to keep track of unreachable actors.
- Added `configuration` object to response of `/api/v1/instance` endpoint.
- Save media types of uploaded avatar and banner images.
- Support for `MitraJcsRsaSignature2022` and `MitraJcsEip191Signature2022` signature suites.
### Changed
- Updated installation instructions, default mitra config and recommended nginx config.
- Limited the number of requests made during the processing of a thread.
- Limited the number of media files that can be attached to a post.
### Deprecated
- Deprecated `post_character_limit` property in `/api/v1/instance` response.
- Avatar and banner uploads without media type via `/api/v1/accounts/update_credentials`.
- `JcsRsaSignature2022` and `JcsEip191Signature2022` signature suites.
### Removed
- Removed ability to upload non-images using `/api/v1/media` endpoint.
### Fixed
- Fixed post and profile page redirections.
- Fixed federation with GNU Social.

View file

@ -36,3 +36,5 @@ Run `cargo clippy` to check code automatically. Try to follow the existing style
### Commits
Commits should be atomic (the tests should pass) and not too big. Commit messages should be informative.
For any notable change there should be an entry in [CHANGELOG.md](./CHANGELOG.md).

2456
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,103 +1,85 @@
[package]
name = "mitra"
version = "1.5.0"
description = "Federated micro-blogging platform and content subscription service"
name = "fedimovies"
version = "1.22.0"
description = "Movies reviews and ratings for the fediverse"
license = "AGPL-3.0"
edition = "2018"
rust-version = "1.54"
edition = "2021"
rust-version = "1.68"
publish = false
default-run = "mitra"
default-run = "fedimovies"
[workspace]
members = [
".",
"fedimovies-cli",
"fedimovies-config",
"fedimovies-models",
"fedimovies-utils",
]
default-members = [
".",
"fedimovies-cli",
"fedimovies-config",
"fedimovies-models",
"fedimovies-utils",
]
[dependencies]
fedimovies-config = { path = "fedimovies-config" }
fedimovies-models = { path = "fedimovies-models" }
fedimovies-utils = { path = "fedimovies-utils" }
# Used to handle incoming HTTP requests
actix-cors = "0.6.2"
actix-cors = "0.6.4"
actix-files = "0.6.2"
actix-web = "4.1.0"
actix-web = "4.3.1"
actix-web-httpauth = "0.8.0"
# Used for HTML sanitization
ammonia = "3.2.0"
# Used for catching errors
anyhow = "1.0.58"
# Used for working with RSA keys, HTTP signatures and file uploads
base64 = "0.13.0"
# Used to decode base58btc
bs58 = "0.4.0"
# Used for working with dates
chrono = { version = "0.4.22", features = ["serde"] }
# Used to build admin CLI tool
clap = { version = "3.1.8", default-features = false, features = ["std", "derive"] }
# Used for parsing markdown
comrak = { version = "0.15.0", default-features = false }
# Used for pooling database connections
deadpool = "0.9.2"
deadpool-postgres = { version = "0.10.2", default-features = false }
# Used to read .env files
dotenv = "0.15.0"
chrono = { version = "0.4.23", default-features = false, features = ["std", "serde"] }
# Used to work with hexadecimal strings
hex = { version = "0.4.3", features = ["serde"] }
# Used for logging
log = { version = "0.4.14", features = ["serde"] }
log = "0.4.14"
env_logger = { version = "0.9.0", default-features = false }
# Used to verify minisign signatures
ed25519-dalek = "1.0.1"
ed25519 = "=1.3.0"
blake2 = "0.10.4"
# Used to guess media type of a file
mime_guess = "2.0.3"
mime-sniffer = "0.1.2"
# Used to query Monero node
monero-rpc = "0.2.0"
ed25519 = "1.5.3"
blake2 = "0.10.5"
# Used to determine the number of CPUs on the system
num_cpus = "1.13.0"
# Used for working with regular expressions
regex = "1.6.0"
# Used to generate random numbers
rand = "0.8.4"
# Used for managing database migrations
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
# Used for making async HTTP requests
reqwest = { version = "0.11.10", features = ["json", "multipart", "socks"] }
reqwest = { version = "0.11.13", features = ["json", "multipart", "socks"] }
# Used for working with RSA keys
rsa = "0.5.0"
pem = "1.0.2"
# Used for hashing passwords
rust-argon2 = "0.8.3"
# Used for working with ethereum keys
secp256k1 = { version = "0.21.3", features = ["rand", "rand-std"] }
# Used for serialization/deserialization
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0"
# Used to create JCS representations
serde_jcs = "0.1.0"
# Used to parse config file
serde_yaml = "0.8.17"
serde_json = "1.0.89"
# Used to calculate SHA2 hashes
sha2 = "0.9.5"
# Used to verify EIP-4361 signatures
siwe = "0.3.0"
siwe = "0.4.0"
# Used for creating error types
thiserror = "1.0.37"
# Async runtime
tokio = { version = "1.17.0", features = ["macros"] }
# Used for working with Postgresql database
tokio-postgres = { version = "0.7.6", features = ["with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
postgres-types = { version = "0.2.3", features = ["derive", "with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
postgres-protocol = "0.6.4"
# Used to construct PostgreSQL queries
postgres_query = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
tokio = { version = "=1.20.4", features = ["macros"] }
# Used to work with URLs
url = "2.2.2"
# Used to generate lexicographically sortable IDs
ulid = { version = "1.0.0", features = ["uuid"] }
# Used to work with UUIDs
uuid = { version = "1.1.2", features = ["serde", "v4"] }
# Used to query ethereum node
web3 = { version = "0.18.0", default-features = false, features = ["http", "http-tls", "signing"] }
[dev-dependencies]
serial_test = "0.5.1"
fedimovies-config = { path = "fedimovies-config", features = ["test-utils"] }
fedimovies-models = { path = "fedimovies-models", features = ["test-utils"] }
fedimovies-utils = { path = "fedimovies-utils", features = ["test-utils"] }
serial_test = "0.7.0"
[features]
production = []
production = ["fedimovies-config/production"]

View file

@ -29,12 +29,67 @@ And these additional standards:
Activities are implemented in way that is compatible with Pleroma, Mastodon and other popular ActivityPub servers.
## Supported FEPs
Supported FEPs:
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-f1d5.md)
- [FEP-e232: Object Links](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-e232.md)
- [FEP-8b32: Object Integrity Proofs](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8b32.md)
## Object integrity proofs
All outgoing activities are signed with actor's key in accordance with [FEP-8b32](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8b32.md) document.
Example:
```json
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/data-integrity/v1"
],
"actor": "https://example.com/users/alice",
"cc": [],
"id": "https://example.com/objects/0185f5f8-10b5-1b69-f45e-25f06792f411",
"object": "https://example.net/users/bob/posts/141892712081205472",
"proof": {
"created": "2023-01-28T01:22:40.183273595Z",
"proofPurpose": "assertionMethod",
"proofValue": "z5djAdMSrV...",
"type": "MitraJcsRsaSignature2022",
"verificationMethod": "https://example.com/users/alice#main-key"
},
"to": [
"https://example.net/users/bob",
"https://www.w3.org/ns/activitystreams#Public"
],
"type":"Like"
}
```
### Supported proof suites
#### MitraJcsRsaSignature2022
Canonicalization algorithm: JCS
Hashing algorithm: SHA-256
Signature algorithm: RSASSA-PKCS1-v1_5
#### MitraJcsEip191Signature2022
Canonicalization algorithm: JCS
Hashing algorithm: KECCAK-256 (EIP-191)
Signature algorithm: ECDSA (EIP-191)
#### MitraJcsEd25519Signature2022
Canonicalization algorithm: JCS
Hashing algorithm: BLAKE2b-512
Signature algorithm: EdDSA
## Custom emojis
Custom emojis are implemented as described in Mastodon documentation: https://docs.joinmastodon.org/spec/activitypub/#emoji.
## Profile extensions
### Cryptocurrency addresses
@ -67,6 +122,30 @@ Supported proof types:
- EIP-191 (Ethereum personal signatures)
- [Minisign](https://jedisct1.github.io/minisign/)
[FEP-c390](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-c390.md) identity proofs are not supported yet.
## Account migrations
After registering an account its owner can upload the list of followers and start the migration process. The server then sends `Move` activity to each follower:
```json
{
"@context": [
"https://www.w3.org/ns/activitystreams"
],
"actor": "https://server2.com/users/alice",
"id": "https://server2.com/activities/00000000-0000-0000-0000-000000000001",
"object": "https://server1.com/users/alice",
"target": "https://server2.com/users/alice",
"to": [
"https://example.com/users/bob"
],
"type": "Move"
}
```
Where `object` is an ID of old account and `target` is an ID of new account. Actors identified by `object` and `target` properties must have at least one identity key in common to be considered aliases. Upon receipt of such activity, actors that follow `object` should un-follow it and follow `target` instead.
## Subscription events
Local actor profiles have `subscribers` property which points to the collection of actor's paid subscribers.

226
README.md
View file

@ -1,45 +1,39 @@
# Mitra
# FediMovies
[![status-badge](https://ci.caric.io/api/badges/FediMovies/fedimovies/status.svg)](https://ci.caric.io/FediMovies/fedimovies)
Federated micro-blogging platform and content subscription service.
Lively federated movies reviews platform.
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
Subscriptions provide a way to receive monthly payments from subscribers and to publish private content made exclusively for them.
Features:
Supported payment methods:
- Micro-blogging service (includes support for quote posts, custom emojis and more).
- Mastodon API.
- Account migrations (from one server to another). Identity can be detached from the server.
- Federation over Tor.
- [Monero](https://www.getmonero.org/get-started/what-is-monero/).
- [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens (on Ethereum and other EVM-compatible blockchains).
## Instances
Other features:
- [FediList](http://demo.fedilist.com/instance?software=fedimovies)
- [Fediverse Observer](https://fedimovies.fediverse.observer/list)
- [Sign-in with a wallet](https://eips.ethereum.org/EIPS/eip-4361).
- Donation buttons.
- Token-gated registration (can be used to verify membership in some group or to stop bots).
- Converting posts into NFTs.
- Saving posts to IPFS.
Demo instance: https://public.mitra.social/ ([invite-only](https://public.mitra.social/about))
Network stats: https://the-federation.info/mitra
Matrix chat: [#mitra:halogen.city](https://matrix.to/#/#mitra:halogen.city)
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
## Code
Server: https://codeberg.org/silverpill/mitra (this repo)
Server: https://code.caric.io/reef/reef (this repo)
Web client: https://codeberg.org/silverpill/mitra-web
Web client:
Ethereum contracts: https://codeberg.org/silverpill/mitra-contracts
## Requirements
- Rust 1.54+ (when building from source)
- Rust 1.57+ (when building from source)
- PostgreSQL 12+
- IPFS node (optional, see [guide](./docs/ipfs.md))
- Ethereum node (optional)
- Monero node and Monero wallet (optional)
Optional:
- IPFS node (see [guide](./docs/ipfs.md))
## Installation
@ -47,75 +41,61 @@ Ethereum contracts: https://codeberg.org/silverpill/mitra-contracts
Run:
```
```shell
cargo build --release --features production
```
This command will produce two binaries in `target/release` directory, `mitra` and `mitractl`.
This command will produce two binaries in `target/release` directory, `fedimovies` and `fedimoviesctl`.
Install PostgreSQL and create the database:
```sql
CREATE USER mitra WITH PASSWORD 'mitra';
CREATE DATABASE mitra OWNER mitra;
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
CREATE DATABASE fedimovies OWNER fedimovies;
```
Create configuration file by copying `contrib/mitra_config.yaml` and configure the instance. Default config file path is `/etc/mitra/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
Create configuration file by copying `contrib/fedimovies_config.yaml` and configure the instance. Default config file path is `/etc/fedimovies/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
Start Mitra:
Put any static files into the directory specified in configuration file. Building instructions for `fedimovies-web` frontend can be found at https://code.caric.io/FediMovies/fedimovies#project-setup.
```
./mitra
Start Fedimovies:
```shell
./fedimovies
```
An HTTP server will be needed to handle HTTPS requests and serve the frontend. See the example of [nginx configuration file](./contrib/mitra.nginx).
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
Building instructions for `mitra-web` frontend can be found at https://codeberg.org/silverpill/mitra-web#project-setup.
To run Mitra as a systemd service, check out the [systemd unit file example](./contrib/mitra.service).
To run Fedimovies as a systemd service, check out the [systemd unit file example](./contrib/fedimovies.service).
### Debian package
Download and install Mitra package:
Download and install Fedimovies package:
```
dpkg -i mitra.deb
```shell
dpkg -i fedimovies.deb
```
Install PostgreSQL and create the database:
```sql
CREATE USER mitra WITH PASSWORD 'mitra';
CREATE DATABASE mitra OWNER mitra;
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
CREATE DATABASE fedimovies OWNER fedimovies;
```
Open configuration file `/etc/mitra/config.yaml` and configure the instance.
Open configuration file `/etc/fedimovies/config.yaml` and configure the instance.
Start Mitra:
Start Fedimovies:
```
systemctl start mitra
```shell
systemctl start fedimovies
```
An HTTP server will be needed to handle HTTPS requests and serve the frontend. See the example of [nginx configuration file](./contrib/mitra.nginx).
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
### Monero
### Tor federation
Install Monero node or choose a [public one](https://monero.fail/).
Configure and start [monero-wallet-rpc](https://monerodocs.org/interacting/monero-wallet-rpc-reference/) daemon.
Create a wallet for your instance.
Add blockchain configuration to `blockchains` array in your configuration file.
### Ethereum
Install Ethereum client or choose a JSON-RPC API provider.
Deploy contracts on the blockchain. Instructions can be found at https://codeberg.org/silverpill/mitra-contracts.
Add blockchain configuration to `blockchains` array in your configuration file.
See [guide](./docs/onion.md).
## Development
@ -123,53 +103,45 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md)
### Start database server
```
```shell
docker-compose up -d
```
Test connection:
```
psql -h localhost -p 55432 -U mitra mitra
```
### Start Monero node and wallet server
(this step is optional)
```
docker-compose --profile monero up -d
```shell
psql -h localhost -p 55432 -U fedimovies fedimovies
```
### Run web service
Create config file, adjust settings if needed:
```
```shell
cp config.yaml.example config.yaml
```
Compile and run service:
```
```shell
cargo run
```
### Run CLI
```
cargo run --bin mitractl
```shell
cargo run --bin fedimoviesctl
```
### Run linter
```
```shell
cargo clippy
```
### Run tests
```
```shell
cargo test
```
@ -179,104 +151,16 @@ See [FEDERATION.md](./FEDERATION.md)
## Client API
### Mastodon API
Most methods are similar to Mastodon API, but Mitra is not fully compatible.
Most methods are similar to Mastodon API, but Fedimovies is not fully compatible.
[OpenAPI spec](./docs/openapi.yaml)
## CLI
Commands must be run as the same user as the web service:
`fedimoviesctl` is a command-line tool for performing instance maintenance.
```
su mitra -c "mitractl generate-invite-code"
```
### Commands
Print help:
```shell
mitractl --help
```
Generate RSA private key:
```
mitractl generate-rsa-key
```
Generate invite code:
```
mitractl generate-invite-code
```
List generated invites:
```
mitractl list-invite-codes
```
Set or change password:
```
mitractl set-password <user-id> <password>
```
Delete profile:
```
mitractl delete-profile 55a3005f-f293-4168-ab70-6ab09a879679
```
Delete post:
```
mitractl delete-post 55a3005f-f293-4168-ab70-6ab09a879679
```
Remove remote posts and media older than 30 days:
```
mitractl delete-extraneous-posts 30
```
Delete attachments that don't belong to any post:
```
mitractl delete-unused-attachments 5
```
Delete empty remote profiles:
```
mitractl delete-empty-profiles 100
```
Generate ethereum address:
```
mitractl generate-ethereum-address
```
Update synchronization starting block of Ethereum blockchain:
```shell
mitractl update-current-block 2000000
```
Create Monero wallet:
```shell
mitractl create-monero-wallet "mitra-wallet" "passw0rd"
```
[Documentation](./docs/fedimoviesctl.md)
## License
[AGPL-3.0](./LICENSE)
## Support
Monero: 8Ahza5RM4JQgtdqvpcF1U628NN5Q87eryXQad3Fy581YWTZU8o3EMbtScuioQZSkyNNEEE1Lkj2cSbG4VnVYCW5L1N4os5p

0
build/.gitkeep Normal file
View file

View file

@ -1,37 +1,18 @@
database_url: postgres://mitra:mitra@127.0.0.1:55432/mitra
storage_dir: files
web_client_dir: null
http_host: '127.0.0.1'
http_port: 8380
instance_uri: 127.0.0.1:8380
instance_uri: http://127.0.0.1:8380
instance_title: Mitra
instance_short_description: My instance
instance_description: My instance
registrations_open: true
blockchains:
# Parameters for hardhat local node
- chain_id: eip155:31337
chain_metadata:
chain_name: localhost
currency_name: ETH
currency_symbol: ETH
currency_decimals: 18
public_api_url: 'http://127.0.0.1:8546'
explorer_url: null
contract_address: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
contract_dir: contracts
api_url: 'http://127.0.0.1:8546'
signing_key: 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
chain_sync_step: 100
chain_reorg_max_depth: 0
# # Parameters for local Monero node
# - chain_id: monero:regtest
# node_url: 'http://127.0.0.1:58081'
# wallet_url: 'http://127.0.0.1:58083'
# wallet_name: test
# wallet_password: test
registration:
type: open
ipfs_api_url: 'http://127.0.0.1:5001'
ipfs_gateway_url: 'http://127.0.0.1:8001'

View file

@ -1,30 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IERC165",
"sourceName": "@openzeppelin/contracts/utils/introspection/IERC165.sol",
"abi": [
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -1,233 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IERC20Metadata",
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -1,341 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IERC721Metadata",
"sourceName": "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "_approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -1,30 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IGate",
"sourceName": "contracts/interfaces/IGate.sol",
"abi": [
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "isAllowedUser",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -1,57 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "IMinter",
"sourceName": "contracts/interfaces/IMinter.sol",
"abi": [
{
"inputs": [],
"name": "collectible",
"outputs": [
{
"internalType": "contract IERC721Metadata",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "string",
"name": "tokenURI",
"type": "string"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -1,87 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "ISubscription",
"sourceName": "contracts/interfaces/ISubscription.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "expires_at",
"type": "uint256"
}
],
"name": "UpdateSubscription",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "cancel",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "send",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "withdrawReceived",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdrawReceivedAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View file

@ -1,137 +0,0 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "ISubscriptionAdapter",
"sourceName": "contracts/interfaces/ISubscriptionAdapter.sol",
"abi": [
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "configureSubscription",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "getSubscriptionPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "getSubscriptionState",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "isSubscriptionConfigured",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "subscription",
"outputs": [
{
"internalType": "contract ISubscription",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "subscriptionToken",
"outputs": [
{
"internalType": "contract IERC20Metadata",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

16
contrib/Dockerfile Normal file
View file

@ -0,0 +1,16 @@
FROM ubuntu:23.04
RUN apt-get update && apt-get install -y \
curl \
wget \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /var/lib/data
COPY build/fedimovies /usr/local/bin
COPY build/fedimoviesctl /usr/local/bin
COPY secret/fedimovies.conf /etc/fedimovies.conf
COPY files /www/frontend/
CMD ["/usr/local/bin/fedimovies"]

View file

@ -0,0 +1,54 @@
server {
server_name example.tld;
listen 80;
listen [::]:80;
location / {
return 301 https://$server_name$request_uri;
}
}
server {
server_name example.tld;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_prefer_server_ciphers off;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000" always;
# CSP header can't be added in location block
add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; img-src 'self' data:; media-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'nonce-oauth-authorization'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'";
add_header X-Content-Type-Options "nosniff";
client_max_body_size 40M;
location / {
# Frontend
root /usr/share/mitra/www;
try_files $uri /index.html;
}
location ~ ^/(actor|api|collections|contracts|feeds|media|nodeinfo|oauth|objects|users|.well-known) {
# Backend
proxy_pass http://127.0.0.1:8383;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
}
}

View file

@ -10,7 +10,7 @@ server {
}
server {
server_name example.tld;;
server_name example.tld;
listen 443 ssl http2;
listen [::]:443 ssl http2;
@ -30,16 +30,14 @@ server {
ssl_stapling on;
ssl_stapling_verify on;
location / {
# Frontend
root /usr/share/mitra/www;
try_files $uri /index.html;
}
add_header Strict-Transport-Security "max-age=63072000" always;
location ~ ^/(actor|api|contracts|feeds|media|nodeinfo|oauth|objects|users|.well-known) {
# Backend
client_max_body_size 40M;
location / {
proxy_pass http://127.0.0.1:8383;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
}
}

View file

@ -2,59 +2,60 @@
database_url: postgres://mitra:mitra@127.0.0.1:5432/mitra
storage_dir: /var/lib/mitra
# Path to web client static files
web_client_dir: /usr/share/mitra/www
http_host: '127.0.0.1'
http_port: 8383
# List of allowed origins for CORS (in addition to main)
#http_cors_allowlist: []
# Log level (debug, info, warn)
#log_level: info
# Domain name
instance_uri: example.tld
instance_uri: https://example.tld
instance_title: example
instance_short_description: my instance
# Long description can contain markdown syntax
instance_description: my instance
instance_description: |
# My instance
Welcome!
registrations_open: false
registration:
# Possible values: open, invite
type: invite
# Possible values: user, read_only_user
default_role: user
# EIP-4361 login message
#login_message: 'Do not sign this message on other sites!'
#post_character_limit: 2000
# Limits
#limits:
# media:
# file_size_limit: 20M
# posts:
# character_limit: 2000
# Proxy for outgoing requests
#proxy_url: 'socks5h://127.0.0.1:9050'
# Data retention parameters
retention:
extraneous_posts: 50
empty_profiles: 150
# Federation parameters
#federation:
# enabled: true
# # Proxy for outgoing requests
# #proxy_url: 'socks5h://127.0.0.1:9050'
# # Proxy for outgoing requests to .onion targets
# #onion_proxy_url: 'socks5h://127.0.0.1:9050'
# List of blocked domains
#blocked_instances: []
# Blockchain integrations
# Multiple configuration are currently not allowed.
# Chain metadata for EVM chains can be found at https://github.com/ethereum-lists/chains
# Signing key for ethereum integration can be generated with `mitractl generate-ethereum-address`
#blockchains:
# - chain_id: eip155:31337
# chain_metadata:
# chain_name: localhost
# currency_name: ETH
# currency_symbol: ETH
# currency_decimals: 18
# public_api_url: 'http://127.0.0.1:8545'
# explorer_url: null
# contract_address: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
# contract_dir: /usr/share/mitra/contracts
# api_url: 'http://127.0.0.1:8545'
# signing_key: null
# chain_sync_step: 1000
# chain_reorg_max_depth: 10
# - chain_id: monero:mainnet
# node_url: 'http://opennode.xmr-tw.org:18089'
# wallet_url: 'http://127.0.0.1:18083'
# wallet_name: mitra
# wallet_password: mitra
# IPFS integration
#ipfs_api_url: 'http://127.0.0.1:5001'
# IPFS gateway (for clients)

View file

@ -17,7 +17,7 @@ services:
monerod:
profiles:
- monero
image: ghcr.io/farcaster-project/containers/monerod:0.18.0.0
image: ghcr.io/farcaster-project/containers/monerod:0.18.1.2
environment:
NETWORK: regtest
OFFLINE: --offline
@ -27,7 +27,7 @@ services:
monero-wallet-rpc:
profiles:
- monero
image: ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.18.0.0
image: ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.18.1.2
environment:
MONERO_DAEMON_ADDRESS: monerod:18081
WALLET_RPC_PORT: 18083

View file

@ -2,7 +2,7 @@
This guide explains how to run IPFS node in resource-constrained environment (such as cheap VPS or single-board computer).
The recommended IPFS implementation is [go-ipfs](https://github.com/ipfs/go-ipfs), version 0.12 or higher. Normally go-ipfs requires at least 2 GB RAM, but after tweaking it can run on a machine with only 512 MB.
The recommended IPFS implementation is [kubo](https://github.com/ipfs/kubo), version 0.18.1 or higher. Normally **kubo** requires at least 2 GB RAM, but after tweaking it can run on a machine with only 512 MB.
## Configuration profiles
@ -14,24 +14,26 @@ ipfs init --profile server
If you're running it on single-board computer, the recommended profile is `lowpower`.
Documentation on configuration profiles: https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#profiles.
Documentation on configuration profiles: https://github.com/ipfs/kubo/blob/master/docs/config.md#profiles
## Configuration options
- `Datastore.StorageMax`. Recommended value is `1G`.
- `Gateway.NoFetch`. Configures gateway to not fetch files from the network. Recommended value is `true`.
- `RelayService.Enabled`. Enables providing p2p relay service to other peers on the network. Recommended value is `false`.
- `Routing.Type`. Should be set to `dht` otherwise the node will not respond to requests from other peers.
- `Swarm.ConnMgr.LowWater`. Recommended value is `10`.
- `Swarm.ConnMgr.HighWater`. Recommended value is `20`.
- `Swarm.ConnMgr.GracePeriod`. Recommended value is `15s`.
- `Swarm.DisableBandwidthMetrics`. Disabling bandwidth metrics can slightly improve performance. Recommended value is `true`.
- `Swarm.RelayService.Enabled`. Enables providing p2p relay service to other peers on the network. Recommended value is `false`.
- `Swarm.ResourceMgr.Enabled`. Enables the libp2p Resource Manager. Recommended value is `true`.
- `Swarm.ResourceMgr.MaxMemory`. Recommended value is `150MB`.
Documentation: https://github.com/ipfs/go-ipfs/blob/master/docs/config.md
Documentation: https://github.com/ipfs/kubo/blob/master/docs/config.md
## Systemd service
When go-ipfs starts, its memory usage is around 100 MB and then it slowly increases. To keep memory usage within reasonable bounds the service needs to be restarted regularly.
When **kubo** starts, its memory usage is around 100 MB and then it slowly increases, often beyond the Resource Manager's limit. To keep memory usage within reasonable bounds the service needs to be restarted regularly.
This can be achieved by using systemd process supervison features:
@ -44,8 +46,8 @@ ExecStart=/usr/local/bin/ipfs daemon
User=ipfs
Group=ipfs
# Terminate service every 20 minutes and restart automatically
RuntimeMaxSec=1200
# Terminate service every 30 minutes and restart automatically
RuntimeMaxSec=1800
Restart=on-failure
# Specify the absolute limit on memory usage

123
docs/mitractl.md Normal file
View file

@ -0,0 +1,123 @@
# mitractl: a tool for instance administrators
Commands must be run as the same user as the web service:
```shell
su mitra -c "mitractl generate-invite-code"
```
---
Print help:
```shell
mitractl --help
```
Generate RSA private key:
```shell
mitractl generate-rsa-key
```
Generate invite code (note is optional):
```shell
mitractl generate-invite-code <note>
```
List generated invites:
```shell
mitractl list-invite-codes
```
Create user:
```shell
mitractl create-user <username> <password> <role-name>
```
Set or change password:
```shell
mitractl set-password <user-id> <password>
```
Change user's role (admin, user or read_only_user).
```shell
mitractl set-role <user-id> <role-name>
```
Delete profile:
```shell
mitractl delete-profile 55a3005f-f293-4168-ab70-6ab09a879679
```
Delete post:
```shell
mitractl delete-post 55a3005f-f293-4168-ab70-6ab09a879679
```
Delete custom emoji:
```shell
mitractl delete-emoji emoji_name example.org
```
Remove remote posts and media older than 30 days:
```shell
mitractl delete-extraneous-posts 30
```
Delete attachments that don't belong to any post:
```shell
mitractl delete-unused-attachments 5
```
Delete empty remote profiles:
```shell
mitractl delete-empty-profiles 100
```
Delete unused remote emojis:
```shell
mitractl prune-remote-emojis
```
Import custom emoji from another instance:
```shell
mitractl import-emoji emoji_name example.org
```
Generate ethereum address:
```shell
mitractl generate-ethereum-address
```
Update synchronization starting block of Ethereum blockchain:
```shell
mitractl update-current-block 2000000
```
Create Monero wallet:
```shell
mitractl create-monero-wallet "mitra-wallet" "passw0rd"
```
Check expired invoice:
```shell
mitractl check-expired-invoice 0184b062-d8d5-cbf1-a71b-6d1aafbae2ab
```

42
docs/onion.md Normal file
View file

@ -0,0 +1,42 @@
# Tor federation
## Tor-only instance
Install Tor.
Install Mitra. Uncomment or add the following block to Mitra configuration file:
```yaml
federation:
proxy_url: 'socks5h://127.0.0.1:9050'
```
Where `127.0.0.1:9050` is the address and the port where Tor proxy is listening.
Configure the onion service by adding these lines to `torrc` configuration file:
```
HiddenServiceDir /var/lib/tor/mitra/
HiddenServicePort 80 127.0.0.1:8383
```
Where `8383` should correspond to `http_port` setting in Mitra configuration file.
Restart the Tor service. Inside the `HiddenServiceDir` directory find the `hostname` file. This file contains the hostname of your onion service. Change the value of `instance_uri` parameter in Mitra configuration file to that hostname (it should end with `.onion`).
Start Mitra.
For more information about running onion services, visit https://community.torproject.org/onion-services/setup/
## Clearnet + Tor
Clearnet instances can federate with Tor-only instances.
Add the following block to Mitra configuration file:
```yaml
federation:
onion_proxy_url: 'socks5h://127.0.0.1:9050'
```
Where `127.0.0.1:9050` is the address and the port where Tor proxy is listening.

View file

@ -18,9 +18,15 @@ paths:
grant_type:
type: string
enum:
- authorization_code
- password
- eip4361
example: eip4361
code:
description: A user authorization code, obtained via GET /oauth/authorize (required if grant type is "authorization_code").
type: string
nullable: true
example: null
username:
description: User name (required if grant type is "password").
type: string
@ -114,7 +120,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/AccountWithSource'
$ref: '#/components/schemas/CredentialAccount'
400:
description: Invalid user data
/api/v1/accounts/verify_credentials:
@ -128,7 +134,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/AccountWithSource'
$ref: '#/components/schemas/CredentialAccount'
/api/v1/accounts/update_credentials:
patch:
summary: Update the user's display and preferences.
@ -145,41 +151,47 @@ paths:
type: string
nullable: true
note:
description: The account bio.
description: The account bio (markdown).
type: string
nullable: true
avatar:
description: Avatar image encoded as base64.
type: string
nullable: true
avatar_media_type:
description: The media type of avatar image.
type: string
nullable: true
header:
description: Header image encoded as base64.
type: string
nullable: true
header_media_type:
description: The media type of header image.
type: string
nullable: true
fields_attributes:
description: The profile fields to be set.
type: array
nullable: true
items:
type: object
properties:
name:
description: Name of the field.
type: string
value:
description: Value of the field (markdown).
type: string
responses:
200:
description: Successful operation.
content:
application/json:
schema:
$ref: '#/components/schemas/AccountWithSource'
$ref: '#/components/schemas/CredentialAccount'
400:
description: Invalid user data.
/api/v1/accounts/change_password:
post:
summary: Set or change user's password.
security:
- tokenAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
new_password:
description: New password.
type: string
responses:
200:
description: Successful operation.
content:
application/json:
schema:
$ref: '#/components/schemas/AccountWithSource'
400:
description: Invalid request data.
/api/v1/accounts/signed_update:
get:
summary: Build Update(Person) activity for signing (experimental).
@ -191,14 +203,14 @@ paths:
schema:
type: object
properties:
internal_activity_id:
description: Internal activity ID.
type: string
format: uuid
activity:
params:
description: Activity parameters
$ref: '#/components/schemas/ActivityParameters'
message:
description: Canonical representation of activity.
type: string
example: '{"type":"Update"}'
/api/v1/accounts/send_activity:
post:
summary: Send signed activity (experimental).
requestBody:
@ -207,10 +219,9 @@ paths:
schema:
type: object
properties:
internal_activity_id:
description: Internal activity ID.
type: string
format: uuid
params:
description: Activity parameters
$ref: '#/components/schemas/ActivityParameters'
signer:
description: Signer's identifier (DID)
type: string
@ -312,6 +323,25 @@ paths:
type: array
items:
$ref: '#/components/schemas/Relationship'
/api/v1/accounts/lookup:
get:
summary: Lookup webfinger address.
parameters:
- name: acct
in: query
description: The username or Webfinger address to lookup.
required: true
schema:
type: string
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
404:
description: Profile not found
/api/v1/accounts/search:
get:
summary: Search for matching profiles by username.
@ -322,6 +352,12 @@ paths:
required: true
schema:
type: string
- name: resolve
in: query
description: Attempt WebFinger lookup.
required: false
schema:
type: boolean
- name: limit
in: query
description: Maximum number of results. Defaults to 40.
@ -560,6 +596,91 @@ paths:
$ref: '#/components/schemas/Relationship'
404:
description: Profile not found
/api/v1/accounts/{account_id}/aliases:
get:
summary: Get actor's verified aliases.
parameters:
- $ref: '#/components/parameters/account_id'
responses:
200:
description: Successful operation
content:
application/json:
schema:
description: Profile list
type: array
items:
$ref: '#/components/schemas/Account'
404:
description: Profile not found
/api/v1/accounts/{account_id}/aliases/all:
get:
summary: Get actor's aliases.
parameters:
- $ref: '#/components/parameters/account_id'
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Aliases'
404:
description: Profile not found
/api/v1/apps:
post:
summary: Create a new application to obtain OAuth2 credentials.
requestBody:
content:
application/json:
schema:
type: object
properties:
client_name:
description: A name for your application.
type: string
redirect_uris:
description: Where the user should be redirected after authorization.
type: string
scopes:
description: Space separated list of scopes.
type: string
example: 'read write'
website:
description: An URL to the homepage of your app.
type: string
nullable: true
responses:
200:
description: Successful operation
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Application'
- type: object
properties:
client_id:
description: Client ID key, to be used for obtaining OAuth tokens.
type: string
client_secret:
description: Client secret key, to be used for obtaining OAuth tokens.
type: string
400:
description: Invalid request data.
/api/v1/custom_emojis:
get:
summary: Returns custom emojis that are available on the server.
responses:
200:
description: Successful operation
content:
application/json:
schema:
description: Emoji list
type: array
items:
$ref: '#/components/schemas/CustomEmoji'
/api/v1/directory:
get:
summary: List profiles visible in the directory.
@ -606,6 +727,32 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Instance'
/api/v1/media:
post:
summary: Create an attachment to be used with a new post.
security:
- tokenAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
file:
description: File encoded as base64.
type: string
media_type:
description: Media type.
type: string
nullable: true
example: image/jpeg
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Attachment'
/api/v1/notifications:
get:
summary: Notifications concerning the user.
@ -633,6 +780,151 @@ paths:
type: array
items:
$ref: '#/components/schemas/Notification'
/api/v1/settings/client_config:
post:
summary: Update client configuration.
security:
- tokenAuth: []
requestBody:
content:
application/json:
schema:
description: |
Client configuration.
Should contain a single key identifying type of client.
type: object
example: {"mitra-web":{"theme":"dark"}}
responses:
200:
description: Successful operation.
content:
application/json:
schema:
$ref: '#/components/schemas/CredentialAccount'
400:
description: Invalid request data.
/api/v1/settings/change_password:
post:
summary: Set or change user's password.
security:
- tokenAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
new_password:
description: New password.
type: string
responses:
200:
description: Successful operation.
content:
application/json:
schema:
$ref: '#/components/schemas/CredentialAccount'
400:
description: Invalid request data.
/api/v1/settings/aliases:
post:
summary: Add alias (not verified).
requestBody:
content:
application/json:
schema:
type: object
properties:
acct:
description: Actor address.
type: string
example: user@example.com
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Aliases'
404:
description: Profile not found.
/api/v1/settings/export_followers:
get:
summary: Export followers to CSV file
security:
- tokenAuth: []
responses:
200:
description: Successful operation
content:
text/csv:
schema:
type: string
example: |
user1@example.org
user2@example.org
/api/v1/settings/export_follows:
get:
summary: Export follows to CSV file
security:
- tokenAuth: []
responses:
200:
description: Successful operation
content:
text/csv:
schema:
type: string
example: |
user1@example.org
user2@example.org
/api/v1/settings/import_follows:
post:
summary: Import follows from CSV file.
security:
- tokenAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
follows_csv:
description: The list of followers in CSV format.
type: string
responses:
204:
description: Successful operation
400:
description: Invalid data.
/api/v1/settings/move_followers:
post:
summary: Move followers from remote alias.
requestBody:
content:
application/json:
schema:
type: object
properties:
from_actor_id:
description: The actor ID to move from.
type: string
example: 'https://xyz.com/users/test'
followers_csv:
description: The list of followers in CSV format.
type: string
example: |
user1@example.org
user2@test.com
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/CredentialAccount'
400:
description: Invalid data.
/api/v1/statuses:
post:
summary: Create new post.
@ -645,9 +937,7 @@ paths:
type: object
properties:
status:
description: |
Text content of the post.
Allowed HTML tags: a, br, pre, code, strong, em, p.
description: Text content of the post.
type: string
content_type:
description: Media type of the post content.
@ -669,6 +959,10 @@ paths:
visibility:
description: Visibility of the post.
$ref: '#/components/schemas/Visibility'
sensitiive:
description: Mark post and attached media as sensitive?
type: boolean
default: false
mentions:
description: Array of profile IDs to be mentioned
type: array
@ -678,7 +972,7 @@ paths:
required:
- status
responses:
201:
200:
description: Post created
content:
application/json:
@ -686,6 +980,42 @@ paths:
$ref: '#/components/schemas/Status'
400:
description: Invalid post data
/api/v1/statuses/preview:
post:
summary: Preview new post.
security:
- tokenAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
status:
description: Text content of the post.
type: string
content_type:
description: Media type of the post content.
type: string
default: text/html
enum:
- text/html
- text/markdown
required:
- status
responses:
200:
description: Preview generated.
content:
application/json:
schema:
type: object
properties:
content:
description: HTML-encoded post content.
type: string
400:
description: Invalid post data.
/api/v1/statuses/{status_id}:
delete:
summary: Delete post
@ -699,6 +1029,47 @@ paths:
description: Post does not belong to user
404:
description: Post not found
/api/v1/statuses/{status_id}/context:
get:
summary: View statuses above and below this status in the thread.
parameters:
- $ref: '#/components/parameters/status_id'
responses:
200:
description: Successful operation.
content:
application/json:
schema:
type: object
properties:
ancestors:
description: Parents in the thread.
type: array
items:
$ref: '#/components/schemas/Status'
descendants:
description: Parents in the thread.
type: array
items:
$ref: '#/components/schemas/Status'
404:
description: Post not found
/api/v1/statuses/{status_id}/thread:
get:
summary: Get thread that contains given post.
parameters:
- $ref: '#/components/parameters/status_id'
responses:
200:
description: Successful operation.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Status'
404:
description: Post not found
/api/v1/statuses/{status_id}/favourite:
post:
summary: Add post to your favourites list
@ -898,7 +1269,7 @@ paths:
expires_at:
description: The date when subscription expires.
type: string
format: dateTime
format: date-time
404:
description: Subscription not found
/api/v1/subscriptions/invoices:
@ -1091,6 +1462,10 @@ components:
description: The location of the user's profile page.
type: string
example: https://example.com/@user
locked:
description: Whether the actor manually approves follow requests.
type: boolean
example: false
identity_proofs:
description: Identity proofs.
type: array
@ -1127,6 +1502,11 @@ components:
type: array
items:
$ref: '#/components/schemas/Field'
emojis:
description: Custom emoji entities to be used when rendering the profile.
type: array
items:
$ref: '#/components/schemas/CustomEmoji'
followers_count:
description: The reported followers of this profile.
type: number
@ -1136,7 +1516,7 @@ components:
subscribers_count:
description: The reported subscribers of this profile.
type: number
AccountWithSource:
CredentialAccount:
allOf:
- $ref: '#/components/schemas/Account'
- type: object
@ -1148,6 +1528,47 @@ components:
note:
description: Profile bio.
type: string
role:
description: The role assigned to the currently authorized user.
$ref: '#/components/schemas/Role'
client_config:
description: Client configurations.
type: object
example: {"mitra-web":{"theme":"dark"}}
ActivityParameters:
type: object
properties:
type:
description: Activity type
type: string
enum:
- update
Aliases:
type: object
properties:
declared:
description: Aliases declared by user.
type: array
items:
$ref: '#/components/schemas/Account'
verified:
description: Cryptographically verified aliases.
type: array
items:
$ref: '#/components/schemas/Account'
Application:
type: object
properties:
name:
description: The name of your application.
type: string
website:
description: The website associated with your application.
type: string
nullable: true
redirect_uri:
description: Where the user should be redirected after authorization.
type: string
Attachment:
type: object
properties:
@ -1162,9 +1583,25 @@ components:
- unknown
- image
- video
- audio
url:
description: The location of the original full-size attachment.
type: string
CustomEmoji:
type: object
properties:
shortcode:
description: The name of the custom emoji.
type: string
url:
description: A link to the custom emoji.
type: string
static_url:
description: A link to a static copy of the custom emoji.
type: string
visible_in_picker:
description: Whether this Emoji should be visible in the picker or unlisted.
type: boolean
Field:
type: object
properties:
@ -1177,7 +1614,7 @@ components:
verified_at:
description: Timestamp of when the server verified the field value.
type: string
format: dateTime
format: date-time
Instance:
type: object
properties:
@ -1191,7 +1628,10 @@ components:
description: A short description defined by the admin.
type: string
description:
description: Admin-defined description of the site.
description: Admin-defined description of the site (HTML).
type: string
description_source:
description: Admin-defined description of the site (Markdown source).
type: string
version:
description: Mastodon API compatibility version and the version of Mitra server.
@ -1200,6 +1640,12 @@ components:
registrations:
description: Whether registrations are enabled.
type: boolean
approval_required:
description: Whether registrations require moderator approval.
type: boolean
invites_enabled:
description: Whether invites are enabled.
type: boolean
stats:
description: Statistics about how much information the instance contains.
type: object
@ -1213,6 +1659,36 @@ components:
domain_count:
description: Domains federated with this instance.
type: integer
configuration:
description: Configured values and limits for this instance.
type: object
properties:
statuses:
description: Limits related to authoring posts.
type: object
properties:
max_characters:
description: The maximum number of allowed characters per post.
type: integer
example: 5000
max_media_attachments:
description: The maximum number of media attachments that can be added to a post.
type: integer
example: 15
media_attachments:
description: Limits realted to uploading attachments.
type: object
properties:
supported_mime_types:
description: Contains MIME types that can be uploaded.
type: array
items:
type: string
example: 'image/png'
image_size_limit:
description: The maximum size of any uploaded image, in bytes.
type: integer
example: 5242880
login_message:
description: Login message for signer.
type: string
@ -1288,6 +1764,10 @@ components:
- paid
- forwarded
- timeout
expires_at:
description: The date when invoice times out.
type: string
format: date-time
Mention:
type: object
properties:
@ -1326,7 +1806,7 @@ components:
created_at:
description: The timestamp of the notification.
type: string
format: dateTime
format: date-time
account:
$ref: '#/components/schemas/Account'
status:
@ -1366,6 +1846,31 @@ components:
description: Are you receiving this user's replies in your home timeline?
type: boolean
default: true
Role:
type: object
properties:
id:
description: The ID of the role in the database.
type: integer
example: 1
name:
description: The name of the role.
type: string
enum:
- user
- admin
- read_only_user
permissions:
description: A list of all permissions granted to the role.
type: array
items:
type: string
enum:
- create_follow_request
- create_post
- delete_any_post
- delete_any_profile
- manage_subscription_options
Signature:
type: object
properties:
@ -1391,11 +1896,11 @@ components:
created_at:
description: The date when this post was created.
type: string
format: dateTime
format: date-time
edited_at:
description: The date when this post was edited.
type: string
format: dateTime
format: date-time
nullable: true
account:
description: The profile that authored this post.
@ -1406,6 +1911,13 @@ components:
visibility:
description: Visibility of this post.
$ref: '#/components/schemas/Visibility'
sensitiive:
description: Is this post marked as sensitive content?
type: boolean
example: false
spoiler_text:
description: Subject or summary line, below which post content is collapsed until expanded.
type: string
media_attachments:
description: Media that is attached to this post.
type: array
@ -1421,6 +1933,11 @@ components:
type: array
items:
$ref: '#/components/schemas/Tag'
emojis:
description: Custom emoji to be used when rendering post content.
type: array
items:
$ref: '#/components/schemas/CustomEmoji'
reblog:
description: The post being reposted.
type: object
@ -1453,7 +1970,7 @@ components:
expires_at:
description: The date when subscription expires.
type: string
format: dateTime
format: date-time
SubscriptionOption:
type: object
properties:

27
fedimovies-cli/Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "fedimovies-cli"
version = "1.22.0"
license = "AGPL-3.0"
edition = "2021"
rust-version = "1.68"
[[bin]]
name = "fedimoviesctl"
path = "src/main.rs"
[dependencies]
fedimovies-config = { path = "../fedimovies-config" }
fedimovies-models = { path = "../fedimovies-models" }
fedimovies-utils = { path = "../fedimovies-utils" }
fedimovies = { path = ".." }
# Used for catching errors
anyhow = "1.0.58"
# Used to build admin CLI tool
clap = { version = "3.2.18", default-features = false, features = ["std", "derive"] }
# Used for logging
log = "0.4.14"
# Async runtime
tokio = { version = "1.20.4", features = ["macros"] }
# Used to work with UUIDs
uuid = "1.1.2"

570
fedimovies-cli/src/cli.rs Normal file
View file

@ -0,0 +1,570 @@
use anyhow::Error;
use clap::Parser;
use uuid::Uuid;
use fedimovies::activitypub::{
actors::helpers::update_remote_profile, builders::delete_note::prepare_delete_note,
builders::delete_person::prepare_delete_person, fetcher::fetchers::fetch_actor,
fetcher::helpers::import_from_outbox,
};
use fedimovies::admin::roles::{role_from_str, ALLOWED_ROLES};
use fedimovies::media::{remove_files, remove_media, MediaStorage};
use fedimovies::validators::{emojis::EMOJI_LOCAL_MAX_SIZE, users::validate_local_username};
use fedimovies_config::Config;
use fedimovies_models::{
attachments::queries::delete_unused_attachments,
cleanup::find_orphaned_files,
database::DatabaseClient,
emojis::helpers::get_emoji_by_name,
emojis::queries::{
create_emoji, delete_emoji, find_unused_remote_emojis, get_emoji_by_name_and_hostname,
},
oauth::queries::delete_oauth_tokens,
posts::queries::{delete_post, find_extraneous_posts, get_post_by_id},
profiles::queries::{
delete_profile, find_empty_profiles, find_unreachable, get_profile_by_id,
get_profile_by_remote_actor_id,
},
subscriptions::queries::reset_subscriptions,
users::queries::{
create_invite_code, create_user, get_invite_codes, get_user_by_id, set_user_password,
set_user_role,
},
users::types::UserCreateData,
};
use fedimovies_utils::{
crypto_rsa::{generate_rsa_key, serialize_private_key},
datetime::{days_before_now, get_min_datetime},
passwords::hash_password,
};
/// Admin CLI tool
#[derive(Parser)]
pub struct Opts {
#[clap(subcommand)]
pub subcmd: SubCommand,
}
#[derive(Parser)]
pub enum SubCommand {
GenerateRsaKey(GenerateRsaKey),
GenerateEthereumAddress(GenerateEthereumAddress),
GenerateInviteCode(GenerateInviteCode),
ListInviteCodes(ListInviteCodes),
CreateUser(CreateUser),
SetPassword(SetPassword),
SetRole(SetRole),
RefetchActor(RefetchActor),
ReadOutbox(ReadOutbox),
DeleteProfile(DeleteProfile),
DeletePost(DeletePost),
DeleteEmoji(DeleteEmoji),
DeleteExtraneousPosts(DeleteExtraneousPosts),
DeleteUnusedAttachments(DeleteUnusedAttachments),
DeleteOrphanedFiles(DeleteOrphanedFiles),
DeleteEmptyProfiles(DeleteEmptyProfiles),
PruneRemoteEmojis(PruneRemoteEmojis),
ListUnreachableActors(ListUnreachableActors),
ImportEmoji(ImportEmoji),
UpdateCurrentBlock(UpdateCurrentBlock),
ResetSubscriptions(ResetSubscriptions),
CreateMoneroWallet(CreateMoneroWallet),
CheckExpiredInvoice(CheckExpiredInvoice),
}
/// Generate RSA private key
#[derive(Parser)]
pub struct GenerateRsaKey;
impl GenerateRsaKey {
pub fn execute(&self) -> () {
let private_key = generate_rsa_key().unwrap();
let private_key_str = serialize_private_key(&private_key).unwrap();
println!("{}", private_key_str);
}
}
/// Generate ethereum address
#[derive(Parser)]
pub struct GenerateEthereumAddress;
impl GenerateEthereumAddress {
pub fn execute(&self) -> () {
println!("dummy");
}
}
/// Generate invite code
#[derive(Parser)]
pub struct GenerateInviteCode {
note: Option<String>,
}
impl GenerateInviteCode {
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
let invite_code = create_invite_code(db_client, self.note.as_deref()).await?;
println!("generated invite code: {}", invite_code);
Ok(())
}
}
/// List invite codes
#[derive(Parser)]
pub struct ListInviteCodes;
impl ListInviteCodes {
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
let invite_codes = get_invite_codes(db_client).await?;
if invite_codes.is_empty() {
println!("no invite codes found");
return Ok(());
};
for invite_code in invite_codes {
if let Some(note) = invite_code.note {
println!("{} ({})", invite_code.code, note);
} else {
println!("{}", invite_code.code);
};
}
Ok(())
}
}
/// Create new user
#[derive(Parser)]
pub struct CreateUser {
username: String,
password: String,
#[clap(value_parser = ALLOWED_ROLES)]
role: String,
}
impl CreateUser {
pub async fn execute(&self, db_client: &mut impl DatabaseClient) -> Result<(), Error> {
validate_local_username(&self.username)?;
let password_hash = hash_password(&self.password)?;
let private_key = generate_rsa_key()?;
let private_key_pem = serialize_private_key(&private_key)?;
let role = role_from_str(&self.role)?;
let user_data = UserCreateData {
username: self.username.clone(),
password_hash: Some(password_hash),
private_key_pem,
wallet_address: None,
invite_code: None,
role,
};
create_user(db_client, user_data).await?;
println!("user created");
Ok(())
}
}
/// Set password
#[derive(Parser)]
pub struct SetPassword {
id: Uuid,
password: String,
}
impl SetPassword {
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
let password_hash = hash_password(&self.password)?;
set_user_password(db_client, &self.id, password_hash).await?;
// Revoke all sessions
delete_oauth_tokens(db_client, &self.id).await?;
println!("password updated");
Ok(())
}
}
/// Change user's role
#[derive(Parser)]
pub struct SetRole {
id: Uuid,
#[clap(value_parser = ALLOWED_ROLES)]
role: String,
}
impl SetRole {
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
let role = role_from_str(&self.role)?;
set_user_role(db_client, &self.id, role).await?;
println!("role changed");
Ok(())
}
}
/// Re-fetch actor profile by actor ID
#[derive(Parser)]
pub struct RefetchActor {
id: String,
}
impl RefetchActor {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?;
let actor = fetch_actor(&config.instance(), &self.id).await?;
update_remote_profile(
db_client,
&config.instance(),
&MediaStorage::from(config),
profile,
actor,
)
.await?;
println!("profile updated");
Ok(())
}
}
/// Pull activities from actor's outbox
#[derive(Parser)]
pub struct ReadOutbox {
actor_id: String,
#[clap(long, default_value_t = 5)]
limit: usize,
}
impl ReadOutbox {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
import_from_outbox(config, db_client, &self.actor_id, self.limit).await?;
Ok(())
}
}
/// Delete profile
#[derive(Parser)]
pub struct DeleteProfile {
id: Uuid,
}
impl DeleteProfile {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let profile = get_profile_by_id(db_client, &self.id).await?;
let mut maybe_delete_person = None;
if profile.is_local() {
let user = get_user_by_id(db_client, &profile.id).await?;
let activity = prepare_delete_person(db_client, &config.instance(), &user).await?;
maybe_delete_person = Some(activity);
};
let deletion_queue = delete_profile(db_client, &profile.id).await?;
remove_media(config, deletion_queue).await;
// Send Delete(Person) activities
if let Some(activity) = maybe_delete_person {
activity.enqueue(db_client).await?;
};
println!("profile deleted");
Ok(())
}
}
/// Delete post
#[derive(Parser)]
pub struct DeletePost {
id: Uuid,
}
impl DeletePost {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let post = get_post_by_id(db_client, &self.id).await?;
let mut maybe_delete_note = None;
if post.author.is_local() {
let author = get_user_by_id(db_client, &post.author.id).await?;
let activity =
prepare_delete_note(db_client, &config.instance(), &author, &post).await?;
maybe_delete_note = Some(activity);
};
let deletion_queue = delete_post(db_client, &post.id).await?;
remove_media(config, deletion_queue).await;
// Send Delete(Note) activity
if let Some(activity) = maybe_delete_note {
activity.enqueue(db_client).await?;
};
println!("post deleted");
Ok(())
}
}
/// Delete custom emoji
#[derive(Parser)]
pub struct DeleteEmoji {
emoji_name: String,
hostname: Option<String>,
}
impl DeleteEmoji {
pub async fn execute(
&self,
config: &Config,
db_client: &impl DatabaseClient,
) -> Result<(), Error> {
let emoji =
get_emoji_by_name(db_client, &self.emoji_name, self.hostname.as_deref()).await?;
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
remove_media(config, deletion_queue).await;
println!("emoji deleted");
Ok(())
}
}
/// Delete old remote posts
#[derive(Parser)]
pub struct DeleteExtraneousPosts {
days: u32,
}
impl DeleteExtraneousPosts {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let updated_before = days_before_now(self.days);
let posts = find_extraneous_posts(db_client, &updated_before).await?;
for post_id in posts {
let deletion_queue = delete_post(db_client, &post_id).await?;
remove_media(config, deletion_queue).await;
println!("post {} deleted", post_id);
}
Ok(())
}
}
/// Delete attachments that don't belong to any post
#[derive(Parser)]
pub struct DeleteUnusedAttachments {
days: u32,
}
impl DeleteUnusedAttachments {
pub async fn execute(
&self,
config: &Config,
db_client: &impl DatabaseClient,
) -> Result<(), Error> {
let created_before = days_before_now(self.days);
let deletion_queue = delete_unused_attachments(db_client, &created_before).await?;
remove_media(config, deletion_queue).await;
println!("unused attachments deleted");
Ok(())
}
}
/// Find and delete orphaned files
#[derive(Parser)]
pub struct DeleteOrphanedFiles;
impl DeleteOrphanedFiles {
pub async fn execute(
&self,
config: &Config,
db_client: &impl DatabaseClient,
) -> Result<(), Error> {
let media_dir = config.media_dir();
let mut files = vec![];
for maybe_path in std::fs::read_dir(&media_dir)? {
let file_name = maybe_path?.file_name().to_string_lossy().to_string();
files.push(file_name);
}
println!("found {} files", files.len());
let orphaned = find_orphaned_files(db_client, files).await?;
if !orphaned.is_empty() {
remove_files(orphaned, &media_dir);
println!("orphaned files deleted");
};
Ok(())
}
}
/// Delete empty remote profiles
#[derive(Parser)]
pub struct DeleteEmptyProfiles {
days: u32,
}
impl DeleteEmptyProfiles {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let updated_before = days_before_now(self.days);
let profiles = find_empty_profiles(db_client, &updated_before).await?;
for profile_id in profiles {
let profile = get_profile_by_id(db_client, &profile_id).await?;
let deletion_queue = delete_profile(db_client, &profile.id).await?;
remove_media(config, deletion_queue).await;
println!("profile {} deleted", profile.acct);
}
Ok(())
}
}
/// Delete unused remote emojis
#[derive(Parser)]
pub struct PruneRemoteEmojis;
impl PruneRemoteEmojis {
pub async fn execute(
&self,
config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let emojis = find_unused_remote_emojis(db_client).await?;
for emoji_id in emojis {
let deletion_queue = delete_emoji(db_client, &emoji_id).await?;
remove_media(config, deletion_queue).await;
println!("emoji {} deleted", emoji_id);
}
Ok(())
}
}
/// List unreachable actors
#[derive(Parser)]
pub struct ListUnreachableActors {
days: u32,
}
impl ListUnreachableActors {
pub async fn execute(
&self,
_config: &Config,
db_client: &impl DatabaseClient,
) -> Result<(), Error> {
let unreachable_since = days_before_now(self.days);
let profiles = find_unreachable(db_client, &unreachable_since).await?;
println!(
"{0: <60} | {1: <35} | {2: <35}",
"ID", "unreachable since", "updated at",
);
for profile in profiles {
println!(
"{0: <60} | {1: <35} | {2: <35}",
profile.actor_id.unwrap(),
profile.unreachable_since.unwrap().to_string(),
profile.updated_at.to_string(),
);
}
Ok(())
}
}
/// Import custom emoji from another instance
#[derive(Parser)]
pub struct ImportEmoji {
emoji_name: String,
hostname: String,
}
impl ImportEmoji {
pub async fn execute(
&self,
_config: &Config,
db_client: &impl DatabaseClient,
) -> Result<(), Error> {
let emoji =
get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?;
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
println!("emoji is too big");
return Ok(());
};
create_emoji(
db_client,
&emoji.emoji_name,
None,
emoji.image,
None,
&get_min_datetime(),
)
.await?;
println!("added emoji to local collection");
Ok(())
}
}
/// Update blockchain synchronization starting block
#[derive(Parser)]
pub struct UpdateCurrentBlock {
number: u64,
}
impl UpdateCurrentBlock {
pub async fn execute(
&self,
_config: &Config,
_db_client: &impl DatabaseClient,
) -> Result<(), Error> {
println!("current block updated");
Ok(())
}
}
/// Reset all subscriptions
/// (can be used during development or when switching between chains)
#[derive(Parser)]
pub struct ResetSubscriptions {
#[clap(long)]
ethereum_contract_replaced: bool,
}
impl ResetSubscriptions {
pub async fn execute(
&self,
_config: &Config,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
reset_subscriptions(db_client, self.ethereum_contract_replaced).await?;
println!("subscriptions deleted");
Ok(())
}
}
/// Create Monero wallet
/// (can be used when monero-wallet-rpc runs with --wallet-dir option)
#[derive(Parser)]
pub struct CreateMoneroWallet {
name: String,
password: Option<String>,
}
impl CreateMoneroWallet {
pub async fn execute(&self, _config: &Config) -> Result<(), Error> {
println!("wallet created");
Ok(())
}
}
/// Check expired invoice
#[derive(Parser)]
pub struct CheckExpiredInvoice {
id: Uuid,
}
impl CheckExpiredInvoice {
pub async fn execute(
&self,
_config: &Config,
_db_client: &impl DatabaseClient,
) -> Result<(), Error> {
Ok(())
}
}

View file

@ -0,0 +1,76 @@
use clap::Parser;
use fedimovies::logger::configure_logger;
use fedimovies_config::parse_config;
use fedimovies_models::database::create_database_client;
use fedimovies_models::database::migrate::apply_migrations;
mod cli;
use cli::{Opts, SubCommand};
#[tokio::main]
async fn main() {
let opts: Opts = Opts::parse();
match opts.subcmd {
SubCommand::GenerateRsaKey(cmd) => cmd.execute(),
SubCommand::GenerateEthereumAddress(cmd) => cmd.execute(),
subcmd => {
// Other commands require initialized app
let (config, config_warnings) = parse_config();
configure_logger(config.log_level);
log::info!("config loaded from {}", config.config_path);
for warning in config_warnings {
log::warn!("{}", warning);
}
let db_config = config.database_url.parse().unwrap();
let db_client =
&mut create_database_client(&db_config, config.tls_ca_file.as_deref()).await;
apply_migrations(db_client).await;
match subcmd {
SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::ListInviteCodes(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::CreateUser(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::SetPassword(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::SetRole(cmd) => cmd.execute(db_client).await.unwrap(),
SubCommand::RefetchActor(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::ReadOutbox(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteProfile(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeletePost(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteEmoji(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteExtraneousPosts(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::DeleteUnusedAttachments(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::DeleteOrphanedFiles(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::DeleteEmptyProfiles(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::PruneRemoteEmojis(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::ListUnreachableActors(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::ImportEmoji(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::UpdateCurrentBlock(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::ResetSubscriptions(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
SubCommand::CheckExpiredInvoice(cmd) => {
cmd.execute(&config, db_client).await.unwrap()
}
_ => unreachable!(),
};
}
};
}

View file

@ -0,0 +1,30 @@
[package]
name = "fedimovies-config"
version = "1.22.0"
license = "AGPL-3.0"
edition = "2021"
rust-version = "1.68"
[dependencies]
fedimovies-utils = { path = "../fedimovies-utils" }
# Used to read .env files
dotenv = "0.15.0"
# Used for logging
log = { version = "0.4.14", features = ["serde"] }
# Used for working with regular expressions
regex = "1.6.0"
# Used for working with RSA keys
rsa = "0.5.0"
# Used for serialization/deserialization
serde = { version = "1.0.136", features = ["derive"] }
# Used to parse config file
serde_yaml = "0.8.17"
# Used for creating error types
thiserror = "1.0.37"
# Used to work with URLs
url = "2.2.2"
[features]
production = []
test-utils = []

View file

@ -0,0 +1,221 @@
use std::path::PathBuf;
use log::Level as LogLevel;
use rsa::RsaPrivateKey;
use serde::Deserialize;
use url::Url;
use fedimovies_utils::urls::normalize_url;
use super::environment::Environment;
use super::federation::FederationConfig;
use super::limits::Limits;
use super::registration::RegistrationConfig;
use super::retention::RetentionConfig;
use super::REEF_VERSION;
fn default_log_level() -> LogLevel {
LogLevel::Info
}
fn default_login_message() -> String {
"What?!".to_string()
}
#[derive(Clone, Deserialize)]
pub struct Config {
// Properties auto-populated from the environment
#[serde(skip)]
pub environment: Environment,
#[serde(skip)]
pub config_path: String,
// Core settings
pub database_url: String,
#[serde(default)]
pub tls_ca_file: Option<PathBuf>,
pub storage_dir: PathBuf,
pub web_client_dir: Option<PathBuf>,
pub http_host: String,
pub http_port: u32,
#[serde(default)]
pub http_cors_allowlist: Vec<String>,
#[serde(default = "default_log_level")]
pub log_level: LogLevel,
// Domain name or <IP address>:<port>
// URI scheme is optional
instance_uri: String,
pub instance_title: String,
pub instance_short_description: String,
pub instance_description: String,
#[serde(default)]
pub tmdb_api_key: Option<String>,
#[serde(default)]
pub movie_user_password: Option<String>,
#[serde(skip)]
pub(super) instance_rsa_key: Option<RsaPrivateKey>,
pub(super) registrations_open: Option<bool>, // deprecated
#[serde(default)]
pub registration: RegistrationConfig,
// EIP-4361 login message
#[serde(default = "default_login_message")]
pub login_message: String,
pub(super) post_character_limit: Option<usize>, // deprecated
#[serde(default)]
pub limits: Limits,
#[serde(default)]
pub retention: RetentionConfig,
pub(super) proxy_url: Option<String>,
#[serde(default)]
pub federation: FederationConfig,
#[serde(default)]
pub blocked_instances: Vec<String>,
// IPFS
pub ipfs_api_url: Option<String>,
pub ipfs_gateway_url: Option<String>,
}
impl Config {
pub(super) fn try_instance_url(&self) -> Result<Url, url::ParseError> {
normalize_url(&self.instance_uri)
}
pub fn instance(&self) -> Instance {
Instance {
_url: self.try_instance_url().unwrap(),
actor_key: self.instance_rsa_key.clone().unwrap(),
proxy_url: self.federation.proxy_url.clone(),
onion_proxy_url: self.federation.onion_proxy_url.clone(),
i2p_proxy_url: self.federation.i2p_proxy_url.clone(),
// Private instance doesn't send activities and sign requests
is_private: !self.federation.enabled,
// || matches!(self.environment, Environment::Development),
fetcher_timeout: self.federation.fetcher_timeout,
deliverer_timeout: self.federation.deliverer_timeout,
}
}
pub fn instance_url(&self) -> String {
self.instance().url()
}
pub fn media_dir(&self) -> PathBuf {
self.storage_dir.join("media")
}
}
#[derive(Clone)]
pub struct Instance {
_url: Url,
// Instance actor
pub actor_key: RsaPrivateKey,
// Proxy for outgoing requests
pub proxy_url: Option<String>,
pub onion_proxy_url: Option<String>,
pub i2p_proxy_url: Option<String>,
// Private instance won't send signed HTTP requests
pub is_private: bool,
pub fetcher_timeout: u64,
pub deliverer_timeout: u64,
}
impl Instance {
pub fn url(&self) -> String {
self._url.origin().ascii_serialization()
}
pub fn hostname(&self) -> String {
self._url.host_str().unwrap().to_string()
}
pub fn agent(&self) -> String {
format!(
"Reef {version}; {instance_url}",
version = REEF_VERSION,
instance_url = self.url(),
)
}
}
#[cfg(feature = "test-utils")]
impl Instance {
pub fn for_test(url: &str) -> Self {
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
Self {
_url: Url::parse(url).unwrap(),
actor_key: generate_weak_rsa_key().unwrap(),
proxy_url: None,
onion_proxy_url: None,
i2p_proxy_url: None,
is_private: true,
fetcher_timeout: 0,
deliverer_timeout: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
#[test]
fn test_instance_url_https_dns() {
let instance_url = Url::parse("https://example.com/").unwrap();
let instance_rsa_key = generate_weak_rsa_key().unwrap();
let instance = Instance {
_url: instance_url,
actor_key: instance_rsa_key,
proxy_url: None,
onion_proxy_url: None,
i2p_proxy_url: None,
is_private: true,
fetcher_timeout: 0,
deliverer_timeout: 0,
};
assert_eq!(instance.url(), "https://example.com");
assert_eq!(instance.hostname(), "example.com");
assert_eq!(
instance.agent(),
format!("Mitra {}; https://example.com", REEF_VERSION),
);
}
#[test]
fn test_instance_url_http_ipv4() {
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
let instance_rsa_key = generate_weak_rsa_key().unwrap();
let instance = Instance {
_url: instance_url,
actor_key: instance_rsa_key,
proxy_url: None,
onion_proxy_url: None,
i2p_proxy_url: None,
is_private: true,
fetcher_timeout: 0,
deliverer_timeout: 0,
};
assert_eq!(instance.url(), "http://1.2.3.4:3777");
assert_eq!(instance.hostname(), "1.2.3.4");
}
}

View file

@ -1,6 +1,6 @@
use std::str::FromStr;
use crate::errors::ConversionError;
use super::ConfigError;
#[derive(Clone, Debug)]
pub enum Environment {
@ -10,19 +10,23 @@ pub enum Environment {
impl Default for Environment {
#[cfg(feature = "production")]
fn default() -> Self { Self::Production }
fn default() -> Self {
Self::Production
}
#[cfg(not(feature = "production"))]
fn default() -> Self { Self::Development }
fn default() -> Self {
Self::Development
}
}
impl FromStr for Environment {
type Err = ConversionError;
type Err = ConfigError;
fn from_str(val: &str) -> Result<Self, Self::Err> {
let environment = match val {
"development" => Environment::Development,
"production" => Environment::Production,
_ => return Err(ConversionError),
_ => return Err(ConfigError("invalid environment type")),
};
Ok(environment)
}

View file

@ -0,0 +1,38 @@
use serde::Deserialize;
fn default_federation_enabled() -> bool {
true
}
const fn default_fetcher_timeout() -> u64 {
300
}
const fn default_deliverer_timeout() -> u64 {
30
}
#[derive(Clone, Deserialize)]
pub struct FederationConfig {
#[serde(default = "default_federation_enabled")]
pub enabled: bool,
#[serde(default = "default_fetcher_timeout")]
pub(super) fetcher_timeout: u64,
#[serde(default = "default_deliverer_timeout")]
pub(super) deliverer_timeout: u64,
pub(super) proxy_url: Option<String>,
pub(super) onion_proxy_url: Option<String>,
pub(super) i2p_proxy_url: Option<String>,
}
impl Default for FederationConfig {
fn default() -> Self {
Self {
enabled: default_federation_enabled(),
fetcher_timeout: default_fetcher_timeout(),
deliverer_timeout: default_deliverer_timeout(),
proxy_url: None,
onion_proxy_url: None,
i2p_proxy_url: None,
}
}
}

View file

@ -0,0 +1,18 @@
mod config;
mod environment;
mod federation;
mod limits;
mod loader;
mod registration;
mod retention;
pub use config::{Config, Instance};
pub use environment::Environment;
pub use loader::parse_config;
pub use registration::{DefaultRole, RegistrationType};
pub const REEF_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(thiserror::Error, Debug)]
#[error("{0}")]
pub struct ConfigError(&'static str);

View file

@ -0,0 +1,106 @@
use super::ConfigError;
use regex::Regex;
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
const FILE_SIZE_RE: &str = r#"^(?i)(?P<size>\d+)(?P<unit>[kmg]?)b?$"#;
fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
let file_size_re = Regex::new(FILE_SIZE_RE).expect("regexp should be valid");
let caps = file_size_re
.captures(value)
.ok_or(ConfigError("invalid file size"))?;
let size: usize = caps["size"]
.to_string()
.parse()
.map_err(|_| ConfigError("invalid file size"))?;
let unit = caps["unit"].to_string().to_lowercase();
let multiplier = match unit.as_str() {
"k" => usize::pow(10, 3),
"m" => usize::pow(10, 6),
"g" => usize::pow(10, 9),
"" => 1,
_ => return Err(ConfigError("invalid file size unit")),
};
Ok(size * multiplier)
}
fn deserialize_file_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: Deserializer<'de>,
{
let file_size_str = String::deserialize(deserializer)?;
let file_size = parse_file_size(&file_size_str).map_err(DeserializerError::custom)?;
Ok(file_size)
}
const fn default_file_size_limit() -> usize {
20_000_000
} // 20 MB
const fn default_emoji_size_limit() -> usize {
500_000
} // 500 kB
#[derive(Clone, Deserialize)]
pub struct MediaLimits {
#[serde(
default = "default_file_size_limit",
deserialize_with = "deserialize_file_size"
)]
pub file_size_limit: usize,
#[serde(
default = "default_emoji_size_limit",
deserialize_with = "deserialize_file_size"
)]
pub emoji_size_limit: usize,
}
impl Default for MediaLimits {
fn default() -> Self {
Self {
file_size_limit: default_file_size_limit(),
emoji_size_limit: default_emoji_size_limit(),
}
}
}
const fn default_post_character_limit() -> usize {
2000
}
#[derive(Clone, Deserialize)]
pub struct PostLimits {
#[serde(default = "default_post_character_limit")]
pub character_limit: usize,
}
impl Default for PostLimits {
fn default() -> Self {
Self {
character_limit: default_post_character_limit(),
}
}
}
#[derive(Clone, Default, Deserialize)]
pub struct Limits {
#[serde(default)]
pub media: MediaLimits,
#[serde(default)]
pub posts: PostLimits,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_file_size() {
let file_size = parse_file_size("1234").unwrap();
assert_eq!(file_size, 1234);
let file_size = parse_file_size("89kB").unwrap();
assert_eq!(file_size, 89_000);
let file_size = parse_file_size("12M").unwrap();
assert_eq!(file_size, 12_000_000);
}
}

View file

@ -0,0 +1,134 @@
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::str::FromStr;
use rsa::RsaPrivateKey;
use fedimovies_utils::{
crypto_rsa::{deserialize_private_key, generate_rsa_key, serialize_private_key},
files::{set_file_permissions, write_file},
};
use super::config::Config;
use super::environment::Environment;
use super::registration::{DefaultRole, RegistrationType};
struct EnvConfig {
config_path: String,
environment: Option<Environment>,
}
#[cfg(feature = "production")]
const DEFAULT_CONFIG_PATH: &str = "/etc/fedimovies/config.yaml";
#[cfg(not(feature = "production"))]
const DEFAULT_CONFIG_PATH: &str = "config.yaml";
fn parse_env() -> EnvConfig {
dotenv::from_filename(".env.local").ok();
dotenv::dotenv().ok();
let config_path = std::env::var("CONFIG_PATH").unwrap_or(DEFAULT_CONFIG_PATH.to_string());
let environment = std::env::var("ENVIRONMENT")
.ok()
.map(|val| Environment::from_str(&val).expect("invalid environment type"));
EnvConfig {
config_path,
environment,
}
}
extern "C" {
fn geteuid() -> u32;
}
fn check_directory_owner(path: &Path) -> () {
let metadata = std::fs::metadata(path).expect("can't read file metadata");
let owner_uid = metadata.uid();
let current_uid = unsafe { geteuid() };
if owner_uid != current_uid {
panic!(
"{} owner ({}) is different from the current user ({})",
path.display(),
owner_uid,
current_uid,
);
};
}
/// Generates new instance RSA key or returns existing key
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
let private_key_path = storage_dir.join("instance_rsa_key");
if private_key_path.exists() {
let private_key_str =
std::fs::read_to_string(&private_key_path).expect("failed to read instance RSA key");
let private_key =
deserialize_private_key(&private_key_str).expect("failed to read instance RSA key");
private_key
} else {
let private_key = generate_rsa_key().expect("failed to generate RSA key");
let private_key_str =
serialize_private_key(&private_key).expect("failed to serialize RSA key");
write_file(private_key_str.as_bytes(), &private_key_path)
.expect("failed to write instance RSA key");
set_file_permissions(&private_key_path, 0o600)
.expect("failed to set permissions on RSA key file");
private_key
}
}
pub fn parse_config() -> (Config, Vec<&'static str>) {
let env = parse_env();
let config_yaml =
std::fs::read_to_string(&env.config_path).expect("failed to load config file");
let mut config = serde_yaml::from_str::<Config>(&config_yaml).expect("invalid yaml data");
let mut warnings = vec![];
// Set parameters from environment
config.config_path = env.config_path;
if let Some(environment) = env.environment {
// Overwrite default only if ENVIRONMENT variable is set
config.environment = environment;
};
// Validate config
if !config.storage_dir.exists() {
panic!("storage directory does not exist");
};
check_directory_owner(&config.storage_dir);
config.try_instance_url().expect("invalid instance URI");
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
};
// Migrations
if let Some(registrations_open) = config.registrations_open {
// Change type if 'registrations_open' parameter is used
warnings
.push("'registrations_open' setting is deprecated, use 'registration.type' instead");
if registrations_open {
config.registration.registration_type = RegistrationType::Open;
} else {
config.registration.registration_type = RegistrationType::Invite;
};
};
if let Some(read_only_user) = config.registration.default_role_read_only_user {
warnings.push("'default_role_read_only_user' setting is deprecated, use 'registration.default_role' instead");
if read_only_user {
config.registration.default_role = DefaultRole::ReadOnlyUser;
} else {
config.registration.default_role = DefaultRole::NormalUser;
};
};
if let Some(post_character_limit) = config.post_character_limit {
warnings.push("'post_character_limit' setting is deprecated, use 'limits.posts.character_limit' instead");
config.limits.posts.character_limit = post_character_limit;
};
if let Some(ref proxy_url) = config.proxy_url {
warnings.push("'proxy_url' setting is deprecated, use 'federation.proxy_url' instead");
config.federation.proxy_url = Some(proxy_url.to_string());
};
// Insert instance RSA key
config.instance_rsa_key = Some(read_instance_rsa_key(&config.storage_dir));
(config, warnings)
}

View file

@ -0,0 +1,66 @@
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
#[derive(Clone, PartialEq)]
pub enum RegistrationType {
Open,
Invite,
}
impl Default for RegistrationType {
fn default() -> Self {
Self::Invite
}
}
impl<'de> Deserialize<'de> for RegistrationType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let registration_type_str = String::deserialize(deserializer)?;
let registration_type = match registration_type_str.as_str() {
"open" => Self::Open,
"invite" => Self::Invite,
_ => return Err(DeserializerError::custom("unknown registration type")),
};
Ok(registration_type)
}
}
#[derive(Clone)]
pub enum DefaultRole {
NormalUser,
ReadOnlyUser,
}
impl Default for DefaultRole {
fn default() -> Self {
Self::NormalUser
}
}
impl<'de> Deserialize<'de> for DefaultRole {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let role_str = String::deserialize(deserializer)?;
let role = match role_str.as_str() {
"user" => Self::NormalUser,
"read_only_user" => Self::ReadOnlyUser,
_ => return Err(DeserializerError::custom("unknown role name")),
};
Ok(role)
}
}
#[derive(Clone, Default, Deserialize)]
pub struct RegistrationConfig {
#[serde(rename = "type")]
pub registration_type: RegistrationType,
pub(super) default_role_read_only_user: Option<bool>, // deprecated
#[serde(default)]
pub default_role: DefaultRole,
}

View file

@ -0,0 +1,17 @@
use serde::Deserialize;
#[derive(Clone, Deserialize)]
pub struct RetentionConfig {
pub extraneous_posts: Option<u32>,
pub empty_profiles: Option<u32>,
}
#[allow(clippy::derivable_impls)]
impl Default for RetentionConfig {
fn default() -> Self {
Self {
extraneous_posts: None,
empty_profiles: None,
}
}
}

View file

@ -0,0 +1,46 @@
[package]
name = "fedimovies-models"
version = "1.22.0"
license = "AGPL-3.0"
edition = "2021"
rust-version = "1.68"
[dependencies]
fedimovies-utils = { path = "../fedimovies-utils" }
# Used for working with dates
chrono = { version = "0.4.23", default-features = false, features = ["std", "serde"] }
# Used for pooling database connections
deadpool = "0.9.2"
deadpool-postgres = { version = "0.10.2", default-features = false }
# Used to work with hexadecimal strings
hex = { version = "0.4.3", features = ["serde"] }
# Used for logging
log = "0.4.14"
# Used for managing database migrations
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
# Used for serialization/deserialization
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.89"
# Used for creating error types
thiserror = "1.0.37"
# Async runtime
tokio = { version = "1.20.4", features = [] }
# Used for working with Postgresql database
openssl = { version = "0.10", features = ["vendored"] }
postgres-openssl = "0.5.0"
tokio-postgres = { version = "0.7.6", features = ["with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
postgres-types = { version = "0.2.3", features = ["derive", "with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
postgres-protocol = "0.6.4"
# Used to construct PostgreSQL queries
postgres_query = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
# Used to work with UUIDs
uuid = { version = "1.1.2", features = ["serde", "v4"] }
[dev-dependencies]
fedimovies-utils = { path = "../fedimovies-utils", features = ["test-utils"] }
serial_test = "0.7.0"
[features]
test-utils = []

View file

@ -0,0 +1,8 @@
CREATE TABLE background_job (
id UUID PRIMARY KEY,
job_type SMALLINT NOT NULL,
job_data JSONB NOT NULL,
job_status SMALLINT NOT NULL DEFAULT 1,
scheduled_for TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View file

@ -0,0 +1 @@
ALTER TABLE follow_request ADD COLUMN activity_id VARCHAR(250) UNIQUE;

View file

@ -0,0 +1 @@
ALTER TABLE actor_profile ADD COLUMN unreachable_since TIMESTAMP WITH TIME ZONE;

View file

@ -0,0 +1,10 @@
ALTER TABLE actor_profile ADD COLUMN avatar JSONB;
ALTER TABLE actor_profile ADD COLUMN banner JSONB;
UPDATE actor_profile
SET avatar = json_build_object('file_name', avatar_file_name)
WHERE avatar_file_name IS NOT NULL;
UPDATE actor_profile
SET banner = json_build_object('file_name', banner_file_name)
WHERE banner_file_name IS NOT NULL;
ALTER TABLE actor_profile DROP COLUMN avatar_file_name;
ALTER TABLE actor_profile DROP COLUMN banner_file_name;

View file

@ -0,0 +1,16 @@
CREATE TABLE emoji (
id UUID PRIMARY KEY,
emoji_name VARCHAR(100) NOT NULL,
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
image JSONB NOT NULL,
object_id VARCHAR(250) UNIQUE,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE (emoji_name, hostname),
CHECK ((hostname IS NULL) = (object_id IS NULL))
);
CREATE TABLE post_emoji (
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, emoji_id)
);

View file

@ -0,0 +1 @@
ALTER TABLE media_attachment ADD COLUMN file_size INTEGER;

View file

@ -0,0 +1,2 @@
ALTER TABLE user_account ADD COLUMN user_role SMALLINT NOT NULL DEFAULT 1;
ALTER TABLE user_account ALTER COLUMN user_role DROP DEFAULT;

View file

@ -0,0 +1,10 @@
CREATE TABLE oauth_application (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
app_name VARCHAR(100) NOT NULL,
website VARCHAR(100),
scopes VARCHAR(200) NOT NULL,
redirect_uri VARCHAR(200) NOT NULL,
client_id UUID UNIQUE NOT NULL,
client_secret VARCHAR(100) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View file

@ -0,0 +1,9 @@
CREATE TABLE oauth_authorization (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
code VARCHAR(100) UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
application_id INTEGER NOT NULL REFERENCES oauth_application (id) ON DELETE CASCADE,
scopes VARCHAR(200) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
);

View file

@ -0,0 +1,2 @@
ALTER TABLE user_invite_code ADD COLUMN note VARCHAR(200);
ALTER TABLE user_invite_code ADD COLUMN created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP;

View file

@ -0,0 +1,5 @@
CREATE TABLE profile_emoji (
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (profile_id, emoji_id)
);

View file

@ -0,0 +1 @@
ALTER TABLE actor_profile ADD COLUMN emojis JSONB NOT NULL DEFAULT '[]';

View file

@ -0,0 +1,4 @@
CREATE TABLE internal_property (
property_name VARCHAR(100) PRIMARY KEY,
property_value JSONB NOT NULL
);

View file

@ -0,0 +1,18 @@
UPDATE actor_profile
SET identity_proofs = replaced.identity_proofs
FROM (
SELECT
actor_profile.id,
jsonb_agg(
CASE
WHEN identity_proof ->> 'proof_type' = 'ethereum-eip191-00'
THEN jsonb_set(identity_proof, '{proof_type}', '1')
WHEN identity_proof ->> 'proof_type' = 'MitraMinisignSignature2022A'
THEN jsonb_set(identity_proof, '{proof_type}', '2')
END
) AS identity_proofs
FROM actor_profile
CROSS JOIN jsonb_array_elements(actor_profile.identity_proofs) AS identity_proof
GROUP BY actor_profile.id
) AS replaced
WHERE actor_profile.id = replaced.id;

View file

@ -0,0 +1,2 @@
ALTER TABLE actor_profile ADD COLUMN manually_approves_followers BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE actor_profile ALTER COLUMN manually_approves_followers DROP DEFAULT;

View file

@ -0,0 +1 @@
ALTER TABLE actor_profile ADD COLUMN aliases JSONB NOT NULL DEFAULT '[]';

View file

@ -0,0 +1,5 @@
ALTER TABLE relationship ADD CONSTRAINT relationship_source_id_target_id_check CHECK (source_id != target_id);
ALTER TABLE follow_request ADD CONSTRAINT follow_request_source_id_target_id_check CHECK (source_id != target_id);
ALTER TABLE post_link ADD CONSTRAINT post_link_source_id_target_id_check CHECK (source_id != target_id);
ALTER TABLE invoice ADD CONSTRAINT invoice_sender_id_recipient_id_check CHECK (sender_id != recipient_id);
ALTER TABLE subscription ADD CONSTRAINT subscription_sender_id_recipient_id_check CHECK (sender_id != recipient_id);

View file

@ -0,0 +1,6 @@
ALTER TABLE actor_profile ALTER COLUMN actor_id TYPE VARCHAR(2000);
ALTER TABLE oauth_application ALTER COLUMN redirect_uri TYPE VARCHAR(2000);
ALTER TABLE follow_request ALTER COLUMN activity_id TYPE VARCHAR(2000);
ALTER TABLE post ALTER COLUMN object_id TYPE VARCHAR(2000);
ALTER TABLE post_reaction ALTER COLUMN activity_id TYPE VARCHAR(2000);
ALTER TABLE emoji ALTER COLUMN object_id TYPE VARCHAR(2000);

View file

@ -0,0 +1 @@
ALTER TABLE user_account ADD COLUMN client_config JSONB NOT NULL DEFAULT '{}';

View file

@ -0,0 +1,2 @@
ALTER TABLE post ADD COLUMN is_sensitive BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE post ALTER COLUMN is_sensitive DROP DEFAULT;

View file

@ -1,3 +1,17 @@
CREATE TABLE internal_property (
property_name VARCHAR(100) PRIMARY KEY,
property_value JSONB NOT NULL
);
CREATE TABLE background_job (
id UUID PRIMARY KEY,
job_type SMALLINT NOT NULL,
job_data JSONB NOT NULL,
job_status SMALLINT NOT NULL DEFAULT 1,
scheduled_for TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE instance (
hostname VARCHAR(100) PRIMARY KEY
);
@ -10,25 +24,31 @@ CREATE TABLE actor_profile (
display_name VARCHAR(200),
bio TEXT,
bio_source TEXT,
avatar_file_name VARCHAR(100),
banner_file_name VARCHAR(100),
avatar JSONB,
banner JSONB,
manually_approves_followers BOOLEAN NOT NULL,
identity_proofs JSONB NOT NULL DEFAULT '[]',
payment_options JSONB NOT NULL DEFAULT '[]',
extra_fields JSONB NOT NULL DEFAULT '[]',
aliases JSONB NOT NULL DEFAULT '[]',
follower_count INTEGER NOT NULL CHECK (follower_count >= 0) DEFAULT 0,
following_count INTEGER NOT NULL CHECK (following_count >= 0) DEFAULT 0,
subscriber_count INTEGER NOT NULL CHECK (subscriber_count >= 0) DEFAULT 0,
post_count INTEGER NOT NULL CHECK (post_count >= 0) DEFAULT 0,
emojis JSONB NOT NULL DEFAULT '[]',
actor_json JSONB,
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
actor_id VARCHAR(2000) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
unreachable_since TIMESTAMP WITH TIME ZONE,
CHECK ((hostname IS NULL) = (actor_json IS NULL))
);
CREATE TABLE user_invite_code (
code VARCHAR(100) PRIMARY KEY,
used BOOLEAN NOT NULL DEFAULT FALSE
used BOOLEAN NOT NULL DEFAULT FALSE,
note VARCHAR(200),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE user_account (
@ -37,9 +57,32 @@ CREATE TABLE user_account (
password_hash VARCHAR(200),
private_key TEXT NOT NULL,
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
user_role SMALLINT NOT NULL,
client_config JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
CREATE TABLE oauth_application (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
app_name VARCHAR(100) NOT NULL,
website VARCHAR(100),
scopes VARCHAR(200) NOT NULL,
redirect_uri VARCHAR(2000) NOT NULL,
client_id UUID UNIQUE NOT NULL,
client_secret VARCHAR(100) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE oauth_authorization (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
code VARCHAR(100) UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
application_id INTEGER NOT NULL REFERENCES oauth_application (id) ON DELETE CASCADE,
scopes VARCHAR(200) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE TABLE oauth_token (
id SERIAL PRIMARY KEY,
owner_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
@ -53,15 +96,18 @@ CREATE TABLE relationship (
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
target_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
relationship_type SMALLINT NOT NULL,
UNIQUE (source_id, target_id, relationship_type)
UNIQUE (source_id, target_id, relationship_type),
CHECK (source_id != target_id)
);
CREATE TABLE follow_request (
id UUID PRIMARY KEY,
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
target_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
activity_id VARCHAR(2000) UNIQUE,
request_status SMALLINT NOT NULL,
UNIQUE (source_id, target_id)
UNIQUE (source_id, target_id),
CHECK (source_id != target_id)
);
CREATE TABLE post (
@ -70,11 +116,12 @@ CREATE TABLE post (
content TEXT NOT NULL,
in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE,
repost_of_id UUID REFERENCES post (id) ON DELETE CASCADE,
visilibity SMALLINT NOT NULL,
visibility SMALLINT NOT NULL,
is_sensitive BOOLEAN NOT NULL,
reply_count INTEGER NOT NULL CHECK (reply_count >= 0) DEFAULT 0,
reaction_count INTEGER NOT NULL CHECK (reaction_count >= 0) DEFAULT 0,
repost_count INTEGER NOT NULL CHECK (repost_count >= 0) DEFAULT 0,
object_id VARCHAR(200) UNIQUE,
object_id VARCHAR(2000) UNIQUE,
ipfs_cid VARCHAR(200),
token_id INTEGER,
token_tx_id VARCHAR(200),
@ -87,7 +134,7 @@ CREATE TABLE post_reaction (
id UUID PRIMARY KEY,
author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
activity_id VARCHAR(250) UNIQUE,
activity_id VARCHAR(2000) UNIQUE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
UNIQUE (author_id, post_id)
);
@ -95,8 +142,9 @@ CREATE TABLE post_reaction (
CREATE TABLE media_attachment (
id UUID PRIMARY KEY,
owner_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
media_type VARCHAR(50),
file_name VARCHAR(200) NOT NULL,
file_size INTEGER,
media_type VARCHAR(50),
ipfs_cid VARCHAR(200),
post_id UUID REFERENCES post (id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
@ -122,7 +170,31 @@ CREATE TABLE post_tag (
CREATE TABLE post_link (
source_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
target_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
PRIMARY KEY (source_id, target_id)
PRIMARY KEY (source_id, target_id),
CHECK (source_id != target_id)
);
CREATE TABLE emoji (
id UUID PRIMARY KEY,
emoji_name VARCHAR(100) NOT NULL,
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
image JSONB NOT NULL,
object_id VARCHAR(2000) UNIQUE,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE (emoji_name, hostname),
CHECK ((hostname IS NULL) = (object_id IS NULL))
);
CREATE TABLE post_emoji (
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, emoji_id)
);
CREATE TABLE profile_emoji (
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (profile_id, emoji_id)
);
CREATE TABLE notification (
@ -152,7 +224,8 @@ CREATE TABLE invoice (
amount BIGINT NOT NULL CHECK (amount >= 0),
invoice_status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (chain_id, payment_address)
UNIQUE (chain_id, payment_address),
CHECK (sender_id != recipient_id)
);
CREATE TABLE subscription (
@ -163,5 +236,6 @@ CREATE TABLE subscription (
chain_id VARCHAR(50) NOT NULL,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE (sender_id, recipient_id)
UNIQUE (sender_id, recipient_id),
CHECK (sender_id != recipient_id)
);

View file

@ -0,0 +1,132 @@
use chrono::{DateTime, Utc};
use uuid::Uuid;
use fedimovies_utils::id::generate_ulid;
use crate::cleanup::{find_orphaned_files, find_orphaned_ipfs_objects, DeletionQueue};
use crate::database::{DatabaseClient, DatabaseError};
use super::types::DbMediaAttachment;
pub async fn create_attachment(
db_client: &impl DatabaseClient,
owner_id: &Uuid,
file_name: String,
file_size: usize,
media_type: Option<String>,
) -> Result<DbMediaAttachment, DatabaseError> {
let attachment_id = generate_ulid();
let file_size: i32 = file_size.try_into().expect("value should be within bounds");
let inserted_row = db_client
.query_one(
"
INSERT INTO media_attachment (
id,
owner_id,
file_name,
file_size,
media_type
)
VALUES ($1, $2, $3, $4, $5)
RETURNING media_attachment
",
&[
&attachment_id,
&owner_id,
&file_name,
&file_size,
&media_type,
],
)
.await?;
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
Ok(db_attachment)
}
pub async fn set_attachment_ipfs_cid(
db_client: &impl DatabaseClient,
attachment_id: &Uuid,
ipfs_cid: &str,
) -> Result<DbMediaAttachment, DatabaseError> {
let maybe_row = db_client
.query_opt(
"
UPDATE media_attachment
SET ipfs_cid = $1
WHERE id = $2 AND ipfs_cid IS NULL
RETURNING media_attachment
",
&[&ipfs_cid, &attachment_id],
)
.await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("attachment"))?;
let db_attachment = row.try_get("media_attachment")?;
Ok(db_attachment)
}
pub async fn delete_unused_attachments(
db_client: &impl DatabaseClient,
created_before: &DateTime<Utc>,
) -> Result<DeletionQueue, DatabaseError> {
let rows = db_client
.query(
"
DELETE FROM media_attachment
WHERE post_id IS NULL AND created_at < $1
RETURNING file_name, ipfs_cid
",
&[&created_before],
)
.await?;
let mut files = vec![];
let mut ipfs_objects = vec![];
for row in rows {
let file_name = row.try_get("file_name")?;
files.push(file_name);
if let Some(ipfs_cid) = row.try_get("ipfs_cid")? {
ipfs_objects.push(ipfs_cid);
};
}
let orphaned_files = find_orphaned_files(db_client, files).await?;
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(db_client, ipfs_objects).await?;
Ok(DeletionQueue {
files: orphaned_files,
ipfs_objects: orphaned_ipfs_objects,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::database::test_utils::create_test_database;
use crate::profiles::{queries::create_profile, types::ProfileCreateData};
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_create_attachment() {
let db_client = &mut create_test_database().await;
let profile_data = ProfileCreateData {
username: "test".to_string(),
..Default::default()
};
let profile = create_profile(db_client, profile_data).await.unwrap();
let file_name = "test.jpg";
let file_size = 10000;
let media_type = "image/png";
let attachment = create_attachment(
db_client,
&profile.id,
file_name.to_string(),
file_size,
Some(media_type.to_string()),
)
.await
.unwrap();
assert_eq!(attachment.owner_id, profile.id);
assert_eq!(attachment.file_name, file_name);
assert_eq!(attachment.file_size.unwrap(), file_size as i32);
assert_eq!(attachment.media_type.unwrap(), media_type);
assert!(attachment.post_id.is_none());
}
}

View file

@ -7,8 +7,9 @@ use uuid::Uuid;
pub struct DbMediaAttachment {
pub id: Uuid,
pub owner_id: Uuid,
pub media_type: Option<String>,
pub file_name: String,
pub file_size: Option<i32>,
pub media_type: Option<String>,
pub ipfs_cid: Option<String>,
pub post_id: Option<Uuid>,
pub created_at: DateTime<Utc>,
@ -18,6 +19,7 @@ pub enum AttachmentType {
Unknown,
Image,
Video,
Audio,
}
impl AttachmentType {
@ -28,10 +30,12 @@ impl AttachmentType {
Self::Image
} else if media_type.starts_with("video/") {
Self::Video
} else if media_type.starts_with("audio/") {
Self::Audio
} else {
Self::Unknown
}
},
}
None => Self::Unknown,
}
}

Some files were not shown because too many files have changed in this diff Show more