Compare commits
No commits in common. "main" and "v1.14.0" have entirely different histories.
317 changed files with 12587 additions and 10364 deletions
.dockerignore.gitignore.woodpecker.ymlCHANGELOG.mdCargo.lockCargo.tomlFEDERATION.mdREADME.md
build
config.yaml.examplecontracts
IERC165.jsonIERC20Metadata.jsonIERC721Metadata.jsonIGate.jsonIMinter.jsonISubscription.jsonISubscriptionAdapter.json
contrib
docs
fedimovies-cli/src
fedimovies-config/src
fedimovies-models
Cargo.toml
justfilemigrations
V0044__user_invite_code__add_note_and_created_at.sqlV0045__profile_emoji.sqlV0046__actor_profile__add_emoji.sqlV0047__internal_property.sqlV0048__actor_profile__identity_proof_type_number.sqlV0049__actor_profile__add_manually_approves_followers.sqlV0050__actor_profile__add_aliases.sqlV0051__relationship__source_id_target_id_not_equal.sqlV0052__post__object_id_length.sqlV0053__user_account__client_config.sqlV0054__post__add_is_sensitive.sql
src
database
instances
notifications
profiles
properties
tags
migrations
V0001__create_tables.sqlV0002__actor_profile__add_extra_fields.sqlV0003__post__add_in_reply_to.sqlV0004__post__add_reply_count.sqlV0005__media_attachment__add_ipfs_cid.sqlV0006__oauth_token.sqlV0007__post__add_object_id.sqlV0008__notification.sqlV0009__post_reaction.sqlV0010__timeline_marker.sqlV0011__mention.sqlV0012__post__add_visibility.sqlV0013__post__add_repost_fields.sqlV0014__post_tag.sqlV0015__post_reaction__add_activity_id.sqlV0016__user_account__nullable_wallet_address.sqlV0017__actor_profile__increase_display_name_length.sqlV0018__relationship__add_id.sqlV0019__relationship__add_relationship_type.sqlV0020__subscription.sqlV0021__user_account__nullable_password_hash.sqlV0022__actor_profile__add_actor_id.sqlV0023__actor_profile__add_identity_proofs.sqlV0024__post__add_updated_at.sqlV0025__actor_profile__add_updated_at.sqlV0026__actor_profile__add_payment_options.sqlV0027__post_link.sqlV0028__actor_profile__clear_payment_options.sqlV0029__subscription__add_chain_id.sqlV0030__invoice.sqlV0031__subscription__sender_address_nullable.sqlV0032__actor_profile__add_subscriber_count.sqlV0033__invoice__add_amount.sqlV0034__actor_profile__add_hostname.sqlV0035__background_job.sqlV0036__follow_request__add_activity_id.sqlV0037__actor_profile__add_unreachable_since.sqlV0038__actor_profile__image_json.sqlV0039__emoji.sqlV0040__media_attachment__add_file_size.sqlV0041__user_account__user_role.sqlV0042__oauth_application.sqlV0043__oauth_authorization.sqlschema.sql
mitra-cli
mitra-config
|
@ -1,21 +0,0 @@
|
|||
# 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
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,9 +1,5 @@
|
|||
.env.local
|
||||
config.yaml
|
||||
/secret/*
|
||||
/files/*
|
||||
!/files/.gitkeep
|
||||
/build/*
|
||||
!/build/.gitkeep
|
||||
/target
|
||||
fly.toml
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
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
|
203
CHANGELOG.md
203
CHANGELOG.md
|
@ -6,209 +6,6 @@ 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
|
||||
|
|
2362
Cargo.lock
generated
2362
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
63
Cargo.toml
63
Cargo.toml
|
@ -1,39 +1,34 @@
|
|||
[package]
|
||||
name = "fedimovies"
|
||||
version = "1.22.0"
|
||||
description = "Movies reviews and ratings for the fediverse"
|
||||
name = "mitra"
|
||||
version = "1.14.0"
|
||||
description = "Federated micro-blogging platform and content subscription service"
|
||||
license = "AGPL-3.0"
|
||||
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
rust-version = "1.56"
|
||||
publish = false
|
||||
default-run = "fedimovies"
|
||||
default-run = "mitra"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"fedimovies-cli",
|
||||
"fedimovies-config",
|
||||
"fedimovies-models",
|
||||
"fedimovies-utils",
|
||||
"mitra-cli",
|
||||
"mitra-config",
|
||||
"mitra-utils",
|
||||
]
|
||||
default-members = [
|
||||
".",
|
||||
"fedimovies-cli",
|
||||
"fedimovies-config",
|
||||
"fedimovies-models",
|
||||
"fedimovies-utils",
|
||||
"mitra-cli",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
fedimovies-config = { path = "fedimovies-config" }
|
||||
fedimovies-models = { path = "fedimovies-models" }
|
||||
fedimovies-utils = { path = "fedimovies-utils" }
|
||||
mitra-config = { path = "mitra-config" }
|
||||
mitra-utils = { path = "mitra-utils" }
|
||||
|
||||
# Used to handle incoming HTTP requests
|
||||
actix-cors = "0.6.4"
|
||||
actix-cors = "0.6.2"
|
||||
actix-files = "0.6.2"
|
||||
actix-web = "4.3.1"
|
||||
actix-web = "4.1.0"
|
||||
actix-web-httpauth = "0.8.0"
|
||||
# Used for catching errors
|
||||
anyhow = "1.0.58"
|
||||
|
@ -41,6 +36,9 @@ anyhow = "1.0.58"
|
|||
base64 = "0.13.0"
|
||||
# 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
|
||||
|
@ -48,16 +46,22 @@ 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.5.3"
|
||||
ed25519 = "1.5.2"
|
||||
blake2 = "0.10.5"
|
||||
# Used to query Monero node
|
||||
monero-rpc = "0.3.2"
|
||||
# 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 for managing database migrations
|
||||
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
|
||||
# Used for making async HTTP requests
|
||||
reqwest = { version = "0.11.13", features = ["json", "multipart", "socks"] }
|
||||
reqwest = { version = "0.11.10", features = ["json", "multipart", "socks"] }
|
||||
# Used for working with RSA keys
|
||||
rsa = "0.5.0"
|
||||
# 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.89"
|
||||
|
@ -68,18 +72,25 @@ siwe = "0.4.0"
|
|||
# Used for creating error types
|
||||
thiserror = "1.0.37"
|
||||
# Async runtime
|
||||
tokio = { version = "=1.20.4", features = ["macros"] }
|
||||
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" }
|
||||
# Used to work with URLs
|
||||
url = "2.2.2"
|
||||
# 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]
|
||||
fedimovies-config = { path = "fedimovies-config", features = ["test-utils"] }
|
||||
fedimovies-models = { path = "fedimovies-models", features = ["test-utils"] }
|
||||
fedimovies-utils = { path = "fedimovies-utils", features = ["test-utils"] }
|
||||
|
||||
mitra-config = { path = "mitra-config", features = ["test-utils"] }
|
||||
mitra-utils = { path = "mitra-utils", features = ["test-utils"] }
|
||||
serial_test = "0.7.0"
|
||||
|
||||
[features]
|
||||
production = ["fedimovies-config/production"]
|
||||
production = ["mitra-config/production"]
|
||||
|
|
|
@ -86,10 +86,6 @@ 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
|
||||
|
|
113
README.md
113
README.md
|
@ -1,38 +1,47 @@
|
|||
# FediMovies
|
||||
[](https://ci.caric.io/FediMovies/fedimovies)
|
||||
# Mitra
|
||||
|
||||
Lively federated movies reviews platform.
|
||||
Federated micro-blogging platform and content subscription service.
|
||||
|
||||
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
|
||||
Features:
|
||||
Subscriptions provide a way to receive monthly payments from subscribers and to publish private content made exclusively for them.
|
||||
|
||||
- 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.
|
||||
Supported payment methods:
|
||||
|
||||
## Instances
|
||||
- [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).
|
||||
|
||||
- [FediList](http://demo.fedilist.com/instance?software=fedimovies)
|
||||
- [Fediverse Observer](https://fedimovies.fediverse.observer/list)
|
||||
Other features:
|
||||
|
||||
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
|
||||
- [Sign-in with a wallet](https://eips.ethereum.org/EIPS/eip-4361).
|
||||
- Account migrations (from one server to another).
|
||||
- Donation buttons.
|
||||
|
||||
Follow: [@mitra@mitra.social](https://mitra.social/@mitra)
|
||||
|
||||
Matrix chat: [#mitra:halogen.city](https://matrix.to/#/#mitra:halogen.city)
|
||||
|
||||
Network stats: [FediList](http://demo.fedilist.com/instance?software=mitra) / [Fediverse Observer](https://mitra.fediverse.observer/list)
|
||||
|
||||
Demo instance: https://public.mitra.social/ ([invite-only](https://public.mitra.social/about))
|
||||
|
||||
## Code
|
||||
|
||||
Server: https://code.caric.io/reef/reef (this repo)
|
||||
Server: https://codeberg.org/silverpill/mitra (this repo)
|
||||
|
||||
Web client:
|
||||
Web client: https://codeberg.org/silverpill/mitra-web
|
||||
|
||||
Ethereum contracts: https://codeberg.org/silverpill/mitra-contracts
|
||||
|
||||
## Requirements
|
||||
|
||||
- Rust 1.57+ (when building from source)
|
||||
- Rust 1.56+ (when building from source)
|
||||
- PostgreSQL 12+
|
||||
|
||||
Optional:
|
||||
|
||||
- Monero node and Monero wallet service
|
||||
- Ethereum node
|
||||
- IPFS node (see [guide](./docs/ipfs.md))
|
||||
|
||||
## Installation
|
||||
|
@ -45,57 +54,71 @@ Run:
|
|||
cargo build --release --features production
|
||||
```
|
||||
|
||||
This command will produce two binaries in `target/release` directory, `fedimovies` and `fedimoviesctl`.
|
||||
This command will produce two binaries in `target/release` directory, `mitra` and `mitractl`.
|
||||
|
||||
Install PostgreSQL and create the database:
|
||||
|
||||
```sql
|
||||
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
||||
CREATE DATABASE mitra OWNER mitra;
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
Put any static files into the directory specified in configuration file. Building instructions for `mitra-web` frontend can be found at https://codeberg.org/silverpill/mitra-web#project-setup.
|
||||
|
||||
Start Fedimovies:
|
||||
Start Mitra:
|
||||
|
||||
```shell
|
||||
./fedimovies
|
||||
./mitra
|
||||
```
|
||||
|
||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
|
||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/mitra.nginx).
|
||||
|
||||
To run Fedimovies as a systemd service, check out the [systemd unit file example](./contrib/fedimovies.service).
|
||||
To run Mitra as a systemd service, check out the [systemd unit file example](./contrib/mitra.service).
|
||||
|
||||
### Debian package
|
||||
|
||||
Download and install Fedimovies package:
|
||||
Download and install Mitra package:
|
||||
|
||||
```shell
|
||||
dpkg -i fedimovies.deb
|
||||
dpkg -i mitra.deb
|
||||
```
|
||||
|
||||
Install PostgreSQL and create the database:
|
||||
|
||||
```sql
|
||||
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
||||
CREATE DATABASE mitra OWNER mitra;
|
||||
```
|
||||
|
||||
Open configuration file `/etc/fedimovies/config.yaml` and configure the instance.
|
||||
Open configuration file `/etc/mitra/config.yaml` and configure the instance.
|
||||
|
||||
Start Fedimovies:
|
||||
Start Mitra:
|
||||
|
||||
```shell
|
||||
systemctl start fedimovies
|
||||
systemctl start mitra
|
||||
```
|
||||
|
||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
|
||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/mitra.nginx).
|
||||
|
||||
### Tor federation
|
||||
### Monero
|
||||
|
||||
See [guide](./docs/onion.md).
|
||||
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. Add `disable-rpc-login=1` to your `monero-wallet-rpc` config (currently RPC auth is not supported in Mitra).
|
||||
|
||||
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.
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -110,7 +133,15 @@ docker-compose up -d
|
|||
Test connection:
|
||||
|
||||
```shell
|
||||
psql -h localhost -p 55432 -U fedimovies fedimovies
|
||||
psql -h localhost -p 55432 -U mitra mitra
|
||||
```
|
||||
|
||||
### Start Monero node and wallet server
|
||||
|
||||
(this step is optional)
|
||||
|
||||
```shell
|
||||
docker-compose --profile monero up -d
|
||||
```
|
||||
|
||||
### Run web service
|
||||
|
@ -130,7 +161,7 @@ cargo run
|
|||
### Run CLI
|
||||
|
||||
```shell
|
||||
cargo run --bin fedimoviesctl
|
||||
cargo run --bin mitractl
|
||||
```
|
||||
|
||||
### Run linter
|
||||
|
@ -151,16 +182,20 @@ See [FEDERATION.md](./FEDERATION.md)
|
|||
|
||||
## Client API
|
||||
|
||||
Most methods are similar to Mastodon API, but Fedimovies is not fully compatible.
|
||||
Most methods are similar to Mastodon API, but Mitra is not fully compatible.
|
||||
|
||||
[OpenAPI spec](./docs/openapi.yaml)
|
||||
|
||||
## CLI
|
||||
|
||||
`fedimoviesctl` is a command-line tool for performing instance maintenance.
|
||||
`mitractl` is a command-line tool for performing instance maintenance.
|
||||
|
||||
[Documentation](./docs/fedimoviesctl.md)
|
||||
[Documentation](./docs/mitractl.md)
|
||||
|
||||
## License
|
||||
|
||||
[AGPL-3.0](./LICENSE)
|
||||
|
||||
## Support
|
||||
|
||||
Monero: 8Ahza5RM4JQgtdqvpcF1U628NN5Q87eryXQad3Fy581YWTZU8o3EMbtScuioQZSkyNNEEE1Lkj2cSbG4VnVYCW5L1N4os5p
|
||||
|
|
|
@ -14,5 +14,28 @@ instance_description: My instance
|
|||
registration:
|
||||
type: open
|
||||
|
||||
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
|
||||
|
||||
ipfs_api_url: 'http://127.0.0.1:5001'
|
||||
ipfs_gateway_url: 'http://127.0.0.1:8001'
|
||||
|
|
30
contracts/IERC165.json
Normal file
30
contracts/IERC165.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
233
contracts/IERC20Metadata.json
Normal file
233
contracts/IERC20Metadata.json
Normal file
|
@ -0,0 +1,233 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
341
contracts/IERC721Metadata.json
Normal file
341
contracts/IERC721Metadata.json
Normal file
|
@ -0,0 +1,341 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
30
contracts/IGate.json
Normal file
30
contracts/IGate.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
57
contracts/IMinter.json
Normal file
57
contracts/IMinter.json
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
87
contracts/ISubscription.json
Normal file
87
contracts/ISubscription.json
Normal file
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
137
contracts/ISubscriptionAdapter.json
Normal file
137
contracts/ISubscriptionAdapter.json
Normal file
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"_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": {}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
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"]
|
|
@ -36,7 +36,7 @@ server {
|
|||
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;
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
# Frontend
|
||||
|
|
|
@ -32,7 +32,7 @@ server {
|
|||
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
|
||||
client_max_body_size 40M;
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8383;
|
||||
|
|
|
@ -20,19 +20,18 @@ instance_uri: https://example.tld
|
|||
instance_title: example
|
||||
instance_short_description: my instance
|
||||
# Long description can contain markdown syntax
|
||||
instance_description: |
|
||||
# My instance
|
||||
Welcome!
|
||||
instance_description: my instance
|
||||
|
||||
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!'
|
||||
|
||||
# Proxy for outgoing requests
|
||||
#proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
|
||||
# Limits
|
||||
#limits:
|
||||
# media:
|
||||
|
@ -41,21 +40,38 @@ registration:
|
|||
# character_limit: 2000
|
||||
|
||||
# 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'
|
||||
#retention:
|
||||
# extraneous_posts: 50
|
||||
# empty_profiles: 150
|
||||
|
||||
# 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: monero:mainnet
|
||||
# node_url: 'http://opennode.xmr-tw.org:18089'
|
||||
# wallet_url: 'http://127.0.0.1:18083'
|
||||
# wallet_name: null
|
||||
# wallet_password: null
|
||||
# - 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
|
||||
|
||||
# IPFS integration
|
||||
#ipfs_api_url: 'http://127.0.0.1:5001'
|
||||
# IPFS gateway (for clients)
|
||||
|
|
|
@ -20,10 +20,10 @@ Generate RSA private key:
|
|||
mitractl generate-rsa-key
|
||||
```
|
||||
|
||||
Generate invite code (note is optional):
|
||||
Generate invite code:
|
||||
|
||||
```shell
|
||||
mitractl generate-invite-code <note>
|
||||
mitractl generate-invite-code
|
||||
```
|
||||
|
||||
List generated invites:
|
||||
|
@ -32,19 +32,13 @@ List generated invites:
|
|||
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).
|
||||
Change user's role:
|
||||
|
||||
```shell
|
||||
mitractl set-role <user-id> <role-name>
|
||||
|
@ -86,12 +80,6 @@ Delete empty remote profiles:
|
|||
mitractl delete-empty-profiles 100
|
||||
```
|
||||
|
||||
Delete unused remote emojis:
|
||||
|
||||
```shell
|
||||
mitractl prune-remote-emojis
|
||||
```
|
||||
|
||||
Import custom emoji from another instance:
|
||||
|
||||
```shell
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
# 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.
|
|
@ -352,12 +352,6 @@ 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.
|
||||
|
@ -596,37 +590,6 @@ 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.
|
||||
|
@ -780,29 +743,6 @@ 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.
|
||||
|
@ -826,28 +766,6 @@ paths:
|
|||
$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
|
||||
|
@ -959,10 +877,6 @@ 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
|
||||
|
@ -1502,11 +1416,6 @@ 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
|
||||
|
@ -1531,10 +1440,6 @@ components:
|
|||
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:
|
||||
|
@ -1543,19 +1448,6 @@ components:
|
|||
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:
|
||||
|
@ -1583,7 +1475,6 @@ components:
|
|||
- unknown
|
||||
- image
|
||||
- video
|
||||
- audio
|
||||
url:
|
||||
description: The location of the original full-size attachment.
|
||||
type: string
|
||||
|
@ -1868,8 +1759,6 @@ components:
|
|||
enum:
|
||||
- create_follow_request
|
||||
- create_post
|
||||
- delete_any_post
|
||||
- delete_any_profile
|
||||
- manage_subscription_options
|
||||
Signature:
|
||||
type: object
|
||||
|
@ -1911,13 +1800,6 @@ 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
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
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!(),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
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,
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
[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 = []
|
|
@ -1,2 +0,0 @@
|
|||
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;
|
|
@ -1,5 +0,0 @@
|
|||
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)
|
||||
);
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE actor_profile ADD COLUMN emojis JSONB NOT NULL DEFAULT '[]';
|
|
@ -1,4 +0,0 @@
|
|||
CREATE TABLE internal_property (
|
||||
property_name VARCHAR(100) PRIMARY KEY,
|
||||
property_value JSONB NOT NULL
|
||||
);
|
|
@ -1,18 +0,0 @@
|
|||
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;
|
|
@ -1,2 +0,0 @@
|
|||
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;
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE actor_profile ADD COLUMN aliases JSONB NOT NULL DEFAULT '[]';
|
|
@ -1,5 +0,0 @@
|
|||
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);
|
|
@ -1,6 +0,0 @@
|
|||
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);
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE user_account ADD COLUMN client_config JSONB NOT NULL DEFAULT '{}';
|
|
@ -1,2 +0,0 @@
|
|||
ALTER TABLE post ADD COLUMN is_sensitive BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE post ALTER COLUMN is_sensitive DROP DEFAULT;
|
|
@ -1,113 +0,0 @@
|
|||
use openssl::ssl::{SslConnector, SslMethod};
|
||||
use postgres_openssl::MakeTlsConnector;
|
||||
use std::path::Path;
|
||||
use tokio_postgres::config::Config as DatabaseConfig;
|
||||
use tokio_postgres::error::{Error as PgError, SqlState};
|
||||
|
||||
pub mod int_enum;
|
||||
pub mod json_macro;
|
||||
pub mod migrate;
|
||||
pub mod query_macro;
|
||||
|
||||
#[cfg(feature = "test-utils")]
|
||||
pub mod test_utils;
|
||||
|
||||
pub type DbPool = deadpool_postgres::Pool;
|
||||
pub use tokio_postgres::GenericClient as DatabaseClient;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("database type error")]
|
||||
pub struct DatabaseTypeError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum DatabaseError {
|
||||
#[error("database pool error")]
|
||||
DatabasePoolError(#[from] deadpool_postgres::PoolError),
|
||||
|
||||
#[error("database query error")]
|
||||
DatabaseQueryError(#[from] postgres_query::Error),
|
||||
|
||||
#[error("database client error")]
|
||||
DatabaseClientError(#[from] tokio_postgres::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
DatabaseTypeError(#[from] DatabaseTypeError),
|
||||
|
||||
#[error("{0} not found")]
|
||||
NotFound(&'static str), // object type
|
||||
|
||||
#[error("{0} already exists")]
|
||||
AlreadyExists(&'static str), // object type
|
||||
}
|
||||
|
||||
pub async fn create_database_client(
|
||||
db_config: &DatabaseConfig,
|
||||
ca_file_path: Option<&Path>,
|
||||
) -> tokio_postgres::Client {
|
||||
let client = if let Some(ca_file_path) = ca_file_path {
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
log::debug!("Using TLS CA file: {}", ca_file_path.display());
|
||||
builder.set_ca_file(ca_file_path).unwrap();
|
||||
let connector = MakeTlsConnector::new(builder.build());
|
||||
let (client, connection) = db_config.connect(connector).await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = connection.await {
|
||||
log::error!("connection with tls error: {}", err);
|
||||
};
|
||||
});
|
||||
client
|
||||
} else {
|
||||
let (client, connection) = db_config.connect(tokio_postgres::NoTls).await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = connection.await {
|
||||
log::error!("connection error: {}", err);
|
||||
};
|
||||
});
|
||||
client
|
||||
};
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
pub fn create_pool(database_url: &str, ca_file_path: Option<&Path>, pool_size: usize) -> DbPool {
|
||||
let manager = if let Some(ca_file_path) = ca_file_path {
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
log::info!("Using TLS CA file: {}", ca_file_path.display());
|
||||
builder.set_ca_file(ca_file_path).unwrap();
|
||||
let connector = MakeTlsConnector::new(builder.build());
|
||||
deadpool_postgres::Manager::new(
|
||||
database_url.parse().expect("invalid database URL"),
|
||||
connector,
|
||||
)
|
||||
} else {
|
||||
deadpool_postgres::Manager::new(
|
||||
database_url.parse().expect("invalid database URL"),
|
||||
tokio_postgres::NoTls,
|
||||
)
|
||||
};
|
||||
|
||||
DbPool::builder(manager)
|
||||
.max_size(pool_size)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_database_client(
|
||||
db_pool: &DbPool,
|
||||
) -> Result<deadpool_postgres::Client, DatabaseError> {
|
||||
// Returns wrapped client
|
||||
// https://github.com/bikeshedder/deadpool/issues/56
|
||||
let client = db_pool.get().await?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub fn catch_unique_violation(object_type: &'static str) -> impl Fn(PgError) -> DatabaseError {
|
||||
move |err| {
|
||||
if let Some(code) = err.code() {
|
||||
if code == &SqlState::UNIQUE_VIOLATION {
|
||||
return DatabaseError::AlreadyExists(object_type);
|
||||
};
|
||||
};
|
||||
err.into()
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
|
||||
pub async fn create_instance(
|
||||
db_client: &impl DatabaseClient,
|
||||
hostname: &str,
|
||||
) -> Result<(), DatabaseError> {
|
||||
db_client
|
||||
.execute(
|
||||
"
|
||||
INSERT INTO instance VALUES ($1)
|
||||
ON CONFLICT DO NOTHING
|
||||
",
|
||||
&[&hostname],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_peers(db_client: &impl DatabaseClient) -> Result<Vec<String>, DatabaseError> {
|
||||
let rows = db_client
|
||||
.query(
|
||||
"
|
||||
SELECT instance.hostname FROM instance
|
||||
",
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let peers = rows
|
||||
.iter()
|
||||
.map(|row| row.try_get("hostname"))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(peers)
|
||||
}
|
||||
|
||||
pub async fn get_peer_count(db_client: &impl DatabaseClient) -> Result<i64, DatabaseError> {
|
||||
let row = db_client
|
||||
.query_one("SELECT count(instance) FROM instance", &[])
|
||||
.await?;
|
||||
let count = row.try_get("count")?;
|
||||
Ok(count)
|
||||
}
|
|
@ -1,265 +0,0 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
use crate::posts::{
|
||||
helpers::{add_related_posts, add_user_actions},
|
||||
queries::{RELATED_ATTACHMENTS, RELATED_EMOJIS, RELATED_LINKS, RELATED_MENTIONS, RELATED_TAGS},
|
||||
};
|
||||
|
||||
use super::types::{EventType, Notification};
|
||||
|
||||
async fn create_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
post_id: Option<&Uuid>,
|
||||
event_type: EventType,
|
||||
) -> Result<(), DatabaseError> {
|
||||
db_client
|
||||
.execute(
|
||||
"
|
||||
INSERT INTO notification (
|
||||
sender_id,
|
||||
recipient_id,
|
||||
post_id,
|
||||
event_type
|
||||
)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
",
|
||||
&[&sender_id, &recipient_id, &post_id, &event_type],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
notification_id: i32,
|
||||
) -> Result<(), DatabaseError> {
|
||||
db_client
|
||||
.execute(
|
||||
"
|
||||
DELETE FROM notification
|
||||
WHERE id = $1
|
||||
",
|
||||
&[¬ification_id],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_follow_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(db_client, sender_id, recipient_id, None, EventType::Follow).await
|
||||
}
|
||||
|
||||
pub async fn create_reply_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
post_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(
|
||||
db_client,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
Some(post_id),
|
||||
EventType::Reply,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_reaction_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
post_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(
|
||||
db_client,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
Some(post_id),
|
||||
EventType::Reaction,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_mention_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
post_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(
|
||||
db_client,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
Some(post_id),
|
||||
EventType::Mention,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_repost_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
post_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(
|
||||
db_client,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
Some(post_id),
|
||||
EventType::Repost,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_subscription_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(
|
||||
db_client,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
None,
|
||||
EventType::Subscription,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_subscription_expiration_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(
|
||||
db_client,
|
||||
sender_id,
|
||||
recipient_id,
|
||||
None,
|
||||
EventType::SubscriptionExpiration,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_move_notification(
|
||||
db_client: &impl DatabaseClient,
|
||||
sender_id: &Uuid,
|
||||
recipient_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
create_notification(db_client, sender_id, recipient_id, None, EventType::Move).await
|
||||
}
|
||||
|
||||
pub async fn get_notifications(
|
||||
db_client: &impl DatabaseClient,
|
||||
recipient_id: &Uuid,
|
||||
max_id: Option<i32>,
|
||||
limit: u16,
|
||||
) -> Result<Vec<Notification>, DatabaseError> {
|
||||
let statement = format!(
|
||||
"
|
||||
SELECT
|
||||
notification, sender, post, post_author, recipient,
|
||||
{related_attachments},
|
||||
{related_mentions},
|
||||
{related_tags},
|
||||
{related_links},
|
||||
{related_emojis}
|
||||
FROM notification
|
||||
JOIN actor_profile AS sender
|
||||
ON notification.sender_id = sender.id
|
||||
LEFT JOIN post
|
||||
ON notification.post_id = post.id
|
||||
LEFT JOIN actor_profile AS post_author
|
||||
ON post.author_id = post_author.id
|
||||
LEFT JOIN actor_profile AS recipient
|
||||
ON notification.recipient_id = recipient.id
|
||||
WHERE
|
||||
recipient_id = $1
|
||||
AND ($2::integer IS NULL OR notification.id < $2)
|
||||
ORDER BY notification.id DESC
|
||||
LIMIT $3
|
||||
",
|
||||
related_attachments = RELATED_ATTACHMENTS,
|
||||
related_mentions = RELATED_MENTIONS,
|
||||
related_tags = RELATED_TAGS,
|
||||
related_links = RELATED_LINKS,
|
||||
related_emojis = RELATED_EMOJIS,
|
||||
);
|
||||
let rows = db_client
|
||||
.query(&statement, &[&recipient_id, &max_id, &i64::from(limit)])
|
||||
.await?;
|
||||
let mut notifications: Vec<Notification> = rows
|
||||
.iter()
|
||||
.map(Notification::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
add_related_posts(
|
||||
db_client,
|
||||
notifications
|
||||
.iter_mut()
|
||||
.filter_map(|item| item.post.as_mut())
|
||||
.collect(),
|
||||
)
|
||||
.await?;
|
||||
add_user_actions(
|
||||
db_client,
|
||||
recipient_id,
|
||||
notifications
|
||||
.iter_mut()
|
||||
.filter_map(|item| item.post.as_mut())
|
||||
.collect(),
|
||||
)
|
||||
.await?;
|
||||
Ok(notifications)
|
||||
}
|
||||
|
||||
pub async fn get_mention_notifications(
|
||||
db_client: &impl DatabaseClient,
|
||||
limit: u16,
|
||||
) -> Result<Vec<Notification>, DatabaseError> {
|
||||
let statement = format!(
|
||||
"
|
||||
SELECT
|
||||
notification, sender, post, post_author, recipient,
|
||||
{related_attachments},
|
||||
{related_mentions},
|
||||
{related_tags},
|
||||
{related_links},
|
||||
{related_emojis}
|
||||
FROM notification
|
||||
JOIN actor_profile AS sender
|
||||
ON notification.sender_id = sender.id
|
||||
LEFT JOIN post
|
||||
ON notification.post_id = post.id
|
||||
LEFT JOIN actor_profile AS post_author
|
||||
ON post.author_id = post_author.id
|
||||
LEFT JOIN actor_profile AS recipient
|
||||
ON notification.recipient_id = recipient.id
|
||||
WHERE
|
||||
event_type = $1
|
||||
ORDER BY notification.id DESC
|
||||
LIMIT $2
|
||||
",
|
||||
related_attachments = RELATED_ATTACHMENTS,
|
||||
related_mentions = RELATED_MENTIONS,
|
||||
related_tags = RELATED_TAGS,
|
||||
related_links = RELATED_LINKS,
|
||||
related_emojis = RELATED_EMOJIS,
|
||||
);
|
||||
let rows = db_client
|
||||
.query(&statement, &[&EventType::Mention, &i64::from(limit)])
|
||||
.await?;
|
||||
let notifications: Vec<Notification> = rows
|
||||
.iter()
|
||||
.map(Notification::try_from)
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(notifications)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
|
||||
use super::queries::{get_profile_by_remote_actor_id, search_profiles_by_did_only};
|
||||
use super::types::DbActorProfile;
|
||||
|
||||
pub async fn find_declared_aliases(
|
||||
db_client: &impl DatabaseClient,
|
||||
profile: &DbActorProfile,
|
||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||
let mut results = vec![];
|
||||
for actor_id in profile.aliases.clone().into_actor_ids() {
|
||||
let alias = match get_profile_by_remote_actor_id(db_client, &actor_id).await {
|
||||
Ok(profile) => profile,
|
||||
// Ignore unknown profiles
|
||||
Err(DatabaseError::NotFound(_)) => continue,
|
||||
Err(other_error) => return Err(other_error),
|
||||
};
|
||||
results.push(alias);
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn find_verified_aliases(
|
||||
db_client: &impl DatabaseClient,
|
||||
profile: &DbActorProfile,
|
||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||
let mut results = vec![];
|
||||
for identity_proof in profile.identity_proofs.inner() {
|
||||
let aliases = search_profiles_by_did_only(db_client, &identity_proof.issuer).await?;
|
||||
for alias in aliases {
|
||||
if alias.id == profile.id {
|
||||
continue;
|
||||
};
|
||||
results.push(alias);
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub mod helpers;
|
||||
pub mod queries;
|
||||
pub mod types;
|
|
@ -1,72 +0,0 @@
|
|||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::database::{DatabaseClient, DatabaseError, DatabaseTypeError};
|
||||
|
||||
pub async fn set_internal_property(
|
||||
db_client: &impl DatabaseClient,
|
||||
name: &str,
|
||||
value: &impl Serialize,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let value_json = serde_json::to_value(value).map_err(|_| DatabaseTypeError)?;
|
||||
db_client
|
||||
.execute(
|
||||
"
|
||||
INSERT INTO internal_property (property_name, property_value)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (property_name) DO UPDATE
|
||||
SET property_value = $2
|
||||
",
|
||||
&[&name, &value_json],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_internal_property<T: DeserializeOwned>(
|
||||
db_client: &impl DatabaseClient,
|
||||
name: &str,
|
||||
) -> Result<Option<T>, DatabaseError> {
|
||||
let maybe_row = db_client
|
||||
.query_opt(
|
||||
"
|
||||
SELECT property_value
|
||||
FROM internal_property
|
||||
WHERE property_name = $1
|
||||
",
|
||||
&[&name],
|
||||
)
|
||||
.await?;
|
||||
let maybe_value = match maybe_row {
|
||||
Some(row) => {
|
||||
let value_json: JsonValue = row.try_get("property_value")?;
|
||||
let value: T = serde_json::from_value(value_json).map_err(|_| DatabaseTypeError)?;
|
||||
Some(value)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
Ok(maybe_value)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database::test_utils::create_test_database;
|
||||
use serial_test::serial;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_set_internal_property() {
|
||||
let db_client = &create_test_database().await;
|
||||
let name = "myproperty";
|
||||
let value = 100;
|
||||
set_internal_property(db_client, name, &value)
|
||||
.await
|
||||
.unwrap();
|
||||
let db_value: u32 = get_internal_property(db_client, name)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(db_value, value);
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
pub mod queries;
|
9
justfile
9
justfile
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env -S just --justfile
|
||||
|
||||
build-release:
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
cp target/x86_64-unknown-linux-gnu/release/fedimovies build/fedimovies
|
||||
cp target/x86_64-unknown-linux-gnu/release/fedimoviesctl build/fedimoviesctl
|
||||
|
||||
deploy: build-release
|
||||
fly deploy
|
|
@ -1,8 +1,3 @@
|
|||
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,
|
||||
|
@ -26,18 +21,15 @@ CREATE TABLE actor_profile (
|
|||
bio_source TEXT,
|
||||
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(2000) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
||||
actor_id VARCHAR(200) 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,
|
||||
|
@ -46,9 +38,7 @@ CREATE TABLE actor_profile (
|
|||
|
||||
CREATE TABLE user_invite_code (
|
||||
code VARCHAR(100) PRIMARY KEY,
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
note VARCHAR(200),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE TABLE user_account (
|
||||
|
@ -58,7 +48,6 @@ CREATE TABLE user_account (
|
|||
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()
|
||||
);
|
||||
|
||||
|
@ -67,7 +56,7 @@ CREATE TABLE oauth_application (
|
|||
app_name VARCHAR(100) NOT NULL,
|
||||
website VARCHAR(100),
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
redirect_uri VARCHAR(2000) 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
|
||||
|
@ -96,18 +85,16 @@ 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),
|
||||
CHECK (source_id != target_id)
|
||||
UNIQUE (source_id, target_id, relationship_type)
|
||||
);
|
||||
|
||||
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,
|
||||
activity_id VARCHAR(250) UNIQUE,
|
||||
request_status SMALLINT NOT NULL,
|
||||
UNIQUE (source_id, target_id),
|
||||
CHECK (source_id != target_id)
|
||||
UNIQUE (source_id, target_id)
|
||||
);
|
||||
|
||||
CREATE TABLE post (
|
||||
|
@ -116,12 +103,11 @@ 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,
|
||||
visibility SMALLINT NOT NULL,
|
||||
is_sensitive BOOLEAN NOT NULL,
|
||||
visilibity SMALLINT 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(2000) UNIQUE,
|
||||
object_id VARCHAR(200) UNIQUE,
|
||||
ipfs_cid VARCHAR(200),
|
||||
token_id INTEGER,
|
||||
token_tx_id VARCHAR(200),
|
||||
|
@ -134,7 +120,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(2000) UNIQUE,
|
||||
activity_id VARCHAR(250) UNIQUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
UNIQUE (author_id, post_id)
|
||||
);
|
||||
|
@ -170,8 +156,7 @@ 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),
|
||||
CHECK (source_id != target_id)
|
||||
PRIMARY KEY (source_id, target_id)
|
||||
);
|
||||
|
||||
CREATE TABLE emoji (
|
||||
|
@ -179,7 +164,7 @@ CREATE TABLE emoji (
|
|||
emoji_name VARCHAR(100) NOT NULL,
|
||||
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
|
||||
image JSONB NOT NULL,
|
||||
object_id VARCHAR(2000) UNIQUE,
|
||||
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))
|
||||
|
@ -191,12 +176,6 @@ CREATE TABLE post_emoji (
|
|||
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 (
|
||||
id SERIAL PRIMARY KEY,
|
||||
sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
|
@ -224,8 +203,7 @@ 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),
|
||||
CHECK (sender_id != recipient_id)
|
||||
UNIQUE (chain_id, payment_address)
|
||||
);
|
||||
|
||||
CREATE TABLE subscription (
|
||||
|
@ -236,6 +214,5 @@ 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),
|
||||
CHECK (sender_id != recipient_id)
|
||||
UNIQUE (sender_id, recipient_id)
|
||||
);
|
|
@ -1,19 +1,18 @@
|
|||
[package]
|
||||
name = "fedimovies-cli"
|
||||
version = "1.22.0"
|
||||
name = "mitra-cli"
|
||||
version = "1.14.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
rust-version = "1.56"
|
||||
|
||||
[[bin]]
|
||||
name = "fedimoviesctl"
|
||||
name = "mitractl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
fedimovies-config = { path = "../fedimovies-config" }
|
||||
fedimovies-models = { path = "../fedimovies-models" }
|
||||
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||
fedimovies = { path = ".." }
|
||||
mitra-config = { path = "../mitra-config" }
|
||||
mitra-utils = { path = "../mitra-utils" }
|
||||
mitra = { path = ".." }
|
||||
|
||||
# Used for catching errors
|
||||
anyhow = "1.0.58"
|
||||
|
@ -22,6 +21,6 @@ clap = { version = "3.2.18", default-features = false, features = ["std", "deriv
|
|||
# Used for logging
|
||||
log = "0.4.14"
|
||||
# Async runtime
|
||||
tokio = { version = "1.20.4", features = ["macros"] }
|
||||
tokio = { version = "1.17.0", features = ["macros"] }
|
||||
# Used to work with UUIDs
|
||||
uuid = "1.1.2"
|
|
@ -1,39 +1,58 @@
|
|||
use anyhow::Error;
|
||||
use anyhow::{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 mitra::activitypub::{
|
||||
actors::helpers::update_remote_profile,
|
||||
builders::delete_note::prepare_delete_note,
|
||||
builders::delete_person::prepare_delete_person,
|
||||
fetcher::fetchers::fetch_actor,
|
||||
};
|
||||
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::{
|
||||
use mitra::database::DatabaseClient;
|
||||
use mitra::ethereum::{
|
||||
signatures::generate_ecdsa_key,
|
||||
sync::save_current_block_number,
|
||||
utils::key_to_ethereum_address,
|
||||
};
|
||||
use mitra::media::remove_files;
|
||||
use mitra::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,
|
||||
create_emoji,
|
||||
delete_emoji,
|
||||
get_emoji_by_name_and_hostname,
|
||||
},
|
||||
emojis::validators::EMOJI_LOCAL_MAX_SIZE,
|
||||
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,
|
||||
delete_profile,
|
||||
find_empty_profiles,
|
||||
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,
|
||||
create_invite_code,
|
||||
get_invite_codes,
|
||||
get_user_by_id,
|
||||
set_user_password,
|
||||
set_user_role,
|
||||
},
|
||||
users::types::UserCreateData,
|
||||
users::types::Role,
|
||||
};
|
||||
use fedimovies_utils::{
|
||||
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||
use mitra::monero::{
|
||||
helpers::check_expired_invoice,
|
||||
wallet::create_monero_wallet,
|
||||
};
|
||||
use mitra_config::Config;
|
||||
use mitra_utils::{
|
||||
crypto_rsa::{
|
||||
generate_rsa_key,
|
||||
serialize_private_key,
|
||||
},
|
||||
datetime::{days_before_now, get_min_datetime},
|
||||
passwords::hash_password,
|
||||
};
|
||||
|
@ -52,11 +71,9 @@ pub enum SubCommand {
|
|||
|
||||
GenerateInviteCode(GenerateInviteCode),
|
||||
ListInviteCodes(ListInviteCodes),
|
||||
CreateUser(CreateUser),
|
||||
SetPassword(SetPassword),
|
||||
SetRole(SetRole),
|
||||
RefetchActor(RefetchActor),
|
||||
ReadOutbox(ReadOutbox),
|
||||
DeleteProfile(DeleteProfile),
|
||||
DeletePost(DeletePost),
|
||||
DeleteEmoji(DeleteEmoji),
|
||||
|
@ -64,8 +81,6 @@ pub enum SubCommand {
|
|||
DeleteUnusedAttachments(DeleteUnusedAttachments),
|
||||
DeleteOrphanedFiles(DeleteOrphanedFiles),
|
||||
DeleteEmptyProfiles(DeleteEmptyProfiles),
|
||||
PruneRemoteEmojis(PruneRemoteEmojis),
|
||||
ListUnreachableActors(ListUnreachableActors),
|
||||
ImportEmoji(ImportEmoji),
|
||||
UpdateCurrentBlock(UpdateCurrentBlock),
|
||||
ResetSubscriptions(ResetSubscriptions),
|
||||
|
@ -91,19 +106,25 @@ pub struct GenerateEthereumAddress;
|
|||
|
||||
impl GenerateEthereumAddress {
|
||||
pub fn execute(&self) -> () {
|
||||
println!("dummy");
|
||||
let private_key = generate_ecdsa_key();
|
||||
let address = key_to_ethereum_address(&private_key);
|
||||
println!(
|
||||
"address {:?}; private key {}",
|
||||
address, private_key.display_secret(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate invite code
|
||||
#[derive(Parser)]
|
||||
pub struct GenerateInviteCode {
|
||||
note: Option<String>,
|
||||
}
|
||||
pub struct GenerateInviteCode;
|
||||
|
||||
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?;
|
||||
pub async fn execute(
|
||||
&self,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let invite_code = create_invite_code(db_client).await?;
|
||||
println!("generated invite code: {}", invite_code);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -114,49 +135,18 @@ impl GenerateInviteCode {
|
|||
pub struct ListInviteCodes;
|
||||
|
||||
impl ListInviteCodes {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
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,
|
||||
for code in invite_codes {
|
||||
println!("{}", code);
|
||||
};
|
||||
create_user(db_client, user_data).await?;
|
||||
println!("user created");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +159,10 @@ pub struct SetPassword {
|
|||
}
|
||||
|
||||
impl SetPassword {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
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
|
||||
|
@ -183,13 +176,15 @@ impl SetPassword {
|
|||
#[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)?;
|
||||
pub async fn execute(
|
||||
&self,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let role = Role::from_name(&self.role)?;
|
||||
set_user_role(db_client, &self.id, role).await?;
|
||||
println!("role changed");
|
||||
Ok(())
|
||||
|
@ -206,42 +201,25 @@ impl RefetchActor {
|
|||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?;
|
||||
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),
|
||||
&config.media_dir(),
|
||||
profile,
|
||||
actor,
|
||||
)
|
||||
.await?;
|
||||
).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 {
|
||||
|
@ -258,14 +236,15 @@ impl DeleteProfile {
|
|||
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?;
|
||||
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;
|
||||
deletion_queue.process(config).await;
|
||||
// Send Delete(Person) activities
|
||||
if let Some(activity) = maybe_delete_person {
|
||||
activity.enqueue(db_client).await?;
|
||||
activity.deliver().await?;
|
||||
};
|
||||
println!("profile deleted");
|
||||
Ok(())
|
||||
|
@ -288,15 +267,19 @@ impl DeletePost {
|
|||
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?;
|
||||
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;
|
||||
deletion_queue.process(config).await;
|
||||
// Send Delete(Note) activity
|
||||
if let Some(activity) = maybe_delete_note {
|
||||
activity.enqueue(db_client).await?;
|
||||
activity.deliver().await?;
|
||||
};
|
||||
println!("post deleted");
|
||||
Ok(())
|
||||
|
@ -316,10 +299,13 @@ impl DeleteEmoji {
|
|||
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 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;
|
||||
deletion_queue.process(config).await;
|
||||
println!("emoji deleted");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -341,9 +327,9 @@ impl DeleteExtraneousPosts {
|
|||
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;
|
||||
deletion_queue.process(config).await;
|
||||
println!("post {} deleted", post_id);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -361,8 +347,11 @@ impl DeleteUnusedAttachments {
|
|||
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;
|
||||
let deletion_queue = delete_unused_attachments(
|
||||
db_client,
|
||||
&created_before,
|
||||
).await?;
|
||||
deletion_queue.process(config).await;
|
||||
println!("unused attachments deleted");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -381,9 +370,10 @@ impl DeleteOrphanedFiles {
|
|||
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();
|
||||
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() {
|
||||
|
@ -411,59 +401,9 @@ impl DeleteEmptyProfiles {
|
|||
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;
|
||||
deletion_queue.process(config).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(())
|
||||
}
|
||||
}
|
||||
|
@ -481,8 +421,11 @@ impl ImportEmoji {
|
|||
_config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let emoji =
|
||||
get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?;
|
||||
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(());
|
||||
|
@ -494,8 +437,7 @@ impl ImportEmoji {
|
|||
emoji.image,
|
||||
None,
|
||||
&get_min_datetime(),
|
||||
)
|
||||
.await?;
|
||||
).await?;
|
||||
println!("added emoji to local collection");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -510,9 +452,10 @@ pub struct UpdateCurrentBlock {
|
|||
impl UpdateCurrentBlock {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
config: &Config,
|
||||
_db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
save_current_block_number(&config.storage_dir, self.number)?;
|
||||
println!("current block updated");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -547,7 +490,18 @@ pub struct CreateMoneroWallet {
|
|||
}
|
||||
|
||||
impl CreateMoneroWallet {
|
||||
pub async fn execute(&self, _config: &Config) -> Result<(), Error> {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
) -> Result<(), Error> {
|
||||
let monero_config = config.blockchain()
|
||||
.and_then(|conf| conf.monero_config())
|
||||
.ok_or(anyhow!("monero configuration not found"))?;
|
||||
create_monero_wallet(
|
||||
monero_config,
|
||||
self.name.clone(),
|
||||
self.password.clone(),
|
||||
).await?;
|
||||
println!("wallet created");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -562,9 +516,17 @@ pub struct CheckExpiredInvoice {
|
|||
impl CheckExpiredInvoice {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
_db_client: &impl DatabaseClient,
|
||||
config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let monero_config = config.blockchain()
|
||||
.and_then(|conf| conf.monero_config())
|
||||
.ok_or(anyhow!("monero configuration not found"))?;
|
||||
check_expired_invoice(
|
||||
monero_config,
|
||||
db_client,
|
||||
&self.id,
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
53
mitra-cli/src/main.rs
Normal file
53
mitra-cli/src/main.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use clap::Parser;
|
||||
|
||||
use mitra::database::create_database_client;
|
||||
use mitra::database::migrate::apply_migrations;
|
||||
use mitra::logger::configure_logger;
|
||||
use mitra_config::parse_config;
|
||||
|
||||
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).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::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::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::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(),
|
||||
_ => panic!(),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "fedimovies-config"
|
||||
version = "1.22.0"
|
||||
name = "mitra-config"
|
||||
version = "1.14.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
rust-version = "1.56"
|
||||
|
||||
[dependencies]
|
||||
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||
mitra-utils = { path = "../mitra-utils" }
|
||||
|
||||
# Used to read .env files
|
||||
dotenv = "0.15.0"
|
87
mitra-config/src/blockchain.rs
Normal file
87
mitra-config/src/blockchain.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use mitra_utils::caip2::{ChainId, ChainIdError};
|
||||
|
||||
fn default_chain_sync_step() -> u64 { 1000 }
|
||||
|
||||
fn default_chain_reorg_max_depth() -> u64 { 10 }
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct EthereumChainMetadata {
|
||||
pub chain_name: String,
|
||||
pub currency_name: String,
|
||||
pub currency_symbol: String,
|
||||
pub currency_decimals: u8,
|
||||
pub public_api_url: String,
|
||||
// Block explorer base URL (should be compatible with https://eips.ethereum.org/EIPS/eip-3091)
|
||||
pub explorer_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct EthereumConfig {
|
||||
// CAIP-2 chain ID
|
||||
pub chain_id: ChainId,
|
||||
// Additional information for clients
|
||||
// https://github.com/ethereum-lists/chains
|
||||
pub chain_metadata: Option<EthereumChainMetadata>,
|
||||
|
||||
pub contract_address: String,
|
||||
pub contract_dir: PathBuf,
|
||||
pub api_url: String,
|
||||
// Instance private key
|
||||
pub signing_key: String,
|
||||
|
||||
#[serde(default = "default_chain_sync_step")]
|
||||
pub chain_sync_step: u64,
|
||||
#[serde(default = "default_chain_reorg_max_depth")]
|
||||
pub chain_reorg_max_depth: u64,
|
||||
}
|
||||
|
||||
impl EthereumConfig {
|
||||
pub fn try_ethereum_chain_id(&self) -> Result<u32, ChainIdError> {
|
||||
self.chain_id.ethereum_chain_id()
|
||||
}
|
||||
|
||||
pub fn ethereum_chain_id(&self) -> u32 {
|
||||
self.try_ethereum_chain_id().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct MoneroConfig {
|
||||
pub chain_id: ChainId,
|
||||
#[serde(alias = "daemon_url")]
|
||||
pub node_url: String,
|
||||
pub wallet_url: String,
|
||||
// Wallet name and password are required when
|
||||
// monero-wallet-rpc is running with --wallet-dir option
|
||||
pub wallet_name: Option<String>,
|
||||
pub wallet_password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BlockchainConfig {
|
||||
Ethereum(EthereumConfig),
|
||||
Monero(MoneroConfig),
|
||||
}
|
||||
|
||||
impl BlockchainConfig {
|
||||
pub fn ethereum_config(&self) -> Option<&EthereumConfig> {
|
||||
if let Self::Ethereum(ethereum_config) = self {
|
||||
Some(ethereum_config)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn monero_config(&self) -> Option<&MoneroConfig> {
|
||||
if let Self::Monero(monero_config) = self {
|
||||
Some(monero_config)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,59 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use log::Level as LogLevel;
|
||||
use log::{Level as LogLevel};
|
||||
use rsa::RsaPrivateKey;
|
||||
use serde::Deserialize;
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Deserializer,
|
||||
de::Error as DeserializerError,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use fedimovies_utils::urls::normalize_url;
|
||||
use mitra_utils::urls::normalize_url;
|
||||
|
||||
use super::blockchain::BlockchainConfig;
|
||||
use super::environment::Environment;
|
||||
use super::federation::FederationConfig;
|
||||
use super::limits::Limits;
|
||||
use super::registration::RegistrationConfig;
|
||||
use super::retention::RetentionConfig;
|
||||
use super::REEF_VERSION;
|
||||
use super::MITRA_VERSION;
|
||||
|
||||
fn default_log_level() -> LogLevel {
|
||||
LogLevel::Info
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum RegistrationType {
|
||||
Open,
|
||||
Invite,
|
||||
}
|
||||
|
||||
fn default_login_message() -> String {
|
||||
"What?!".to_string()
|
||||
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, Default, Deserialize)]
|
||||
pub struct RegistrationConfig {
|
||||
#[serde(rename = "type")]
|
||||
pub registration_type: RegistrationType,
|
||||
|
||||
#[serde(default)]
|
||||
pub default_role_read_only_user: bool, // default is false
|
||||
}
|
||||
|
||||
fn default_log_level() -> LogLevel { LogLevel::Info }
|
||||
|
||||
fn default_login_message() -> String { "Do not sign this message on other sites!".to_string() }
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
// Properties auto-populated from the environment
|
||||
|
@ -33,8 +65,6 @@ pub struct Config {
|
|||
|
||||
// Core settings
|
||||
pub database_url: String,
|
||||
#[serde(default)]
|
||||
pub tls_ca_file: Option<PathBuf>,
|
||||
pub storage_dir: PathBuf,
|
||||
pub web_client_dir: Option<PathBuf>,
|
||||
|
||||
|
@ -55,11 +85,6 @@ pub struct Config {
|
|||
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>,
|
||||
|
||||
|
@ -74,20 +99,23 @@ pub struct Config {
|
|||
|
||||
pub(super) post_character_limit: Option<usize>, // deprecated
|
||||
|
||||
proxy_url: Option<String>,
|
||||
|
||||
#[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>,
|
||||
|
||||
// Blockchain integrations
|
||||
#[serde(rename = "blockchain")]
|
||||
_blockchain: Option<BlockchainConfig>, // deprecated
|
||||
#[serde(default)]
|
||||
blockchains: Vec<BlockchainConfig>,
|
||||
|
||||
// IPFS
|
||||
pub ipfs_api_url: Option<String>,
|
||||
pub ipfs_gateway_url: Option<String>,
|
||||
|
@ -102,14 +130,8 @@ impl Config {
|
|||
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,
|
||||
proxy_url: self.proxy_url.clone(),
|
||||
is_private: matches!(self.environment, Environment::Development),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,6 +142,18 @@ impl Config {
|
|||
pub fn media_dir(&self) -> PathBuf {
|
||||
self.storage_dir.join("media")
|
||||
}
|
||||
|
||||
pub fn blockchain(&self) -> Option<&BlockchainConfig> {
|
||||
if let Some(ref _blockchain_config) = self._blockchain {
|
||||
panic!("'blockchain' setting is not supported anymore, use 'blockchains' instead");
|
||||
} else {
|
||||
match &self.blockchains[..] {
|
||||
[blockchain_config] => Some(blockchain_config),
|
||||
[] => None,
|
||||
_ => panic!("multichain deployments are not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -129,12 +163,8 @@ pub struct Instance {
|
|||
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 {
|
||||
|
@ -148,9 +178,9 @@ impl Instance {
|
|||
|
||||
pub fn agent(&self) -> String {
|
||||
format!(
|
||||
"Reef {version}; {instance_url}",
|
||||
version = REEF_VERSION,
|
||||
instance_url = self.url(),
|
||||
"Mitra {version}; {instance_url}",
|
||||
version=MITRA_VERSION,
|
||||
instance_url=self.url(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -158,24 +188,20 @@ impl Instance {
|
|||
#[cfg(feature = "test-utils")]
|
||||
impl Instance {
|
||||
pub fn for_test(url: &str) -> Self {
|
||||
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
use mitra_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 mitra_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
use super::*;
|
||||
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
|
||||
#[test]
|
||||
fn test_instance_url_https_dns() {
|
||||
|
@ -185,18 +211,14 @@ mod tests {
|
|||
_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),
|
||||
format!("Mitra {}; https://example.com", MITRA_VERSION),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -208,11 +230,7 @@ mod tests {
|
|||
_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");
|
|
@ -10,13 +10,9 @@ 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 {
|
|
@ -1,17 +1,20 @@
|
|||
mod blockchain;
|
||||
mod config;
|
||||
mod environment;
|
||||
mod federation;
|
||||
mod limits;
|
||||
mod loader;
|
||||
mod registration;
|
||||
mod retention;
|
||||
|
||||
pub use config::{Config, Instance};
|
||||
pub use blockchain::{
|
||||
BlockchainConfig,
|
||||
EthereumConfig,
|
||||
MoneroConfig,
|
||||
};
|
||||
pub use config::{Config, Instance, RegistrationType};
|
||||
pub use environment::Environment;
|
||||
pub use loader::parse_config;
|
||||
pub use registration::{DefaultRole, RegistrationType};
|
||||
|
||||
pub const REEF_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const MITRA_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("{0}")]
|
|
@ -1,17 +1,19 @@
|
|||
use super::ConfigError;
|
||||
use regex::Regex;
|
||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Deserializer,
|
||||
de::{Error as DeserializerError},
|
||||
};
|
||||
use super::ConfigError;
|
||||
|
||||
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)
|
||||
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()
|
||||
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() {
|
||||
|
@ -24,49 +26,37 @@ fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
|
|||
Ok(size * multiplier)
|
||||
}
|
||||
|
||||
fn deserialize_file_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
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)?;
|
||||
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
|
||||
const fn default_file_size_limit() -> usize { 20_000_000 } // 20 MB
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct MediaLimits {
|
||||
#[serde(
|
||||
default = "default_file_size_limit",
|
||||
deserialize_with = "deserialize_file_size"
|
||||
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
|
||||
}
|
||||
const fn default_post_character_limit() -> usize { 2000 }
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct PostLimits {
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue