Compare commits
133 commits
Author | SHA1 | Date | |
---|---|---|---|
d2075dcbd2 | |||
7281340423 | |||
406781570c | |||
205ea8c5b8 | |||
9944095959 | |||
13d6fa25bc | |||
456d0789fb | |||
f73a05439d | |||
4df257d024 | |||
44004de576 | |||
93be6f8559 | |||
3860b4780d | |||
de6072026b | |||
2b44c8e20d | |||
d8bea2c868 | |||
5d0b03c2a3 | |||
a2bc297f0e | |||
fe8380e359 | |||
22883798b3 | |||
cb61f4a86b | |||
bb28bf800d | |||
60a27b5b11 | |||
0d77557ad6 | |||
1e40a42524 | |||
b7fafe6458 | |||
17d8c11726 | |||
b049b75873 | |||
272d06897a | |||
47529ff703 | |||
|
b4ff7abbc1 | ||
|
5906185154 | ||
|
b3b62a9c7f | ||
|
b77d4a9bdf | ||
|
b6e7fa5d13 | ||
|
05eeb5ae2a | ||
|
f41b205084 | ||
|
1302611731 | ||
|
469a5484a1 | ||
|
7471c03ed1 | ||
|
e2ea58d33a | ||
|
a3f44cf678 | ||
|
69caf0b5bc | ||
|
01cefa6ea1 | ||
|
1092319f6e | ||
|
f8df50934c | ||
|
7f6ebb89c0 | ||
|
c022e0d320 | ||
|
b6abcf252a | ||
|
55c0b1eb6b | ||
|
8daf566eb2 | ||
|
533ef48393 | ||
|
8533a892bf | ||
ad3ea0e7ca | |||
83286b7522 | |||
c5dbb0257f | |||
c0049e6d49 | |||
e5be1326de | |||
89e3f93592 | |||
5ef024d923 | |||
|
cdb728a70a | ||
|
8708abd9cd | ||
|
fc82c83421 | ||
|
01494f1770 | ||
|
278950252e | ||
|
7c38c0a4d6 | ||
|
e950189086 | ||
|
970071a9f0 | ||
|
20080333d0 | ||
|
b9fdb1ccf4 | ||
|
9768fc6228 | ||
|
9e5672929b | ||
|
dcaa2227d2 | ||
|
b0bf3cf594 | ||
|
99f6c08e9a | ||
|
13df9e0478 | ||
|
59e5f12016 | ||
|
edebae0dc6 | ||
|
ebbde534af | ||
|
300d2ef6f8 | ||
|
3c2d2d124b | ||
|
dbca8183bb | ||
|
9a32fb9c80 | ||
|
779d4e7287 | ||
|
6604ea8a2b | ||
|
95daa94a97 | ||
|
19780c3b8a | ||
|
00ca54f9b4 | ||
|
006665f6fb | ||
|
348149bbaa | ||
|
dd0c53c5e9 | ||
|
378d94e7b8 | ||
|
8cfb2318a2 | ||
|
462da87e9b | ||
|
4f9a99e6f2 | ||
|
eb1f815548 | ||
|
b85a0fb7ac | ||
|
5e1f441e8b | ||
|
0521f1f731 | ||
|
5ba8b8d6ae | ||
|
ac6491d030 | ||
|
08d7482f32 | ||
|
5450ba8871 | ||
|
399a632a88 | ||
|
ef852d781e | ||
|
441850dd21 | ||
|
73b576c643 | ||
|
f5dd0a17c9 | ||
|
37ab3dc456 | ||
|
76e85a3b7b | ||
|
521c2cbe41 | ||
|
3b5c8a4131 | ||
|
21135d7704 | ||
|
dae9be1388 | ||
|
39ab6bbb13 | ||
|
cdb304a8b7 | ||
|
848a0685de | ||
|
608ec096cd | ||
|
28be8dbb31 | ||
|
e3ee144889 | ||
|
b80b827fde | ||
|
fcf63ff317 | ||
|
9a513c928f | ||
|
7640598431 | ||
|
f76438b6f8 | ||
|
306fd7b75b | ||
|
a515af1111 | ||
|
f27b2e13eb | ||
|
a07d7ce34a | ||
|
f037a4d58c | ||
|
b56e11e81d | ||
|
c80bfccd6a | ||
|
0b65e7473e | ||
|
1637e38ee4 |
310 changed files with 8713 additions and 12240 deletions
|
@ -1,6 +1,3 @@
|
||||||
[registries.crates-io]
|
|
||||||
protocol = "git"
|
|
||||||
|
|
||||||
# https://github.com/rust-lang/cargo/issues/5034#issuecomment-927105016
|
# https://github.com/rust-lang/cargo/issues/5034#issuecomment-927105016
|
||||||
[target.'cfg(feature = "cargo-clippy")']
|
[target.'cfg(feature = "cargo-clippy")']
|
||||||
rustflags = [
|
rustflags = [
|
||||||
|
|
21
.dockerignore
Normal file
21
.dockerignore
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# flyctl launch added from .gitignore
|
||||||
|
**/.env.local
|
||||||
|
**/config.yaml
|
||||||
|
target
|
||||||
|
|
||||||
|
# other things
|
||||||
|
docs/*
|
||||||
|
fedimovies-*
|
||||||
|
scripts/*
|
||||||
|
src/*
|
||||||
|
|
||||||
|
# flyctl launch added from .idea/.gitignore
|
||||||
|
# Default ignored files
|
||||||
|
.idea/shelf
|
||||||
|
.idea/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
.idea/httpRequests
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
.idea/dataSources
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
fly.toml
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,5 +1,9 @@
|
||||||
.env.local
|
.env.local
|
||||||
config.yaml
|
config.yaml
|
||||||
|
/secret/*
|
||||||
/files/*
|
/files/*
|
||||||
!/files/.gitkeep
|
!/files/.gitkeep
|
||||||
|
/build/*
|
||||||
|
!/build/.gitkeep
|
||||||
/target
|
/target
|
||||||
|
fly.toml
|
||||||
|
|
63
.woodpecker.yml
Normal file
63
.woodpecker.yml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
matrix:
|
||||||
|
RUST: [stable]
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
|
||||||
|
check-formatting:
|
||||||
|
image: rust
|
||||||
|
when:
|
||||||
|
branch: [ main ]
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- .woodpecker.yml
|
||||||
|
- src/**/*.rs
|
||||||
|
- fedimovies-cli/**/*.rs
|
||||||
|
- fedimovies-config/**/*.rs
|
||||||
|
- fedimovies-models/**/*.rs
|
||||||
|
- fedimovies-utils/**/*.rs
|
||||||
|
environment:
|
||||||
|
- CARGO_TERM_COLOR=always
|
||||||
|
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||||
|
commands:
|
||||||
|
- rustup default $RUST
|
||||||
|
- rustup component add rustfmt
|
||||||
|
- cargo fmt --all -- --check
|
||||||
|
|
||||||
|
check-style:
|
||||||
|
image: rust
|
||||||
|
when:
|
||||||
|
branch: [ main ]
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- .woodpecker.yml
|
||||||
|
- src/**/*.rs
|
||||||
|
- fedimovies-cli/**/*.rs
|
||||||
|
- fedimovies-config/**/*.rs
|
||||||
|
- fedimovies-models/**/*.rs
|
||||||
|
- fedimovies-utils/**/*.rs
|
||||||
|
environment:
|
||||||
|
- CARGO_TERM_COLOR=always
|
||||||
|
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||||
|
commands:
|
||||||
|
- rustup default $RUST
|
||||||
|
- rustup component add clippy
|
||||||
|
- cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
run-tests:
|
||||||
|
image: rust
|
||||||
|
when:
|
||||||
|
branch: [ main ]
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- .woodpecker.yml
|
||||||
|
- src/**/*.rs
|
||||||
|
- fedimovies-cli/**/*.rs
|
||||||
|
- fedimovies-config/**/*.rs
|
||||||
|
- fedimovies-models/**/*.rs
|
||||||
|
- fedimovies-utils/**/*.rs
|
||||||
|
environment:
|
||||||
|
- CARGO_TERM_COLOR=always
|
||||||
|
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||||
|
commands:
|
||||||
|
- rustup default $RUST
|
||||||
|
- cargo test --all -- --nocapture
|
124
CHANGELOG.md
124
CHANGELOG.md
|
@ -6,6 +6,130 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [1.17.0] - 2023-03-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
2157
Cargo.lock
generated
2157
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
62
Cargo.toml
62
Cargo.toml
|
@ -1,36 +1,39 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mitra"
|
name = "fedimovies"
|
||||||
version = "1.17.0"
|
version = "1.22.0"
|
||||||
description = "Federated micro-blogging platform and content subscription service"
|
description = "Movies reviews and ratings for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.56"
|
rust-version = "1.68"
|
||||||
publish = false
|
publish = false
|
||||||
default-run = "mitra"
|
default-run = "fedimovies"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
".",
|
".",
|
||||||
"mitra-cli",
|
"fedimovies-cli",
|
||||||
"mitra-config",
|
"fedimovies-config",
|
||||||
"mitra-utils",
|
"fedimovies-models",
|
||||||
|
"fedimovies-utils",
|
||||||
]
|
]
|
||||||
default-members = [
|
default-members = [
|
||||||
".",
|
".",
|
||||||
"mitra-cli",
|
"fedimovies-cli",
|
||||||
"mitra-config",
|
"fedimovies-config",
|
||||||
"mitra-utils",
|
"fedimovies-models",
|
||||||
|
"fedimovies-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mitra-config = { path = "mitra-config" }
|
fedimovies-config = { path = "fedimovies-config" }
|
||||||
mitra-utils = { path = "mitra-utils" }
|
fedimovies-models = { path = "fedimovies-models" }
|
||||||
|
fedimovies-utils = { path = "fedimovies-utils" }
|
||||||
|
|
||||||
# Used to handle incoming HTTP requests
|
# Used to handle incoming HTTP requests
|
||||||
actix-cors = "0.6.2"
|
actix-cors = "0.6.4"
|
||||||
actix-files = "0.6.2"
|
actix-files = "0.6.2"
|
||||||
actix-web = "4.1.0"
|
actix-web = "4.3.1"
|
||||||
actix-web-httpauth = "0.8.0"
|
actix-web-httpauth = "0.8.0"
|
||||||
# Used for catching errors
|
# Used for catching errors
|
||||||
anyhow = "1.0.58"
|
anyhow = "1.0.58"
|
||||||
|
@ -38,9 +41,6 @@ anyhow = "1.0.58"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
# Used for working with dates
|
# Used for working with dates
|
||||||
chrono = { version = "0.4.23", default-features = false, features = ["std", "serde"] }
|
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
|
# Used to work with hexadecimal strings
|
||||||
hex = { version = "0.4.3", features = ["serde"] }
|
hex = { version = "0.4.3", features = ["serde"] }
|
||||||
# Used for logging
|
# Used for logging
|
||||||
|
@ -50,20 +50,14 @@ env_logger = { version = "0.9.0", default-features = false }
|
||||||
ed25519-dalek = "1.0.1"
|
ed25519-dalek = "1.0.1"
|
||||||
ed25519 = "1.5.3"
|
ed25519 = "1.5.3"
|
||||||
blake2 = "0.10.5"
|
blake2 = "0.10.5"
|
||||||
# Used to query Monero node
|
|
||||||
monero-rpc = "0.3.2"
|
|
||||||
# Used to determine the number of CPUs on the system
|
# Used to determine the number of CPUs on the system
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
# Used for working with regular expressions
|
# Used for working with regular expressions
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
# Used for managing database migrations
|
|
||||||
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
|
|
||||||
# Used for making async HTTP requests
|
# Used for making async HTTP requests
|
||||||
reqwest = { version = "0.11.13", features = ["json", "multipart", "socks"] }
|
reqwest = { version = "0.11.13", features = ["json", "multipart", "socks"] }
|
||||||
# Used for working with RSA keys
|
# Used for working with RSA keys
|
||||||
rsa = "0.5.0"
|
rsa = "0.5.0"
|
||||||
# Used for working with ethereum keys
|
|
||||||
secp256k1 = { version = "0.21.3", features = ["rand", "rand-std"] }
|
|
||||||
# Used for serialization/deserialization
|
# Used for serialization/deserialization
|
||||||
serde = { version = "1.0.136", features = ["derive"] }
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
serde_json = "1.0.89"
|
serde_json = "1.0.89"
|
||||||
|
@ -74,28 +68,18 @@ siwe = "0.4.0"
|
||||||
# Used for creating error types
|
# Used for creating error types
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.37"
|
||||||
# Async runtime
|
# Async runtime
|
||||||
tokio = { version = "1.20.4", features = ["macros"] }
|
tokio = { version = "=1.20.4", 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
|
# Used to work with URLs
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
# Used to work with UUIDs
|
# Used to work with UUIDs
|
||||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
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]
|
[dev-dependencies]
|
||||||
mitra-config = { path = "mitra-config", features = ["test-utils"] }
|
fedimovies-config = { path = "fedimovies-config", features = ["test-utils"] }
|
||||||
mitra-utils = { path = "mitra-utils", features = ["test-utils"] }
|
fedimovies-models = { path = "fedimovies-models", features = ["test-utils"] }
|
||||||
|
fedimovies-utils = { path = "fedimovies-utils", features = ["test-utils"] }
|
||||||
|
|
||||||
serial_test = "0.7.0"
|
serial_test = "0.7.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
ethereum-extras = []
|
production = ["fedimovies-config/production"]
|
||||||
|
|
||||||
production = ["mitra-config/production"]
|
|
||||||
|
|
102
README.md
102
README.md
|
@ -1,6 +1,7 @@
|
||||||
# Mitra
|
# FediMovies
|
||||||
|
[![status-badge](https://ci.caric.io/api/badges/FediMovies/fedimovies/status.svg)](https://ci.caric.io/FediMovies/fedimovies)
|
||||||
|
|
||||||
Federated micro-blogging platform.
|
Lively federated movies reviews platform.
|
||||||
|
|
||||||
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||||
|
|
||||||
|
@ -8,41 +9,30 @@ Features:
|
||||||
|
|
||||||
- Micro-blogging service (includes support for quote posts, custom emojis and more).
|
- Micro-blogging service (includes support for quote posts, custom emojis and more).
|
||||||
- Mastodon API.
|
- Mastodon API.
|
||||||
- Content subscription service. Subscriptions provide a way to receive monthly payments from subscribers and to publish private content made exclusively for them.
|
|
||||||
- Supported payment methods: [Monero](https://www.getmonero.org/get-started/what-is-monero/) and [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens (on Ethereum and other EVM-compatible blockchains).
|
|
||||||
- [Sign-in with a wallet](https://eips.ethereum.org/EIPS/eip-4361).
|
|
||||||
- Donation buttons.
|
|
||||||
- Account migrations (from one server to another). Identity can be detached from the server.
|
- Account migrations (from one server to another). Identity can be detached from the server.
|
||||||
- Federation over Tor.
|
- Federation over Tor.
|
||||||
|
|
||||||
Follow: [@mitra@mitra.social](https://mitra.social/@mitra)
|
|
||||||
|
|
||||||
Matrix chat: [#mitra:halogen.city](https://matrix.to/#/#mitra:halogen.city)
|
|
||||||
|
|
||||||
## Instances
|
## Instances
|
||||||
|
|
||||||
- [FediList](http://demo.fedilist.com/instance?software=mitra)
|
- [FediList](http://demo.fedilist.com/instance?software=fedimovies)
|
||||||
- [Fediverse Observer](https://mitra.fediverse.observer/list)
|
- [Fediverse Observer](https://fedimovies.fediverse.observer/list)
|
||||||
|
|
||||||
Demo instance: https://public.mitra.social/ ([invite-only](https://public.mitra.social/about))
|
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
|
|
||||||
Server: https://codeberg.org/silverpill/mitra (this repo)
|
Server: https://code.caric.io/reef/reef (this repo)
|
||||||
|
|
||||||
Web client: https://codeberg.org/silverpill/mitra-web
|
Web client:
|
||||||
|
|
||||||
Ethereum contracts: https://codeberg.org/silverpill/mitra-contracts
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Rust 1.56+ (when building from source)
|
- Rust 1.57+ (when building from source)
|
||||||
- PostgreSQL 12+
|
- PostgreSQL 12+
|
||||||
|
|
||||||
Optional:
|
Optional:
|
||||||
|
|
||||||
- Monero node and Monero wallet service
|
|
||||||
- Ethereum node
|
|
||||||
- IPFS node (see [guide](./docs/ipfs.md))
|
- IPFS node (see [guide](./docs/ipfs.md))
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -55,76 +45,58 @@ Run:
|
||||||
cargo build --release --features production
|
cargo build --release --features production
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will produce two binaries in `target/release` directory, `mitra` and `mitractl`.
|
This command will produce two binaries in `target/release` directory, `fedimovies` and `fedimoviesctl`.
|
||||||
|
|
||||||
Install PostgreSQL and create the database:
|
Install PostgreSQL and create the database:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||||
CREATE DATABASE mitra OWNER mitra;
|
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||||
```
|
```
|
||||||
|
|
||||||
Create configuration file by copying `contrib/mitra_config.yaml` and configure the instance. Default config file path is `/etc/mitra/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
|
Create configuration file by copying `contrib/fedimovies_config.yaml` and configure the instance. Default config file path is `/etc/fedimovies/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
Start Mitra:
|
Start Fedimovies:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./mitra
|
./fedimovies
|
||||||
```
|
```
|
||||||
|
|
||||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/mitra.nginx).
|
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
|
||||||
|
|
||||||
To run Mitra as a systemd service, check out the [systemd unit file example](./contrib/mitra.service).
|
To run Fedimovies as a systemd service, check out the [systemd unit file example](./contrib/fedimovies.service).
|
||||||
|
|
||||||
### Debian package
|
### Debian package
|
||||||
|
|
||||||
Download and install Mitra package:
|
Download and install Fedimovies package:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
dpkg -i mitra.deb
|
dpkg -i fedimovies.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
Install PostgreSQL and create the database:
|
Install PostgreSQL and create the database:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||||
CREATE DATABASE mitra OWNER mitra;
|
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||||
```
|
```
|
||||||
|
|
||||||
Open configuration file `/etc/mitra/config.yaml` and configure the instance.
|
Open configuration file `/etc/fedimovies/config.yaml` and configure the instance.
|
||||||
|
|
||||||
Start Mitra:
|
Start Fedimovies:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
systemctl start mitra
|
systemctl start fedimovies
|
||||||
```
|
```
|
||||||
|
|
||||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/mitra.nginx).
|
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
|
||||||
|
|
||||||
### Tor federation
|
### Tor federation
|
||||||
|
|
||||||
See [guide](./docs/onion.md).
|
See [guide](./docs/onion.md).
|
||||||
|
|
||||||
### Monero
|
|
||||||
|
|
||||||
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
|
## Development
|
||||||
|
|
||||||
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||||
|
@ -138,15 +110,7 @@ docker-compose up -d
|
||||||
Test connection:
|
Test connection:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
psql -h localhost -p 55432 -U mitra mitra
|
psql -h localhost -p 55432 -U fedimovies fedimovies
|
||||||
```
|
|
||||||
|
|
||||||
### Start Monero node and wallet server
|
|
||||||
|
|
||||||
(this step is optional)
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker-compose --profile monero up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run web service
|
### Run web service
|
||||||
|
@ -166,7 +130,7 @@ cargo run
|
||||||
### Run CLI
|
### Run CLI
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run --bin mitractl
|
cargo run --bin fedimoviesctl
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run linter
|
### Run linter
|
||||||
|
@ -187,20 +151,16 @@ See [FEDERATION.md](./FEDERATION.md)
|
||||||
|
|
||||||
## Client API
|
## Client API
|
||||||
|
|
||||||
Most methods are similar to Mastodon API, but Mitra is not fully compatible.
|
Most methods are similar to Mastodon API, but Fedimovies is not fully compatible.
|
||||||
|
|
||||||
[OpenAPI spec](./docs/openapi.yaml)
|
[OpenAPI spec](./docs/openapi.yaml)
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
`mitractl` is a command-line tool for performing instance maintenance.
|
`fedimoviesctl` is a command-line tool for performing instance maintenance.
|
||||||
|
|
||||||
[Documentation](./docs/mitractl.md)
|
[Documentation](./docs/fedimoviesctl.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[AGPL-3.0](./LICENSE)
|
[AGPL-3.0](./LICENSE)
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Monero: 8Ahza5RM4JQgtdqvpcF1U628NN5Q87eryXQad3Fy581YWTZU8o3EMbtScuioQZSkyNNEEE1Lkj2cSbG4VnVYCW5L1N4os5p
|
|
||||||
|
|
0
build/.gitkeep
Normal file
0
build/.gitkeep
Normal file
|
@ -14,28 +14,5 @@ instance_description: My instance
|
||||||
registration:
|
registration:
|
||||||
type: open
|
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_api_url: 'http://127.0.0.1:5001'
|
||||||
ipfs_gateway_url: 'http://127.0.0.1:8001'
|
ipfs_gateway_url: 'http://127.0.0.1:8001'
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "IERC165",
|
|
||||||
"sourceName": "@openzeppelin/contracts/utils/introspection/IERC165.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bytes4",
|
|
||||||
"name": "interfaceId",
|
|
||||||
"type": "bytes4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "supportsInterface",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
|
@ -1,233 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "IERC20Metadata",
|
|
||||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "spender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "value",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Approval",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "value",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Transfer",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "spender",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "allowance",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "spender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "approve",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "account",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "balanceOf",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "decimals",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint8",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "name",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "symbol",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "totalSupply",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "transfer",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "sender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "transferFrom",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
|
@ -1,341 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "IERC721Metadata",
|
|
||||||
"sourceName": "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "approved",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Approval",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "operator",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "approved",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "ApprovalForAll",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Transfer",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "approve",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "balanceOf",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "balance",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getApproved",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "operator",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "operator",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "isApprovedForAll",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "name",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "ownerOf",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "safeTransferFrom",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes",
|
|
||||||
"name": "data",
|
|
||||||
"type": "bytes"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "safeTransferFrom",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "operator",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "_approved",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "setApprovalForAll",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bytes4",
|
|
||||||
"name": "interfaceId",
|
|
||||||
"type": "bytes4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "supportsInterface",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "symbol",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "tokenURI",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "tokenId",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "transferFrom",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "IGate",
|
|
||||||
"sourceName": "contracts/interfaces/IGate.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "user",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "isAllowedUser",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "IMinter",
|
|
||||||
"sourceName": "contracts/interfaces/IMinter.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "collectible",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "contract IERC721Metadata",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "user",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "tokenURI",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint8",
|
|
||||||
"name": "v",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "r",
|
|
||||||
"type": "bytes32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "s",
|
|
||||||
"type": "bytes32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "mint",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "ISubscription",
|
|
||||||
"sourceName": "contracts/interfaces/ISubscription.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "sender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "expires_at",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "UpdateSubscription",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "cancel",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "send",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "sender",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "withdrawReceived",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "withdrawReceivedAll",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "ISubscriptionAdapter",
|
|
||||||
"sourceName": "contracts/interfaces/ISubscriptionAdapter.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "price",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint8",
|
|
||||||
"name": "v",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "r",
|
|
||||||
"type": "bytes32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "s",
|
|
||||||
"type": "bytes32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "configureSubscription",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getSubscriptionPrice",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "sender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "getSubscriptionState",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "recipient",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "isSubscriptionConfigured",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "subscription",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "contract ISubscription",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "subscriptionToken",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "contract IERC20Metadata",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x",
|
|
||||||
"deployedBytecode": "0x",
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
16
contrib/Dockerfile
Normal file
16
contrib/Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
FROM ubuntu:23.04
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir -p /var/lib/data
|
||||||
|
|
||||||
|
COPY build/fedimovies /usr/local/bin
|
||||||
|
COPY build/fedimoviesctl /usr/local/bin
|
||||||
|
COPY secret/fedimovies.conf /etc/fedimovies.conf
|
||||||
|
COPY files /www/frontend/
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/fedimovies"]
|
|
@ -47,6 +47,7 @@ retention:
|
||||||
|
|
||||||
# Federation parameters
|
# Federation parameters
|
||||||
#federation:
|
#federation:
|
||||||
|
# enabled: true
|
||||||
# # Proxy for outgoing requests
|
# # Proxy for outgoing requests
|
||||||
# #proxy_url: 'socks5h://127.0.0.1:9050'
|
# #proxy_url: 'socks5h://127.0.0.1:9050'
|
||||||
# # Proxy for outgoing requests to .onion targets
|
# # Proxy for outgoing requests to .onion targets
|
||||||
|
@ -55,31 +56,6 @@ retention:
|
||||||
# List of blocked domains
|
# List of blocked domains
|
||||||
#blocked_instances: []
|
#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 integration
|
||||||
#ipfs_api_url: 'http://127.0.0.1:5001'
|
#ipfs_api_url: 'http://127.0.0.1:5001'
|
||||||
# IPFS gateway (for clients)
|
# IPFS gateway (for clients)
|
||||||
|
|
|
@ -32,13 +32,19 @@ List generated invites:
|
||||||
mitractl list-invite-codes
|
mitractl list-invite-codes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Create user:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mitractl create-user <username> <password> <role-name>
|
||||||
|
```
|
||||||
|
|
||||||
Set or change password:
|
Set or change password:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mitractl set-password <user-id> <password>
|
mitractl set-password <user-id> <password>
|
||||||
```
|
```
|
||||||
|
|
||||||
Change user's role:
|
Change user's role (admin, user or read_only_user).
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mitractl set-role <user-id> <role-name>
|
mitractl set-role <user-id> <role-name>
|
||||||
|
@ -80,6 +86,12 @@ Delete empty remote profiles:
|
||||||
mitractl delete-empty-profiles 100
|
mitractl delete-empty-profiles 100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Delete unused remote emojis:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mitractl prune-remote-emojis
|
||||||
|
```
|
||||||
|
|
||||||
Import custom emoji from another instance:
|
Import custom emoji from another instance:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -352,6 +352,12 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- name: resolve
|
||||||
|
in: query
|
||||||
|
description: Attempt WebFinger lookup.
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
- name: limit
|
- name: limit
|
||||||
in: query
|
in: query
|
||||||
description: Maximum number of results. Defaults to 40.
|
description: Maximum number of results. Defaults to 40.
|
||||||
|
@ -544,23 +550,6 @@ paths:
|
||||||
$ref: '#/components/schemas/Subscription'
|
$ref: '#/components/schemas/Subscription'
|
||||||
404:
|
404:
|
||||||
description: Profile not found
|
description: Profile not found
|
||||||
/api/v1/accounts/{account_id}/aliases:
|
|
||||||
get:
|
|
||||||
summary: Get actor's 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}/follow:
|
/api/v1/accounts/{account_id}/follow:
|
||||||
post:
|
post:
|
||||||
summary: Follow the given actor.
|
summary: Follow the given actor.
|
||||||
|
@ -607,6 +596,37 @@ paths:
|
||||||
$ref: '#/components/schemas/Relationship'
|
$ref: '#/components/schemas/Relationship'
|
||||||
404:
|
404:
|
||||||
description: Profile not found
|
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:
|
/api/v1/apps:
|
||||||
post:
|
post:
|
||||||
summary: Create a new application to obtain OAuth2 credentials.
|
summary: Create a new application to obtain OAuth2 credentials.
|
||||||
|
@ -760,6 +780,29 @@ paths:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Notification'
|
$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:
|
/api/v1/settings/change_password:
|
||||||
post:
|
post:
|
||||||
summary: Set or change user's password.
|
summary: Set or change user's password.
|
||||||
|
@ -783,6 +826,28 @@ paths:
|
||||||
$ref: '#/components/schemas/CredentialAccount'
|
$ref: '#/components/schemas/CredentialAccount'
|
||||||
400:
|
400:
|
||||||
description: Invalid request data.
|
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:
|
/api/v1/settings/export_followers:
|
||||||
get:
|
get:
|
||||||
summary: Export followers to CSV file
|
summary: Export followers to CSV file
|
||||||
|
@ -894,6 +959,10 @@ paths:
|
||||||
visibility:
|
visibility:
|
||||||
description: Visibility of the post.
|
description: Visibility of the post.
|
||||||
$ref: '#/components/schemas/Visibility'
|
$ref: '#/components/schemas/Visibility'
|
||||||
|
sensitiive:
|
||||||
|
description: Mark post and attached media as sensitive?
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
mentions:
|
mentions:
|
||||||
description: Array of profile IDs to be mentioned
|
description: Array of profile IDs to be mentioned
|
||||||
type: array
|
type: array
|
||||||
|
@ -1462,6 +1531,10 @@ components:
|
||||||
role:
|
role:
|
||||||
description: The role assigned to the currently authorized user.
|
description: The role assigned to the currently authorized user.
|
||||||
$ref: '#/components/schemas/Role'
|
$ref: '#/components/schemas/Role'
|
||||||
|
client_config:
|
||||||
|
description: Client configurations.
|
||||||
|
type: object
|
||||||
|
example: {"mitra-web":{"theme":"dark"}}
|
||||||
ActivityParameters:
|
ActivityParameters:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1470,6 +1543,19 @@ components:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- update
|
- 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:
|
Application:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1782,6 +1868,8 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- create_follow_request
|
- create_follow_request
|
||||||
- create_post
|
- create_post
|
||||||
|
- delete_any_post
|
||||||
|
- delete_any_profile
|
||||||
- manage_subscription_options
|
- manage_subscription_options
|
||||||
Signature:
|
Signature:
|
||||||
type: object
|
type: object
|
||||||
|
@ -1823,6 +1911,10 @@ components:
|
||||||
visibility:
|
visibility:
|
||||||
description: Visibility of this post.
|
description: Visibility of this post.
|
||||||
$ref: '#/components/schemas/Visibility'
|
$ref: '#/components/schemas/Visibility'
|
||||||
|
sensitiive:
|
||||||
|
description: Is this post marked as sensitive content?
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
spoiler_text:
|
spoiler_text:
|
||||||
description: Subject or summary line, below which post content is collapsed until expanded.
|
description: Subject or summary line, below which post content is collapsed until expanded.
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mitra-cli"
|
name = "fedimovies-cli"
|
||||||
version = "1.17.0"
|
version = "1.22.0"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.56"
|
rust-version = "1.68"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mitractl"
|
name = "fedimoviesctl"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mitra-config = { path = "../mitra-config" }
|
fedimovies-config = { path = "../fedimovies-config" }
|
||||||
mitra-utils = { path = "../mitra-utils" }
|
fedimovies-models = { path = "../fedimovies-models" }
|
||||||
mitra = { path = ".." }
|
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||||
|
fedimovies = { path = ".." }
|
||||||
|
|
||||||
# Used for catching errors
|
# Used for catching errors
|
||||||
anyhow = "1.0.58"
|
anyhow = "1.0.58"
|
|
@ -1,59 +1,39 @@
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::Error;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use mitra::activitypub::{
|
use fedimovies::activitypub::{
|
||||||
actors::helpers::update_remote_profile,
|
actors::helpers::update_remote_profile, builders::delete_note::prepare_delete_note,
|
||||||
builders::delete_note::prepare_delete_note,
|
builders::delete_person::prepare_delete_person, fetcher::fetchers::fetch_actor,
|
||||||
builders::delete_person::prepare_delete_person,
|
fetcher::helpers::import_from_outbox,
|
||||||
fetcher::fetchers::fetch_actor,
|
|
||||||
};
|
};
|
||||||
use mitra::database::DatabaseClient;
|
use fedimovies::admin::roles::{role_from_str, ALLOWED_ROLES};
|
||||||
use mitra::ethereum::{
|
use fedimovies::media::{remove_files, remove_media, MediaStorage};
|
||||||
signatures::generate_ecdsa_key,
|
use fedimovies::validators::{emojis::EMOJI_LOCAL_MAX_SIZE, users::validate_local_username};
|
||||||
sync::save_current_block_number,
|
use fedimovies_config::Config;
|
||||||
utils::key_to_ethereum_address,
|
use fedimovies_models::{
|
||||||
};
|
|
||||||
use mitra::media::remove_files;
|
|
||||||
use mitra::models::{
|
|
||||||
attachments::queries::delete_unused_attachments,
|
attachments::queries::delete_unused_attachments,
|
||||||
cleanup::find_orphaned_files,
|
cleanup::find_orphaned_files,
|
||||||
|
database::DatabaseClient,
|
||||||
emojis::helpers::get_emoji_by_name,
|
emojis::helpers::get_emoji_by_name,
|
||||||
emojis::queries::{
|
emojis::queries::{
|
||||||
create_emoji,
|
create_emoji, delete_emoji, find_unused_remote_emojis, get_emoji_by_name_and_hostname,
|
||||||
delete_emoji,
|
|
||||||
get_emoji_by_name_and_hostname,
|
|
||||||
},
|
},
|
||||||
emojis::validators::EMOJI_LOCAL_MAX_SIZE,
|
|
||||||
oauth::queries::delete_oauth_tokens,
|
oauth::queries::delete_oauth_tokens,
|
||||||
posts::queries::{delete_post, find_extraneous_posts, get_post_by_id},
|
posts::queries::{delete_post, find_extraneous_posts, get_post_by_id},
|
||||||
profiles::queries::{
|
profiles::queries::{
|
||||||
delete_profile,
|
delete_profile, find_empty_profiles, find_unreachable, get_profile_by_id,
|
||||||
find_empty_profiles,
|
|
||||||
find_unreachable,
|
|
||||||
get_profile_by_id,
|
|
||||||
get_profile_by_remote_actor_id,
|
get_profile_by_remote_actor_id,
|
||||||
},
|
},
|
||||||
subscriptions::queries::reset_subscriptions,
|
subscriptions::queries::reset_subscriptions,
|
||||||
users::queries::{
|
users::queries::{
|
||||||
create_invite_code,
|
create_invite_code, create_user, get_invite_codes, get_user_by_id, set_user_password,
|
||||||
get_invite_codes,
|
|
||||||
get_user_by_id,
|
|
||||||
set_user_password,
|
|
||||||
set_user_role,
|
set_user_role,
|
||||||
},
|
},
|
||||||
users::types::Role,
|
users::types::UserCreateData,
|
||||||
};
|
};
|
||||||
use mitra::monero::{
|
use fedimovies_utils::{
|
||||||
helpers::check_expired_invoice,
|
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||||
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},
|
datetime::{days_before_now, get_min_datetime},
|
||||||
passwords::hash_password,
|
passwords::hash_password,
|
||||||
};
|
};
|
||||||
|
@ -72,9 +52,11 @@ pub enum SubCommand {
|
||||||
|
|
||||||
GenerateInviteCode(GenerateInviteCode),
|
GenerateInviteCode(GenerateInviteCode),
|
||||||
ListInviteCodes(ListInviteCodes),
|
ListInviteCodes(ListInviteCodes),
|
||||||
|
CreateUser(CreateUser),
|
||||||
SetPassword(SetPassword),
|
SetPassword(SetPassword),
|
||||||
SetRole(SetRole),
|
SetRole(SetRole),
|
||||||
RefetchActor(RefetchActor),
|
RefetchActor(RefetchActor),
|
||||||
|
ReadOutbox(ReadOutbox),
|
||||||
DeleteProfile(DeleteProfile),
|
DeleteProfile(DeleteProfile),
|
||||||
DeletePost(DeletePost),
|
DeletePost(DeletePost),
|
||||||
DeleteEmoji(DeleteEmoji),
|
DeleteEmoji(DeleteEmoji),
|
||||||
|
@ -82,6 +64,7 @@ pub enum SubCommand {
|
||||||
DeleteUnusedAttachments(DeleteUnusedAttachments),
|
DeleteUnusedAttachments(DeleteUnusedAttachments),
|
||||||
DeleteOrphanedFiles(DeleteOrphanedFiles),
|
DeleteOrphanedFiles(DeleteOrphanedFiles),
|
||||||
DeleteEmptyProfiles(DeleteEmptyProfiles),
|
DeleteEmptyProfiles(DeleteEmptyProfiles),
|
||||||
|
PruneRemoteEmojis(PruneRemoteEmojis),
|
||||||
ListUnreachableActors(ListUnreachableActors),
|
ListUnreachableActors(ListUnreachableActors),
|
||||||
ImportEmoji(ImportEmoji),
|
ImportEmoji(ImportEmoji),
|
||||||
UpdateCurrentBlock(UpdateCurrentBlock),
|
UpdateCurrentBlock(UpdateCurrentBlock),
|
||||||
|
@ -108,12 +91,7 @@ pub struct GenerateEthereumAddress;
|
||||||
|
|
||||||
impl GenerateEthereumAddress {
|
impl GenerateEthereumAddress {
|
||||||
pub fn execute(&self) -> () {
|
pub fn execute(&self) -> () {
|
||||||
let private_key = generate_ecdsa_key();
|
println!("dummy");
|
||||||
let address = key_to_ethereum_address(&private_key);
|
|
||||||
println!(
|
|
||||||
"address {:?}; private key {}",
|
|
||||||
address, private_key.display_secret(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +102,8 @@ pub struct GenerateInviteCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateInviteCode {
|
impl GenerateInviteCode {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
let invite_code = create_invite_code(db_client, self.note.as_deref()).await?;
|
||||||
db_client: &impl DatabaseClient,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let invite_code = create_invite_code(
|
|
||||||
db_client,
|
|
||||||
self.note.as_deref(),
|
|
||||||
).await?;
|
|
||||||
println!("generated invite code: {}", invite_code);
|
println!("generated invite code: {}", invite_code);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -142,10 +114,7 @@ impl GenerateInviteCode {
|
||||||
pub struct ListInviteCodes;
|
pub struct ListInviteCodes;
|
||||||
|
|
||||||
impl ListInviteCodes {
|
impl ListInviteCodes {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
db_client: &impl DatabaseClient,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let invite_codes = get_invite_codes(db_client).await?;
|
let invite_codes = get_invite_codes(db_client).await?;
|
||||||
if invite_codes.is_empty() {
|
if invite_codes.is_empty() {
|
||||||
println!("no invite codes found");
|
println!("no invite codes found");
|
||||||
|
@ -157,7 +126,37 @@ impl ListInviteCodes {
|
||||||
} else {
|
} else {
|
||||||
println!("{}", invite_code.code);
|
println!("{}", invite_code.code);
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new user
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct CreateUser {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
#[clap(value_parser = ALLOWED_ROLES)]
|
||||||
|
role: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateUser {
|
||||||
|
pub async fn execute(&self, db_client: &mut impl DatabaseClient) -> Result<(), Error> {
|
||||||
|
validate_local_username(&self.username)?;
|
||||||
|
let password_hash = hash_password(&self.password)?;
|
||||||
|
let private_key = generate_rsa_key()?;
|
||||||
|
let private_key_pem = serialize_private_key(&private_key)?;
|
||||||
|
let role = role_from_str(&self.role)?;
|
||||||
|
let user_data = UserCreateData {
|
||||||
|
username: self.username.clone(),
|
||||||
|
password_hash: Some(password_hash),
|
||||||
|
private_key_pem,
|
||||||
|
wallet_address: None,
|
||||||
|
invite_code: None,
|
||||||
|
role,
|
||||||
};
|
};
|
||||||
|
create_user(db_client, user_data).await?;
|
||||||
|
println!("user created");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,10 +169,7 @@ pub struct SetPassword {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetPassword {
|
impl SetPassword {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
db_client: &impl DatabaseClient,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let password_hash = hash_password(&self.password)?;
|
let password_hash = hash_password(&self.password)?;
|
||||||
set_user_password(db_client, &self.id, password_hash).await?;
|
set_user_password(db_client, &self.id, password_hash).await?;
|
||||||
// Revoke all sessions
|
// Revoke all sessions
|
||||||
|
@ -187,15 +183,13 @@ impl SetPassword {
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct SetRole {
|
pub struct SetRole {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
|
#[clap(value_parser = ALLOWED_ROLES)]
|
||||||
role: String,
|
role: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetRole {
|
impl SetRole {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
let role = role_from_str(&self.role)?;
|
||||||
db_client: &impl DatabaseClient,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let role = Role::from_name(&self.role)?;
|
|
||||||
set_user_role(db_client, &self.id, role).await?;
|
set_user_role(db_client, &self.id, role).await?;
|
||||||
println!("role changed");
|
println!("role changed");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -214,23 +208,40 @@ impl RefetchActor {
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &mut impl DatabaseClient,
|
db_client: &mut impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let profile = get_profile_by_remote_actor_id(
|
let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?;
|
||||||
db_client,
|
|
||||||
&self.id,
|
|
||||||
).await?;
|
|
||||||
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
||||||
update_remote_profile(
|
update_remote_profile(
|
||||||
db_client,
|
db_client,
|
||||||
&config.instance(),
|
&config.instance(),
|
||||||
&config.media_dir(),
|
&MediaStorage::from(config),
|
||||||
profile,
|
profile,
|
||||||
actor,
|
actor,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
println!("profile updated");
|
println!("profile updated");
|
||||||
Ok(())
|
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
|
/// Delete profile
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct DeleteProfile {
|
pub struct DeleteProfile {
|
||||||
|
@ -247,12 +258,11 @@ impl DeleteProfile {
|
||||||
let mut maybe_delete_person = None;
|
let mut maybe_delete_person = None;
|
||||||
if profile.is_local() {
|
if profile.is_local() {
|
||||||
let user = get_user_by_id(db_client, &profile.id).await?;
|
let user = get_user_by_id(db_client, &profile.id).await?;
|
||||||
let activity =
|
let activity = prepare_delete_person(db_client, &config.instance(), &user).await?;
|
||||||
prepare_delete_person(db_client, &config.instance(), &user).await?;
|
|
||||||
maybe_delete_person = Some(activity);
|
maybe_delete_person = Some(activity);
|
||||||
};
|
};
|
||||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||||
deletion_queue.process(config).await;
|
remove_media(config, deletion_queue).await;
|
||||||
// Send Delete(Person) activities
|
// Send Delete(Person) activities
|
||||||
if let Some(activity) = maybe_delete_person {
|
if let Some(activity) = maybe_delete_person {
|
||||||
activity.enqueue(db_client).await?;
|
activity.enqueue(db_client).await?;
|
||||||
|
@ -278,16 +288,12 @@ impl DeletePost {
|
||||||
let mut maybe_delete_note = None;
|
let mut maybe_delete_note = None;
|
||||||
if post.author.is_local() {
|
if post.author.is_local() {
|
||||||
let author = get_user_by_id(db_client, &post.author.id).await?;
|
let author = get_user_by_id(db_client, &post.author.id).await?;
|
||||||
let activity = prepare_delete_note(
|
let activity =
|
||||||
db_client,
|
prepare_delete_note(db_client, &config.instance(), &author, &post).await?;
|
||||||
&config.instance(),
|
|
||||||
&author,
|
|
||||||
&post,
|
|
||||||
).await?;
|
|
||||||
maybe_delete_note = Some(activity);
|
maybe_delete_note = Some(activity);
|
||||||
};
|
};
|
||||||
let deletion_queue = delete_post(db_client, &post.id).await?;
|
let deletion_queue = delete_post(db_client, &post.id).await?;
|
||||||
deletion_queue.process(config).await;
|
remove_media(config, deletion_queue).await;
|
||||||
// Send Delete(Note) activity
|
// Send Delete(Note) activity
|
||||||
if let Some(activity) = maybe_delete_note {
|
if let Some(activity) = maybe_delete_note {
|
||||||
activity.enqueue(db_client).await?;
|
activity.enqueue(db_client).await?;
|
||||||
|
@ -310,13 +316,10 @@ impl DeleteEmoji {
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let emoji = get_emoji_by_name(
|
let emoji =
|
||||||
db_client,
|
get_emoji_by_name(db_client, &self.emoji_name, self.hostname.as_deref()).await?;
|
||||||
&self.emoji_name,
|
|
||||||
self.hostname.as_deref(),
|
|
||||||
).await?;
|
|
||||||
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
|
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
|
||||||
deletion_queue.process(config).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("emoji deleted");
|
println!("emoji deleted");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -338,9 +341,9 @@ impl DeleteExtraneousPosts {
|
||||||
let posts = find_extraneous_posts(db_client, &updated_before).await?;
|
let posts = find_extraneous_posts(db_client, &updated_before).await?;
|
||||||
for post_id in posts {
|
for post_id in posts {
|
||||||
let deletion_queue = delete_post(db_client, &post_id).await?;
|
let deletion_queue = delete_post(db_client, &post_id).await?;
|
||||||
deletion_queue.process(config).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("post {} deleted", post_id);
|
println!("post {} deleted", post_id);
|
||||||
};
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,11 +361,8 @@ impl DeleteUnusedAttachments {
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let created_before = days_before_now(self.days);
|
let created_before = days_before_now(self.days);
|
||||||
let deletion_queue = delete_unused_attachments(
|
let deletion_queue = delete_unused_attachments(db_client, &created_before).await?;
|
||||||
db_client,
|
remove_media(config, deletion_queue).await;
|
||||||
&created_before,
|
|
||||||
).await?;
|
|
||||||
deletion_queue.process(config).await;
|
|
||||||
println!("unused attachments deleted");
|
println!("unused attachments deleted");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -381,10 +381,9 @@ impl DeleteOrphanedFiles {
|
||||||
let media_dir = config.media_dir();
|
let media_dir = config.media_dir();
|
||||||
let mut files = vec![];
|
let mut files = vec![];
|
||||||
for maybe_path in std::fs::read_dir(&media_dir)? {
|
for maybe_path in std::fs::read_dir(&media_dir)? {
|
||||||
let file_name = maybe_path?.file_name()
|
let file_name = maybe_path?.file_name().to_string_lossy().to_string();
|
||||||
.to_string_lossy().to_string();
|
|
||||||
files.push(file_name);
|
files.push(file_name);
|
||||||
};
|
}
|
||||||
println!("found {} files", files.len());
|
println!("found {} files", files.len());
|
||||||
let orphaned = find_orphaned_files(db_client, files).await?;
|
let orphaned = find_orphaned_files(db_client, files).await?;
|
||||||
if !orphaned.is_empty() {
|
if !orphaned.is_empty() {
|
||||||
|
@ -412,9 +411,29 @@ impl DeleteEmptyProfiles {
|
||||||
for profile_id in profiles {
|
for profile_id in profiles {
|
||||||
let profile = get_profile_by_id(db_client, &profile_id).await?;
|
let profile = get_profile_by_id(db_client, &profile_id).await?;
|
||||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||||
deletion_queue.process(config).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("profile {} deleted", profile.acct);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,7 +463,7 @@ impl ListUnreachableActors {
|
||||||
profile.unreachable_since.unwrap().to_string(),
|
profile.unreachable_since.unwrap().to_string(),
|
||||||
profile.updated_at.to_string(),
|
profile.updated_at.to_string(),
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,11 +481,8 @@ impl ImportEmoji {
|
||||||
_config: &Config,
|
_config: &Config,
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let emoji = get_emoji_by_name_and_hostname(
|
let emoji =
|
||||||
db_client,
|
get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?;
|
||||||
&self.emoji_name,
|
|
||||||
&self.hostname,
|
|
||||||
).await?;
|
|
||||||
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
|
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
|
||||||
println!("emoji is too big");
|
println!("emoji is too big");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -478,7 +494,8 @@ impl ImportEmoji {
|
||||||
emoji.image,
|
emoji.image,
|
||||||
None,
|
None,
|
||||||
&get_min_datetime(),
|
&get_min_datetime(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
println!("added emoji to local collection");
|
println!("added emoji to local collection");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -494,9 +511,8 @@ impl UpdateCurrentBlock {
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
&self,
|
&self,
|
||||||
_config: &Config,
|
_config: &Config,
|
||||||
db_client: &impl DatabaseClient,
|
_db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
save_current_block_number(db_client, self.number).await?;
|
|
||||||
println!("current block updated");
|
println!("current block updated");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -531,18 +547,7 @@ pub struct CreateMoneroWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateMoneroWallet {
|
impl CreateMoneroWallet {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, _config: &Config) -> Result<(), Error> {
|
||||||
&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");
|
println!("wallet created");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -557,17 +562,9 @@ pub struct CheckExpiredInvoice {
|
||||||
impl CheckExpiredInvoice {
|
impl CheckExpiredInvoice {
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
&self,
|
&self,
|
||||||
config: &Config,
|
_config: &Config,
|
||||||
db_client: &impl DatabaseClient,
|
_db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
76
fedimovies-cli/src/main.rs
Normal file
76
fedimovies-cli/src/main.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use fedimovies::logger::configure_logger;
|
||||||
|
use fedimovies_config::parse_config;
|
||||||
|
use fedimovies_models::database::create_database_client;
|
||||||
|
use fedimovies_models::database::migrate::apply_migrations;
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
use cli::{Opts, SubCommand};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let opts: Opts = Opts::parse();
|
||||||
|
|
||||||
|
match opts.subcmd {
|
||||||
|
SubCommand::GenerateRsaKey(cmd) => cmd.execute(),
|
||||||
|
SubCommand::GenerateEthereumAddress(cmd) => cmd.execute(),
|
||||||
|
subcmd => {
|
||||||
|
// Other commands require initialized app
|
||||||
|
let (config, config_warnings) = parse_config();
|
||||||
|
configure_logger(config.log_level);
|
||||||
|
log::info!("config loaded from {}", config.config_path);
|
||||||
|
for warning in config_warnings {
|
||||||
|
log::warn!("{}", warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_config = config.database_url.parse().unwrap();
|
||||||
|
let db_client =
|
||||||
|
&mut create_database_client(&db_config, config.tls_ca_file.as_deref()).await;
|
||||||
|
apply_migrations(db_client).await;
|
||||||
|
|
||||||
|
match subcmd {
|
||||||
|
SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
|
SubCommand::ListInviteCodes(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
|
SubCommand::CreateUser(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
|
SubCommand::SetPassword(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
|
SubCommand::SetRole(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
|
SubCommand::RefetchActor(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
|
SubCommand::ReadOutbox(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
|
SubCommand::DeleteProfile(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
|
SubCommand::DeletePost(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
|
SubCommand::DeleteEmoji(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
|
SubCommand::DeleteExtraneousPosts(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::DeleteUnusedAttachments(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::DeleteOrphanedFiles(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::DeleteEmptyProfiles(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::PruneRemoteEmojis(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::ListUnreachableActors(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::ImportEmoji(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
|
SubCommand::UpdateCurrentBlock(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::ResetSubscriptions(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
|
||||||
|
SubCommand::CheckExpiredInvoice(cmd) => {
|
||||||
|
cmd.execute(&config, db_client).await.unwrap()
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "mitra-config"
|
name = "fedimovies-config"
|
||||||
version = "1.17.0"
|
version = "1.22.0"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.56"
|
rust-version = "1.68"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mitra-utils = { path = "../mitra-utils" }
|
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||||
|
|
||||||
# Used to read .env files
|
# Used to read .env files
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
|
@ -1,23 +1,26 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use log::{Level as LogLevel};
|
use log::Level as LogLevel;
|
||||||
use rsa::RsaPrivateKey;
|
use rsa::RsaPrivateKey;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use mitra_utils::urls::normalize_url;
|
use fedimovies_utils::urls::normalize_url;
|
||||||
|
|
||||||
use super::blockchain::BlockchainConfig;
|
|
||||||
use super::environment::Environment;
|
use super::environment::Environment;
|
||||||
use super::federation::FederationConfig;
|
use super::federation::FederationConfig;
|
||||||
use super::limits::Limits;
|
use super::limits::Limits;
|
||||||
use super::registration::RegistrationConfig;
|
use super::registration::RegistrationConfig;
|
||||||
use super::retention::RetentionConfig;
|
use super::retention::RetentionConfig;
|
||||||
use super::MITRA_VERSION;
|
use super::REEF_VERSION;
|
||||||
|
|
||||||
fn default_log_level() -> LogLevel { LogLevel::Info }
|
fn default_log_level() -> LogLevel {
|
||||||
|
LogLevel::Info
|
||||||
|
}
|
||||||
|
|
||||||
fn default_login_message() -> String { "Do not sign this message on other sites!".to_string() }
|
fn default_login_message() -> String {
|
||||||
|
"What?!".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -30,6 +33,8 @@ pub struct Config {
|
||||||
|
|
||||||
// Core settings
|
// Core settings
|
||||||
pub database_url: String,
|
pub database_url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub tls_ca_file: Option<PathBuf>,
|
||||||
pub storage_dir: PathBuf,
|
pub storage_dir: PathBuf,
|
||||||
pub web_client_dir: Option<PathBuf>,
|
pub web_client_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
@ -50,6 +55,11 @@ pub struct Config {
|
||||||
pub instance_short_description: String,
|
pub instance_short_description: String,
|
||||||
pub instance_description: String,
|
pub instance_description: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub tmdb_api_key: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub movie_user_password: Option<String>,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(super) instance_rsa_key: Option<RsaPrivateKey>,
|
pub(super) instance_rsa_key: Option<RsaPrivateKey>,
|
||||||
|
|
||||||
|
@ -73,17 +83,11 @@ pub struct Config {
|
||||||
pub(super) proxy_url: Option<String>,
|
pub(super) proxy_url: Option<String>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(super) federation: FederationConfig,
|
pub federation: FederationConfig,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub blocked_instances: Vec<String>,
|
pub blocked_instances: Vec<String>,
|
||||||
|
|
||||||
// Blockchain integrations
|
|
||||||
#[serde(rename = "blockchain")]
|
|
||||||
_blockchain: Option<BlockchainConfig>, // deprecated
|
|
||||||
#[serde(default)]
|
|
||||||
blockchains: Vec<BlockchainConfig>,
|
|
||||||
|
|
||||||
// IPFS
|
// IPFS
|
||||||
pub ipfs_api_url: Option<String>,
|
pub ipfs_api_url: Option<String>,
|
||||||
pub ipfs_gateway_url: Option<String>,
|
pub ipfs_gateway_url: Option<String>,
|
||||||
|
@ -100,7 +104,12 @@ impl Config {
|
||||||
actor_key: self.instance_rsa_key.clone().unwrap(),
|
actor_key: self.instance_rsa_key.clone().unwrap(),
|
||||||
proxy_url: self.federation.proxy_url.clone(),
|
proxy_url: self.federation.proxy_url.clone(),
|
||||||
onion_proxy_url: self.federation.onion_proxy_url.clone(),
|
onion_proxy_url: self.federation.onion_proxy_url.clone(),
|
||||||
is_private: matches!(self.environment, Environment::Development),
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,18 +120,6 @@ impl Config {
|
||||||
pub fn media_dir(&self) -> PathBuf {
|
pub fn media_dir(&self) -> PathBuf {
|
||||||
self.storage_dir.join("media")
|
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)]
|
#[derive(Clone)]
|
||||||
|
@ -133,8 +130,11 @@ pub struct Instance {
|
||||||
// Proxy for outgoing requests
|
// Proxy for outgoing requests
|
||||||
pub proxy_url: Option<String>,
|
pub proxy_url: Option<String>,
|
||||||
pub onion_proxy_url: Option<String>,
|
pub onion_proxy_url: Option<String>,
|
||||||
|
pub i2p_proxy_url: Option<String>,
|
||||||
// Private instance won't send signed HTTP requests
|
// Private instance won't send signed HTTP requests
|
||||||
pub is_private: bool,
|
pub is_private: bool,
|
||||||
|
pub fetcher_timeout: u64,
|
||||||
|
pub deliverer_timeout: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
@ -148,8 +148,8 @@ impl Instance {
|
||||||
|
|
||||||
pub fn agent(&self) -> String {
|
pub fn agent(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Mitra {version}; {instance_url}",
|
"Reef {version}; {instance_url}",
|
||||||
version=MITRA_VERSION,
|
version = REEF_VERSION,
|
||||||
instance_url = self.url(),
|
instance_url = self.url(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -158,21 +158,24 @@ impl Instance {
|
||||||
#[cfg(feature = "test-utils")]
|
#[cfg(feature = "test-utils")]
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn for_test(url: &str) -> Self {
|
pub fn for_test(url: &str) -> Self {
|
||||||
use mitra_utils::crypto_rsa::generate_weak_rsa_key;
|
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||||
Self {
|
Self {
|
||||||
_url: Url::parse(url).unwrap(),
|
_url: Url::parse(url).unwrap(),
|
||||||
actor_key: generate_weak_rsa_key().unwrap(),
|
actor_key: generate_weak_rsa_key().unwrap(),
|
||||||
proxy_url: None,
|
proxy_url: None,
|
||||||
onion_proxy_url: None,
|
onion_proxy_url: None,
|
||||||
|
i2p_proxy_url: None,
|
||||||
is_private: true,
|
is_private: true,
|
||||||
|
fetcher_timeout: 0,
|
||||||
|
deliverer_timeout: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use mitra_utils::crypto_rsa::generate_weak_rsa_key;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_instance_url_https_dns() {
|
fn test_instance_url_https_dns() {
|
||||||
|
@ -183,14 +186,17 @@ mod tests {
|
||||||
actor_key: instance_rsa_key,
|
actor_key: instance_rsa_key,
|
||||||
proxy_url: None,
|
proxy_url: None,
|
||||||
onion_proxy_url: None,
|
onion_proxy_url: None,
|
||||||
|
i2p_proxy_url: None,
|
||||||
is_private: true,
|
is_private: true,
|
||||||
|
fetcher_timeout: 0,
|
||||||
|
deliverer_timeout: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(instance.url(), "https://example.com");
|
assert_eq!(instance.url(), "https://example.com");
|
||||||
assert_eq!(instance.hostname(), "example.com");
|
assert_eq!(instance.hostname(), "example.com");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
instance.agent(),
|
instance.agent(),
|
||||||
format!("Mitra {}; https://example.com", MITRA_VERSION),
|
format!("Mitra {}; https://example.com", REEF_VERSION),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +209,10 @@ mod tests {
|
||||||
actor_key: instance_rsa_key,
|
actor_key: instance_rsa_key,
|
||||||
proxy_url: None,
|
proxy_url: None,
|
||||||
onion_proxy_url: None,
|
onion_proxy_url: None,
|
||||||
|
i2p_proxy_url: None,
|
||||||
is_private: true,
|
is_private: true,
|
||||||
|
fetcher_timeout: 0,
|
||||||
|
deliverer_timeout: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(instance.url(), "http://1.2.3.4:3777");
|
assert_eq!(instance.url(), "http://1.2.3.4:3777");
|
|
@ -10,9 +10,13 @@ pub enum Environment {
|
||||||
|
|
||||||
impl Default for Environment {
|
impl Default for Environment {
|
||||||
#[cfg(feature = "production")]
|
#[cfg(feature = "production")]
|
||||||
fn default() -> Self { Self::Production }
|
fn default() -> Self {
|
||||||
|
Self::Production
|
||||||
|
}
|
||||||
#[cfg(not(feature = "production"))]
|
#[cfg(not(feature = "production"))]
|
||||||
fn default() -> Self { Self::Development }
|
fn default() -> Self {
|
||||||
|
Self::Development
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Environment {
|
impl FromStr for Environment {
|
38
fedimovies-config/src/federation.rs
Normal file
38
fedimovies-config/src/federation.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
fn default_federation_enabled() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_fetcher_timeout() -> u64 {
|
||||||
|
300
|
||||||
|
}
|
||||||
|
const fn default_deliverer_timeout() -> u64 {
|
||||||
|
30
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize)]
|
||||||
|
pub struct FederationConfig {
|
||||||
|
#[serde(default = "default_federation_enabled")]
|
||||||
|
pub enabled: bool,
|
||||||
|
#[serde(default = "default_fetcher_timeout")]
|
||||||
|
pub(super) fetcher_timeout: u64,
|
||||||
|
#[serde(default = "default_deliverer_timeout")]
|
||||||
|
pub(super) deliverer_timeout: u64,
|
||||||
|
pub(super) proxy_url: Option<String>,
|
||||||
|
pub(super) onion_proxy_url: Option<String>,
|
||||||
|
pub(super) i2p_proxy_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FederationConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: default_federation_enabled(),
|
||||||
|
fetcher_timeout: default_fetcher_timeout(),
|
||||||
|
deliverer_timeout: default_deliverer_timeout(),
|
||||||
|
proxy_url: None,
|
||||||
|
onion_proxy_url: None,
|
||||||
|
i2p_proxy_url: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
mod blockchain;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod environment;
|
mod environment;
|
||||||
mod federation;
|
mod federation;
|
||||||
|
@ -7,17 +6,12 @@ mod loader;
|
||||||
mod registration;
|
mod registration;
|
||||||
mod retention;
|
mod retention;
|
||||||
|
|
||||||
pub use blockchain::{
|
|
||||||
BlockchainConfig,
|
|
||||||
EthereumConfig,
|
|
||||||
MoneroConfig,
|
|
||||||
};
|
|
||||||
pub use config::{Config, Instance};
|
pub use config::{Config, Instance};
|
||||||
pub use environment::Environment;
|
pub use environment::Environment;
|
||||||
pub use loader::parse_config;
|
pub use loader::parse_config;
|
||||||
pub use registration::{DefaultRole, RegistrationType};
|
pub use registration::{DefaultRole, RegistrationType};
|
||||||
|
|
||||||
pub const MITRA_VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const REEF_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
|
@ -1,19 +1,17 @@
|
||||||
use regex::Regex;
|
|
||||||
use serde::{
|
|
||||||
Deserialize,
|
|
||||||
Deserializer,
|
|
||||||
de::{Error as DeserializerError},
|
|
||||||
};
|
|
||||||
use super::ConfigError;
|
use super::ConfigError;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
|
||||||
|
|
||||||
const FILE_SIZE_RE: &str = r#"^(?i)(?P<size>\d+)(?P<unit>[kmg]?)b?$"#;
|
const FILE_SIZE_RE: &str = r#"^(?i)(?P<size>\d+)(?P<unit>[kmg]?)b?$"#;
|
||||||
|
|
||||||
fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
|
fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
|
||||||
let file_size_re = Regex::new(FILE_SIZE_RE)
|
let file_size_re = Regex::new(FILE_SIZE_RE).expect("regexp should be valid");
|
||||||
.expect("regexp should be valid");
|
let caps = file_size_re
|
||||||
let caps = file_size_re.captures(value)
|
.captures(value)
|
||||||
.ok_or(ConfigError("invalid file size"))?;
|
.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"))?;
|
.map_err(|_| ConfigError("invalid file size"))?;
|
||||||
let unit = caps["unit"].to_string().to_lowercase();
|
let unit = caps["unit"].to_string().to_lowercase();
|
||||||
let multiplier = match unit.as_str() {
|
let multiplier = match unit.as_str() {
|
||||||
|
@ -26,37 +24,49 @@ fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
|
||||||
Ok(size * multiplier)
|
Ok(size * multiplier)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_file_size<'de, D>(
|
fn deserialize_file_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
||||||
deserializer: D,
|
where
|
||||||
) -> Result<usize, D::Error>
|
D: Deserializer<'de>,
|
||||||
where D: Deserializer<'de>
|
|
||||||
{
|
{
|
||||||
let file_size_str = String::deserialize(deserializer)?;
|
let file_size_str = String::deserialize(deserializer)?;
|
||||||
let file_size = parse_file_size(&file_size_str)
|
let file_size = parse_file_size(&file_size_str).map_err(DeserializerError::custom)?;
|
||||||
.map_err(DeserializerError::custom)?;
|
|
||||||
Ok(file_size)
|
Ok(file_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_file_size_limit() -> usize { 20_000_000 } // 20 MB
|
const fn default_file_size_limit() -> usize {
|
||||||
|
20_000_000
|
||||||
|
} // 20 MB
|
||||||
|
const fn default_emoji_size_limit() -> usize {
|
||||||
|
500_000
|
||||||
|
} // 500 kB
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct MediaLimits {
|
pub struct MediaLimits {
|
||||||
#[serde(
|
#[serde(
|
||||||
default = "default_file_size_limit",
|
default = "default_file_size_limit",
|
||||||
deserialize_with = "deserialize_file_size",
|
deserialize_with = "deserialize_file_size"
|
||||||
)]
|
)]
|
||||||
pub file_size_limit: usize,
|
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 {
|
impl Default for MediaLimits {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
file_size_limit: default_file_size_limit(),
|
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)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct PostLimits {
|
pub struct PostLimits {
|
|
@ -4,12 +4,8 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use rsa::RsaPrivateKey;
|
use rsa::RsaPrivateKey;
|
||||||
|
|
||||||
use mitra_utils::{
|
use fedimovies_utils::{
|
||||||
crypto_rsa::{
|
crypto_rsa::{deserialize_private_key, generate_rsa_key, serialize_private_key},
|
||||||
deserialize_private_key,
|
|
||||||
generate_rsa_key,
|
|
||||||
serialize_private_key,
|
|
||||||
},
|
|
||||||
files::{set_file_permissions, write_file},
|
files::{set_file_permissions, write_file},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,16 +19,16 @@ struct EnvConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "production")]
|
#[cfg(feature = "production")]
|
||||||
const DEFAULT_CONFIG_PATH: &str = "/etc/mitra/config.yaml";
|
const DEFAULT_CONFIG_PATH: &str = "/etc/fedimovies/config.yaml";
|
||||||
#[cfg(not(feature = "production"))]
|
#[cfg(not(feature = "production"))]
|
||||||
const DEFAULT_CONFIG_PATH: &str = "config.yaml";
|
const DEFAULT_CONFIG_PATH: &str = "config.yaml";
|
||||||
|
|
||||||
fn parse_env() -> EnvConfig {
|
fn parse_env() -> EnvConfig {
|
||||||
dotenv::from_filename(".env.local").ok();
|
dotenv::from_filename(".env.local").ok();
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
let config_path = std::env::var("CONFIG_PATH")
|
let config_path = std::env::var("CONFIG_PATH").unwrap_or(DEFAULT_CONFIG_PATH.to_string());
|
||||||
.unwrap_or(DEFAULT_CONFIG_PATH.to_string());
|
let environment = std::env::var("ENVIRONMENT")
|
||||||
let environment = std::env::var("ENVIRONMENT").ok()
|
.ok()
|
||||||
.map(|val| Environment::from_str(&val).expect("invalid environment type"));
|
.map(|val| Environment::from_str(&val).expect("invalid environment type"));
|
||||||
EnvConfig {
|
EnvConfig {
|
||||||
config_path,
|
config_path,
|
||||||
|
@ -45,8 +41,7 @@ extern "C" {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_directory_owner(path: &Path) -> () {
|
fn check_directory_owner(path: &Path) -> () {
|
||||||
let metadata = std::fs::metadata(path)
|
let metadata = std::fs::metadata(path).expect("can't read file metadata");
|
||||||
.expect("can't read file metadata");
|
|
||||||
let owner_uid = metadata.uid();
|
let owner_uid = metadata.uid();
|
||||||
let current_uid = unsafe { geteuid() };
|
let current_uid = unsafe { geteuid() };
|
||||||
if owner_uid != current_uid {
|
if owner_uid != current_uid {
|
||||||
|
@ -63,16 +58,15 @@ fn check_directory_owner(path: &Path) -> () {
|
||||||
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
||||||
let private_key_path = storage_dir.join("instance_rsa_key");
|
let private_key_path = storage_dir.join("instance_rsa_key");
|
||||||
if private_key_path.exists() {
|
if private_key_path.exists() {
|
||||||
let private_key_str = std::fs::read_to_string(&private_key_path)
|
let private_key_str =
|
||||||
.expect("failed to read instance RSA key");
|
std::fs::read_to_string(&private_key_path).expect("failed to read instance RSA key");
|
||||||
let private_key = deserialize_private_key(&private_key_str)
|
let private_key =
|
||||||
.expect("failed to read instance RSA key");
|
deserialize_private_key(&private_key_str).expect("failed to read instance RSA key");
|
||||||
private_key
|
private_key
|
||||||
} else {
|
} else {
|
||||||
let private_key = generate_rsa_key()
|
let private_key = generate_rsa_key().expect("failed to generate RSA key");
|
||||||
.expect("failed to generate RSA key");
|
let private_key_str =
|
||||||
let private_key_str = serialize_private_key(&private_key)
|
serialize_private_key(&private_key).expect("failed to serialize RSA key");
|
||||||
.expect("failed to serialize RSA key");
|
|
||||||
write_file(private_key_str.as_bytes(), &private_key_path)
|
write_file(private_key_str.as_bytes(), &private_key_path)
|
||||||
.expect("failed to write instance RSA key");
|
.expect("failed to write instance RSA key");
|
||||||
set_file_permissions(&private_key_path, 0o600)
|
set_file_permissions(&private_key_path, 0o600)
|
||||||
|
@ -83,10 +77,9 @@ fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
||||||
|
|
||||||
pub fn parse_config() -> (Config, Vec<&'static str>) {
|
pub fn parse_config() -> (Config, Vec<&'static str>) {
|
||||||
let env = parse_env();
|
let env = parse_env();
|
||||||
let config_yaml = std::fs::read_to_string(&env.config_path)
|
let config_yaml =
|
||||||
.expect("failed to load config file");
|
std::fs::read_to_string(&env.config_path).expect("failed to load config file");
|
||||||
let mut config = serde_yaml::from_str::<Config>(&config_yaml)
|
let mut config = serde_yaml::from_str::<Config>(&config_yaml).expect("invalid yaml data");
|
||||||
.expect("invalid yaml data");
|
|
||||||
let mut warnings = vec![];
|
let mut warnings = vec![];
|
||||||
|
|
||||||
// Set parameters from environment
|
// Set parameters from environment
|
||||||
|
@ -102,14 +95,6 @@ pub fn parse_config() -> (Config, Vec<&'static str>) {
|
||||||
};
|
};
|
||||||
check_directory_owner(&config.storage_dir);
|
check_directory_owner(&config.storage_dir);
|
||||||
config.try_instance_url().expect("invalid instance URI");
|
config.try_instance_url().expect("invalid instance URI");
|
||||||
if let Some(blockchain_config) = config.blockchain() {
|
|
||||||
if let Some(ethereum_config) = blockchain_config.ethereum_config() {
|
|
||||||
ethereum_config.try_ethereum_chain_id().unwrap();
|
|
||||||
if !ethereum_config.contract_dir.exists() {
|
|
||||||
panic!("contract directory does not exist");
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
||||||
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
||||||
};
|
};
|
||||||
|
@ -117,7 +102,8 @@ pub fn parse_config() -> (Config, Vec<&'static str>) {
|
||||||
// Migrations
|
// Migrations
|
||||||
if let Some(registrations_open) = config.registrations_open {
|
if let Some(registrations_open) = config.registrations_open {
|
||||||
// Change type if 'registrations_open' parameter is used
|
// Change type if 'registrations_open' parameter is used
|
||||||
warnings.push("'registrations_open' setting is deprecated, use 'registration.type' instead");
|
warnings
|
||||||
|
.push("'registrations_open' setting is deprecated, use 'registration.type' instead");
|
||||||
if registrations_open {
|
if registrations_open {
|
||||||
config.registration.registration_type = RegistrationType::Open;
|
config.registration.registration_type = RegistrationType::Open;
|
||||||
} else {
|
} else {
|
|
@ -1,8 +1,4 @@
|
||||||
use serde::{
|
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
|
||||||
Deserialize,
|
|
||||||
Deserializer,
|
|
||||||
de::Error as DeserializerError,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum RegistrationType {
|
pub enum RegistrationType {
|
||||||
|
@ -11,12 +7,15 @@ pub enum RegistrationType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RegistrationType {
|
impl Default for RegistrationType {
|
||||||
fn default() -> Self { Self::Invite }
|
fn default() -> Self {
|
||||||
|
Self::Invite
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for RegistrationType {
|
impl<'de> Deserialize<'de> for RegistrationType {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'de>
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let registration_type_str = String::deserialize(deserializer)?;
|
let registration_type_str = String::deserialize(deserializer)?;
|
||||||
let registration_type = match registration_type_str.as_str() {
|
let registration_type = match registration_type_str.as_str() {
|
||||||
|
@ -35,12 +34,15 @@ pub enum DefaultRole {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DefaultRole {
|
impl Default for DefaultRole {
|
||||||
fn default() -> Self { Self::NormalUser }
|
fn default() -> Self {
|
||||||
|
Self::NormalUser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for DefaultRole {
|
impl<'de> Deserialize<'de> for DefaultRole {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where D: Deserializer<'de>
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let role_str = String::deserialize(deserializer)?;
|
let role_str = String::deserialize(deserializer)?;
|
||||||
let role = match role_str.as_str() {
|
let role = match role_str.as_str() {
|
46
fedimovies-models/Cargo.toml
Normal file
46
fedimovies-models/Cargo.toml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
[package]
|
||||||
|
name = "fedimovies-models"
|
||||||
|
version = "1.22.0"
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.68"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||||
|
|
||||||
|
# Used for working with dates
|
||||||
|
chrono = { version = "0.4.23", default-features = false, features = ["std", "serde"] }
|
||||||
|
# Used for pooling database connections
|
||||||
|
deadpool = "0.9.2"
|
||||||
|
deadpool-postgres = { version = "0.10.2", default-features = false }
|
||||||
|
# Used to work with hexadecimal strings
|
||||||
|
hex = { version = "0.4.3", features = ["serde"] }
|
||||||
|
# Used for logging
|
||||||
|
log = "0.4.14"
|
||||||
|
# Used for managing database migrations
|
||||||
|
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
|
||||||
|
# Used for serialization/deserialization
|
||||||
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
|
serde_json = "1.0.89"
|
||||||
|
# Used for creating error types
|
||||||
|
thiserror = "1.0.37"
|
||||||
|
# Async runtime
|
||||||
|
tokio = { version = "1.20.4", features = [] }
|
||||||
|
# Used for working with Postgresql database
|
||||||
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
postgres-openssl = "0.5.0"
|
||||||
|
tokio-postgres = { version = "0.7.6", features = ["with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
|
||||||
|
postgres-types = { version = "0.2.3", features = ["derive", "with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
|
||||||
|
postgres-protocol = "0.6.4"
|
||||||
|
# Used to construct PostgreSQL queries
|
||||||
|
postgres_query = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
|
||||||
|
postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
|
||||||
|
# Used to work with UUIDs
|
||||||
|
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
fedimovies-utils = { path = "../fedimovies-utils", features = ["test-utils"] }
|
||||||
|
serial_test = "0.7.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-utils = []
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE actor_profile ADD COLUMN manually_approves_followers BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
ALTER TABLE actor_profile ALTER COLUMN manually_approves_followers DROP DEFAULT;
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE actor_profile ADD COLUMN aliases JSONB NOT NULL DEFAULT '[]';
|
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE relationship ADD CONSTRAINT relationship_source_id_target_id_check CHECK (source_id != target_id);
|
||||||
|
ALTER TABLE follow_request ADD CONSTRAINT follow_request_source_id_target_id_check CHECK (source_id != target_id);
|
||||||
|
ALTER TABLE post_link ADD CONSTRAINT post_link_source_id_target_id_check CHECK (source_id != target_id);
|
||||||
|
ALTER TABLE invoice ADD CONSTRAINT invoice_sender_id_recipient_id_check CHECK (sender_id != recipient_id);
|
||||||
|
ALTER TABLE subscription ADD CONSTRAINT subscription_sender_id_recipient_id_check CHECK (sender_id != recipient_id);
|
|
@ -0,0 +1,6 @@
|
||||||
|
ALTER TABLE actor_profile ALTER COLUMN actor_id TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE oauth_application ALTER COLUMN redirect_uri TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE follow_request ALTER COLUMN activity_id TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE post ALTER COLUMN object_id TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE post_reaction ALTER COLUMN activity_id TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE emoji ALTER COLUMN object_id TYPE VARCHAR(2000);
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE user_account ADD COLUMN client_config JSONB NOT NULL DEFAULT '{}';
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE post ADD COLUMN is_sensitive BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
ALTER TABLE post ALTER COLUMN is_sensitive DROP DEFAULT;
|
|
@ -26,16 +26,18 @@ CREATE TABLE actor_profile (
|
||||||
bio_source TEXT,
|
bio_source TEXT,
|
||||||
avatar JSONB,
|
avatar JSONB,
|
||||||
banner JSONB,
|
banner JSONB,
|
||||||
|
manually_approves_followers BOOLEAN NOT NULL,
|
||||||
identity_proofs JSONB NOT NULL DEFAULT '[]',
|
identity_proofs JSONB NOT NULL DEFAULT '[]',
|
||||||
payment_options JSONB NOT NULL DEFAULT '[]',
|
payment_options JSONB NOT NULL DEFAULT '[]',
|
||||||
extra_fields 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,
|
follower_count INTEGER NOT NULL CHECK (follower_count >= 0) DEFAULT 0,
|
||||||
following_count INTEGER NOT NULL CHECK (following_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,
|
subscriber_count INTEGER NOT NULL CHECK (subscriber_count >= 0) DEFAULT 0,
|
||||||
post_count INTEGER NOT NULL CHECK (post_count >= 0) DEFAULT 0,
|
post_count INTEGER NOT NULL CHECK (post_count >= 0) DEFAULT 0,
|
||||||
emojis JSONB NOT NULL DEFAULT '[]',
|
emojis JSONB NOT NULL DEFAULT '[]',
|
||||||
actor_json JSONB,
|
actor_json JSONB,
|
||||||
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
actor_id VARCHAR(2000) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
unreachable_since TIMESTAMP WITH TIME ZONE,
|
unreachable_since TIMESTAMP WITH TIME ZONE,
|
||||||
|
@ -56,6 +58,7 @@ CREATE TABLE user_account (
|
||||||
private_key TEXT NOT NULL,
|
private_key TEXT NOT NULL,
|
||||||
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
|
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
|
||||||
user_role SMALLINT NOT NULL,
|
user_role SMALLINT NOT NULL,
|
||||||
|
client_config JSONB NOT NULL DEFAULT '{}',
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -64,7 +67,7 @@ CREATE TABLE oauth_application (
|
||||||
app_name VARCHAR(100) NOT NULL,
|
app_name VARCHAR(100) NOT NULL,
|
||||||
website VARCHAR(100),
|
website VARCHAR(100),
|
||||||
scopes VARCHAR(200) NOT NULL,
|
scopes VARCHAR(200) NOT NULL,
|
||||||
redirect_uri VARCHAR(200) NOT NULL,
|
redirect_uri VARCHAR(2000) NOT NULL,
|
||||||
client_id UUID UNIQUE NOT NULL,
|
client_id UUID UNIQUE NOT NULL,
|
||||||
client_secret VARCHAR(100) NOT NULL,
|
client_secret VARCHAR(100) NOT NULL,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
@ -93,16 +96,18 @@ CREATE TABLE relationship (
|
||||||
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||||
target_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,
|
relationship_type SMALLINT NOT NULL,
|
||||||
UNIQUE (source_id, target_id, relationship_type)
|
UNIQUE (source_id, target_id, relationship_type),
|
||||||
|
CHECK (source_id != target_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE follow_request (
|
CREATE TABLE follow_request (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||||
target_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(250) UNIQUE,
|
activity_id VARCHAR(2000) UNIQUE,
|
||||||
request_status SMALLINT NOT NULL,
|
request_status SMALLINT NOT NULL,
|
||||||
UNIQUE (source_id, target_id)
|
UNIQUE (source_id, target_id),
|
||||||
|
CHECK (source_id != target_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE post (
|
CREATE TABLE post (
|
||||||
|
@ -111,11 +116,12 @@ CREATE TABLE post (
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||||
repost_of_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
repost_of_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||||
visilibity SMALLINT NOT NULL,
|
visibility SMALLINT NOT NULL,
|
||||||
|
is_sensitive BOOLEAN NOT NULL,
|
||||||
reply_count INTEGER NOT NULL CHECK (reply_count >= 0) DEFAULT 0,
|
reply_count INTEGER NOT NULL CHECK (reply_count >= 0) DEFAULT 0,
|
||||||
reaction_count INTEGER NOT NULL CHECK (reaction_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,
|
repost_count INTEGER NOT NULL CHECK (repost_count >= 0) DEFAULT 0,
|
||||||
object_id VARCHAR(200) UNIQUE,
|
object_id VARCHAR(2000) UNIQUE,
|
||||||
ipfs_cid VARCHAR(200),
|
ipfs_cid VARCHAR(200),
|
||||||
token_id INTEGER,
|
token_id INTEGER,
|
||||||
token_tx_id VARCHAR(200),
|
token_tx_id VARCHAR(200),
|
||||||
|
@ -128,7 +134,7 @@ CREATE TABLE post_reaction (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||||
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||||
activity_id VARCHAR(250) UNIQUE,
|
activity_id VARCHAR(2000) UNIQUE,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||||
UNIQUE (author_id, post_id)
|
UNIQUE (author_id, post_id)
|
||||||
);
|
);
|
||||||
|
@ -164,7 +170,8 @@ CREATE TABLE post_tag (
|
||||||
CREATE TABLE post_link (
|
CREATE TABLE post_link (
|
||||||
source_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
source_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||||
target_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
target_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||||
PRIMARY KEY (source_id, target_id)
|
PRIMARY KEY (source_id, target_id),
|
||||||
|
CHECK (source_id != target_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE emoji (
|
CREATE TABLE emoji (
|
||||||
|
@ -172,7 +179,7 @@ CREATE TABLE emoji (
|
||||||
emoji_name VARCHAR(100) NOT NULL,
|
emoji_name VARCHAR(100) NOT NULL,
|
||||||
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
|
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
|
||||||
image JSONB NOT NULL,
|
image JSONB NOT NULL,
|
||||||
object_id VARCHAR(250) UNIQUE,
|
object_id VARCHAR(2000) UNIQUE,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
UNIQUE (emoji_name, hostname),
|
UNIQUE (emoji_name, hostname),
|
||||||
CHECK ((hostname IS NULL) = (object_id IS NULL))
|
CHECK ((hostname IS NULL) = (object_id IS NULL))
|
||||||
|
@ -217,7 +224,8 @@ CREATE TABLE invoice (
|
||||||
amount BIGINT NOT NULL CHECK (amount >= 0),
|
amount BIGINT NOT NULL CHECK (amount >= 0),
|
||||||
invoice_status SMALLINT NOT NULL DEFAULT 1,
|
invoice_status SMALLINT NOT NULL DEFAULT 1,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
UNIQUE (chain_id, payment_address)
|
UNIQUE (chain_id, payment_address),
|
||||||
|
CHECK (sender_id != recipient_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE subscription (
|
CREATE TABLE subscription (
|
||||||
|
@ -228,5 +236,6 @@ CREATE TABLE subscription (
|
||||||
chain_id VARCHAR(50) NOT NULL,
|
chain_id VARCHAR(50) NOT NULL,
|
||||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
UNIQUE (sender_id, recipient_id)
|
UNIQUE (sender_id, recipient_id),
|
||||||
|
CHECK (sender_id != recipient_id)
|
||||||
);
|
);
|
|
@ -1,14 +1,11 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use mitra_utils::id::generate_ulid;
|
use fedimovies_utils::id::generate_ulid;
|
||||||
|
|
||||||
|
use crate::cleanup::{find_orphaned_files, find_orphaned_ipfs_objects, DeletionQueue};
|
||||||
use crate::database::{DatabaseClient, DatabaseError};
|
use crate::database::{DatabaseClient, DatabaseError};
|
||||||
use crate::models::cleanup::{
|
|
||||||
find_orphaned_files,
|
|
||||||
find_orphaned_ipfs_objects,
|
|
||||||
DeletionQueue,
|
|
||||||
};
|
|
||||||
use super::types::DbMediaAttachment;
|
use super::types::DbMediaAttachment;
|
||||||
|
|
||||||
pub async fn create_attachment(
|
pub async fn create_attachment(
|
||||||
|
@ -19,9 +16,9 @@ pub async fn create_attachment(
|
||||||
media_type: Option<String>,
|
media_type: Option<String>,
|
||||||
) -> Result<DbMediaAttachment, DatabaseError> {
|
) -> Result<DbMediaAttachment, DatabaseError> {
|
||||||
let attachment_id = generate_ulid();
|
let attachment_id = generate_ulid();
|
||||||
let file_size: i32 = file_size.try_into()
|
let file_size: i32 = file_size.try_into().expect("value should be within bounds");
|
||||||
.expect("value should be within bounds");
|
let inserted_row = db_client
|
||||||
let inserted_row = db_client.query_one(
|
.query_one(
|
||||||
"
|
"
|
||||||
INSERT INTO media_attachment (
|
INSERT INTO media_attachment (
|
||||||
id,
|
id,
|
||||||
|
@ -40,7 +37,8 @@ pub async fn create_attachment(
|
||||||
&file_size,
|
&file_size,
|
||||||
&media_type,
|
&media_type,
|
||||||
],
|
],
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
|
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
|
||||||
Ok(db_attachment)
|
Ok(db_attachment)
|
||||||
}
|
}
|
||||||
|
@ -50,7 +48,8 @@ pub async fn set_attachment_ipfs_cid(
|
||||||
attachment_id: &Uuid,
|
attachment_id: &Uuid,
|
||||||
ipfs_cid: &str,
|
ipfs_cid: &str,
|
||||||
) -> Result<DbMediaAttachment, DatabaseError> {
|
) -> Result<DbMediaAttachment, DatabaseError> {
|
||||||
let maybe_row = db_client.query_opt(
|
let maybe_row = db_client
|
||||||
|
.query_opt(
|
||||||
"
|
"
|
||||||
UPDATE media_attachment
|
UPDATE media_attachment
|
||||||
SET ipfs_cid = $1
|
SET ipfs_cid = $1
|
||||||
|
@ -58,7 +57,8 @@ pub async fn set_attachment_ipfs_cid(
|
||||||
RETURNING media_attachment
|
RETURNING media_attachment
|
||||||
",
|
",
|
||||||
&[&ipfs_cid, &attachment_id],
|
&[&ipfs_cid, &attachment_id],
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let row = maybe_row.ok_or(DatabaseError::NotFound("attachment"))?;
|
let row = maybe_row.ok_or(DatabaseError::NotFound("attachment"))?;
|
||||||
let db_attachment = row.try_get("media_attachment")?;
|
let db_attachment = row.try_get("media_attachment")?;
|
||||||
Ok(db_attachment)
|
Ok(db_attachment)
|
||||||
|
@ -68,14 +68,16 @@ pub async fn delete_unused_attachments(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
created_before: &DateTime<Utc>,
|
created_before: &DateTime<Utc>,
|
||||||
) -> Result<DeletionQueue, DatabaseError> {
|
) -> Result<DeletionQueue, DatabaseError> {
|
||||||
let rows = db_client.query(
|
let rows = db_client
|
||||||
|
.query(
|
||||||
"
|
"
|
||||||
DELETE FROM media_attachment
|
DELETE FROM media_attachment
|
||||||
WHERE post_id IS NULL AND created_at < $1
|
WHERE post_id IS NULL AND created_at < $1
|
||||||
RETURNING file_name, ipfs_cid
|
RETURNING file_name, ipfs_cid
|
||||||
",
|
",
|
||||||
&[&created_before],
|
&[&created_before],
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
let mut files = vec![];
|
let mut files = vec![];
|
||||||
let mut ipfs_objects = vec![];
|
let mut ipfs_objects = vec![];
|
||||||
for row in rows {
|
for row in rows {
|
||||||
|
@ -84,7 +86,7 @@ pub async fn delete_unused_attachments(
|
||||||
if let Some(ipfs_cid) = row.try_get("ipfs_cid")? {
|
if let Some(ipfs_cid) = row.try_get("ipfs_cid")? {
|
||||||
ipfs_objects.push(ipfs_cid);
|
ipfs_objects.push(ipfs_cid);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
let orphaned_files = find_orphaned_files(db_client, files).await?;
|
let orphaned_files = find_orphaned_files(db_client, files).await?;
|
||||||
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(db_client, ipfs_objects).await?;
|
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(db_client, ipfs_objects).await?;
|
||||||
Ok(DeletionQueue {
|
Ok(DeletionQueue {
|
||||||
|
@ -95,13 +97,10 @@ pub async fn delete_unused_attachments(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serial_test::serial;
|
|
||||||
use crate::database::test_utils::create_test_database;
|
|
||||||
use crate::models::{
|
|
||||||
profiles::types::ProfileCreateData,
|
|
||||||
profiles::queries::create_profile,
|
|
||||||
};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::database::test_utils::create_test_database;
|
||||||
|
use crate::profiles::{queries::create_profile, types::ProfileCreateData};
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
|
@ -121,11 +120,13 @@ mod tests {
|
||||||
file_name.to_string(),
|
file_name.to_string(),
|
||||||
file_size,
|
file_size,
|
||||||
Some(media_type.to_string()),
|
Some(media_type.to_string()),
|
||||||
).await.unwrap();
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(attachment.owner_id, profile.id);
|
assert_eq!(attachment.owner_id, profile.id);
|
||||||
assert_eq!(attachment.file_name, file_name);
|
assert_eq!(attachment.file_name, file_name);
|
||||||
assert_eq!(attachment.file_size.unwrap(), file_size as i32);
|
assert_eq!(attachment.file_size.unwrap(), file_size as i32);
|
||||||
assert_eq!(attachment.media_type.unwrap(), media_type);
|
assert_eq!(attachment.media_type.unwrap(), media_type);
|
||||||
assert_eq!(attachment.post_id.is_none(), true);
|
assert!(attachment.post_id.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -35,7 +35,7 @@ impl AttachmentType {
|
||||||
} else {
|
} else {
|
||||||
Self::Unknown
|
Self::Unknown
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => Self::Unknown,
|
None => Self::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,8 +2,8 @@ use chrono::{DateTime, Utc};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::database::{DatabaseClient, DatabaseError};
|
|
||||||
use super::types::{DbBackgroundJob, JobStatus, JobType};
|
use super::types::{DbBackgroundJob, JobStatus, JobType};
|
||||||
|
use crate::database::{DatabaseClient, DatabaseError};
|
||||||
|
|
||||||
pub async fn enqueue_job(
|
pub async fn enqueue_job(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
|
@ -12,7 +12,8 @@ pub async fn enqueue_job(
|
||||||
scheduled_for: &DateTime<Utc>,
|
scheduled_for: &DateTime<Utc>,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
let job_id = Uuid::new_v4();
|
let job_id = Uuid::new_v4();
|
||||||
db_client.execute(
|
db_client
|
||||||
|
.execute(
|
||||||
"
|
"
|
||||||
INSERT INTO background_job (
|
INSERT INTO background_job (
|
||||||
id,
|
id,
|
||||||
|
@ -23,7 +24,8 @@ pub async fn enqueue_job(
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
",
|
",
|
||||||
&[&job_id, &job_type, &job_data, &scheduled_for],
|
&[&job_id, &job_type, &job_data, &scheduled_for],
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +33,12 @@ pub async fn get_job_batch(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
job_type: &JobType,
|
job_type: &JobType,
|
||||||
batch_size: u32,
|
batch_size: u32,
|
||||||
|
job_timeout: u32,
|
||||||
) -> Result<Vec<DbBackgroundJob>, DatabaseError> {
|
) -> Result<Vec<DbBackgroundJob>, DatabaseError> {
|
||||||
let rows = db_client.query(
|
// https://github.com/sfackler/rust-postgres/issues/60
|
||||||
|
let job_timeout_pg = format!("{}S", job_timeout); // interval
|
||||||
|
let rows = db_client
|
||||||
|
.query(
|
||||||
"
|
"
|
||||||
UPDATE background_job
|
UPDATE background_job
|
||||||
SET
|
SET
|
||||||
|
@ -43,9 +49,18 @@ pub async fn get_job_batch(
|
||||||
FROM background_job
|
FROM background_job
|
||||||
WHERE
|
WHERE
|
||||||
job_type = $2
|
job_type = $2
|
||||||
AND job_status = $3
|
|
||||||
AND scheduled_for < CURRENT_TIMESTAMP
|
AND scheduled_for < CURRENT_TIMESTAMP
|
||||||
ORDER BY scheduled_for ASC
|
AND (
|
||||||
|
-- queued
|
||||||
|
job_status = $3
|
||||||
|
-- running
|
||||||
|
OR job_status = $1
|
||||||
|
AND updated_at < CURRENT_TIMESTAMP - $5::text::interval
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
-- queued jobs first
|
||||||
|
job_status ASC,
|
||||||
|
scheduled_for ASC
|
||||||
LIMIT $4
|
LIMIT $4
|
||||||
)
|
)
|
||||||
RETURNING background_job
|
RETURNING background_job
|
||||||
|
@ -55,9 +70,12 @@ pub async fn get_job_batch(
|
||||||
&job_type,
|
&job_type,
|
||||||
&JobStatus::Queued,
|
&JobStatus::Queued,
|
||||||
&i64::from(batch_size),
|
&i64::from(batch_size),
|
||||||
|
&job_timeout_pg,
|
||||||
],
|
],
|
||||||
).await?;
|
)
|
||||||
let jobs = rows.iter()
|
.await?;
|
||||||
|
let jobs = rows
|
||||||
|
.iter()
|
||||||
.map(|row| row.try_get("background_job"))
|
.map(|row| row.try_get("background_job"))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
Ok(jobs)
|
Ok(jobs)
|
||||||
|
@ -67,13 +85,15 @@ pub async fn delete_job_from_queue(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
job_id: &Uuid,
|
job_id: &Uuid,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
let deleted_count = db_client.execute(
|
let deleted_count = db_client
|
||||||
|
.execute(
|
||||||
"
|
"
|
||||||
DELETE FROM background_job
|
DELETE FROM background_job
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
&[&job_id],
|
&[&job_id],
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
if deleted_count == 0 {
|
if deleted_count == 0 {
|
||||||
return Err(DatabaseError::NotFound("background job"));
|
return Err(DatabaseError::NotFound("background job"));
|
||||||
};
|
};
|
||||||
|
@ -82,10 +102,10 @@ pub async fn delete_job_from_queue(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::database::test_utils::create_test_database;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use crate::database::test_utils::create_test_database;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
|
@ -98,20 +118,22 @@ mod tests {
|
||||||
"failure_count": 0,
|
"failure_count": 0,
|
||||||
});
|
});
|
||||||
let scheduled_for = Utc::now();
|
let scheduled_for = Utc::now();
|
||||||
enqueue_job(db_client, &job_type, &job_data, &scheduled_for).await.unwrap();
|
enqueue_job(db_client, &job_type, &job_data, &scheduled_for)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let batch_1 = get_job_batch(db_client, &job_type, 10).await.unwrap();
|
let batch_1 = get_job_batch(db_client, &job_type, 10, 3600).await.unwrap();
|
||||||
assert_eq!(batch_1.len(), 1);
|
assert_eq!(batch_1.len(), 1);
|
||||||
let job = &batch_1[0];
|
let job = &batch_1[0];
|
||||||
assert_eq!(job.job_type, job_type);
|
assert_eq!(job.job_type, job_type);
|
||||||
assert_eq!(job.job_data, job_data);
|
assert_eq!(job.job_data, job_data);
|
||||||
assert_eq!(job.job_status, JobStatus::Running);
|
assert_eq!(job.job_status, JobStatus::Running);
|
||||||
|
|
||||||
let batch_2 = get_job_batch(db_client, &job_type, 10).await.unwrap();
|
let batch_2 = get_job_batch(db_client, &job_type, 10, 3600).await.unwrap();
|
||||||
assert_eq!(batch_2.len(), 0);
|
assert_eq!(batch_2.len(), 0);
|
||||||
|
|
||||||
delete_job_from_queue(db_client, &job.id).await.unwrap();
|
delete_job_from_queue(db_client, &job.id).await.unwrap();
|
||||||
let batch_3 = get_job_batch(db_client, &job_type, 10).await.unwrap();
|
let batch_3 = get_job_batch(db_client, &job_type, 10, 3600).await.unwrap();
|
||||||
assert_eq!(batch_3.len(), 0);
|
assert_eq!(batch_3.len(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde_json::Value;
|
|
||||||
use postgres_types::FromSql;
|
use postgres_types::FromSql;
|
||||||
|
use serde_json::Value;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::database::{
|
use crate::database::{
|
|
@ -1,39 +1,16 @@
|
||||||
use mitra_config::Config;
|
|
||||||
|
|
||||||
use crate::database::{DatabaseClient, DatabaseError};
|
use crate::database::{DatabaseClient, DatabaseError};
|
||||||
use crate::ipfs::store as ipfs_store;
|
|
||||||
use crate::media::remove_files;
|
|
||||||
|
|
||||||
pub struct DeletionQueue {
|
pub struct DeletionQueue {
|
||||||
pub files: Vec<String>,
|
pub files: Vec<String>,
|
||||||
pub ipfs_objects: Vec<String>,
|
pub ipfs_objects: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeletionQueue {
|
|
||||||
pub async fn process(self, config: &Config) -> () {
|
|
||||||
remove_files(self.files, &config.media_dir());
|
|
||||||
if !self.ipfs_objects.is_empty() {
|
|
||||||
match &config.ipfs_api_url {
|
|
||||||
Some(ipfs_api_url) => {
|
|
||||||
ipfs_store::remove(ipfs_api_url, self.ipfs_objects).await
|
|
||||||
.unwrap_or_else(|err| log::error!("{}", err));
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
log::error!(
|
|
||||||
"can not remove objects because IPFS API URL is not set: {:?}",
|
|
||||||
self.ipfs_objects,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find_orphaned_files(
|
pub async fn find_orphaned_files(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
files: Vec<String>,
|
files: Vec<String>,
|
||||||
) -> Result<Vec<String>, DatabaseError> {
|
) -> Result<Vec<String>, DatabaseError> {
|
||||||
let rows = db_client.query(
|
let rows = db_client
|
||||||
|
.query(
|
||||||
"
|
"
|
||||||
SELECT DISTINCT fname
|
SELECT DISTINCT fname
|
||||||
FROM unnest($1::text[]) AS fname
|
FROM unnest($1::text[]) AS fname
|
||||||
|
@ -52,8 +29,10 @@ pub async fn find_orphaned_files(
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
&[&files],
|
&[&files],
|
||||||
).await?;
|
)
|
||||||
let orphaned_files = rows.iter()
|
.await?;
|
||||||
|
let orphaned_files = rows
|
||||||
|
.iter()
|
||||||
.map(|row| row.try_get("fname"))
|
.map(|row| row.try_get("fname"))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
Ok(orphaned_files)
|
Ok(orphaned_files)
|
||||||
|
@ -63,7 +42,8 @@ pub async fn find_orphaned_ipfs_objects(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
ipfs_objects: Vec<String>,
|
ipfs_objects: Vec<String>,
|
||||||
) -> Result<Vec<String>, DatabaseError> {
|
) -> Result<Vec<String>, DatabaseError> {
|
||||||
let rows = db_client.query(
|
let rows = db_client
|
||||||
|
.query(
|
||||||
"
|
"
|
||||||
SELECT DISTINCT cid
|
SELECT DISTINCT cid
|
||||||
FROM unnest($1::text[]) AS cid
|
FROM unnest($1::text[]) AS cid
|
||||||
|
@ -76,8 +56,10 @@ pub async fn find_orphaned_ipfs_objects(
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
&[&ipfs_objects],
|
&[&ipfs_objects],
|
||||||
).await?;
|
)
|
||||||
let orphaned_ipfs_objects = rows.iter()
|
.await?;
|
||||||
|
let orphaned_ipfs_objects = rows
|
||||||
|
.iter()
|
||||||
.map(|row| row.try_get("cid"))
|
.map(|row| row.try_get("cid"))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
Ok(orphaned_ipfs_objects)
|
Ok(orphaned_ipfs_objects)
|
|
@ -12,7 +12,7 @@ macro_rules! int_enum_from_sql {
|
||||||
|
|
||||||
postgres_types::accepts!(INT2);
|
postgres_types::accepts!(INT2);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! int_enum_to_sql {
|
macro_rules! int_enum_to_sql {
|
||||||
|
@ -31,7 +31,7 @@ macro_rules! int_enum_to_sql {
|
||||||
postgres_types::accepts!(INT2);
|
postgres_types::accepts!(INT2);
|
||||||
postgres_types::to_sql_checked!();
|
postgres_types::to_sql_checked!();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use {int_enum_from_sql, int_enum_to_sql};
|
pub(crate) use {int_enum_from_sql, int_enum_to_sql};
|
|
@ -14,7 +14,7 @@ macro_rules! json_from_sql {
|
||||||
|
|
||||||
postgres_types::accepts!(JSON, JSONB);
|
postgres_types::accepts!(JSON, JSONB);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements ToSql trait for any serializable type
|
/// Implements ToSql trait for any serializable type
|
||||||
|
@ -33,7 +33,7 @@ macro_rules! json_to_sql {
|
||||||
postgres_types::accepts!(JSON, JSONB);
|
postgres_types::accepts!(JSON, JSONB);
|
||||||
postgres_types::to_sql_checked!();
|
postgres_types::to_sql_checked!();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use {json_from_sql, json_to_sql};
|
pub(crate) use {json_from_sql, json_to_sql};
|
|
@ -8,7 +8,8 @@ mod embedded {
|
||||||
pub async fn apply_migrations(db_client: &mut Client) {
|
pub async fn apply_migrations(db_client: &mut Client) {
|
||||||
let migration_report = embedded::migrations::runner()
|
let migration_report = embedded::migrations::runner()
|
||||||
.run_async(db_client)
|
.run_async(db_client)
|
||||||
.await.unwrap();
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for migration in migration_report.applied_migrations() {
|
for migration in migration_report.applied_migrations() {
|
||||||
log::info!(
|
log::info!(
|
113
fedimovies-models/src/database/mod.rs
Normal file
113
fedimovies-models/src/database/mod.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue