Compare commits
337 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 | ||
|
773c36f3df | ||
|
43b56be722 | ||
|
1d4bb200d2 | ||
|
8b8f1bb678 | ||
|
a3a4579e03 | ||
|
c7fd3ddc83 | ||
|
c87c5da17c | ||
|
d4f701332f | ||
|
8400241ab4 | ||
|
43d084de8d | ||
|
cb7bc04b64 | ||
|
2b6de17fb9 | ||
|
0e9879bacb | ||
|
138c1e915e | ||
|
4f64b2c615 | ||
|
133e1349cf | ||
|
268707a78a | ||
|
b5365099a4 | ||
|
a300a822ec | ||
|
161d29e00e | ||
|
6d1ed4571b | ||
|
50f31e96fc | ||
|
94a5f3a3cd | ||
|
0817177282 | ||
|
f17c9d9f76 | ||
|
522fd5bafa | ||
|
452de34780 | ||
|
ba1c694294 | ||
|
70c2d2aa25 | ||
|
2787efc83f | ||
|
bd53e147ca | ||
|
4204350375 | ||
|
6335e216a9 | ||
|
0995186bc8 | ||
|
d93c2ca23d | ||
|
1b1e2a1521 | ||
|
721238d897 | ||
|
849b201ab9 | ||
|
8d479ac15d | ||
|
cc51c2c647 | ||
|
82b6c4e7cf | ||
|
baec22272d | ||
|
49b0011a9c | ||
|
e02ebebe02 | ||
|
62069dc011 | ||
|
c201f3ea2b | ||
|
e4254e7a3d | ||
|
971b541826 | ||
|
ca2e541ff5 | ||
|
0f3c247069 | ||
|
49f51f44d8 | ||
|
f5c012769f | ||
|
f66e0b812f | ||
|
56e0ed8f5d | ||
|
bacb8c8380 | ||
|
59a5f7b4fc | ||
|
21ea50279e | ||
|
0245eb59a2 | ||
|
872fe8fef3 | ||
|
d0b4d77519 | ||
|
7a3ec91581 | ||
|
227eef2d6b | ||
|
6fa4ed96c8 | ||
|
e1e9851d5c | ||
|
c796cddff8 | ||
|
2b5d4562aa | ||
|
50176b00cc | ||
|
2f621201f8 | ||
|
21054de712 | ||
|
e8ea52adba | ||
|
10f2596830 | ||
|
7d3c558ede | ||
|
bcef9bb989 | ||
|
f1972be8db | ||
|
a9cb1c6a83 | ||
|
1d16fb45a5 | ||
|
23b44ce0db | ||
|
9fd6724819 | ||
|
6945ded963 | ||
|
bc3184cf77 | ||
|
80b9032cbc | ||
|
c461fbc268 | ||
|
2acf50fa01 | ||
|
0988c0c91e | ||
|
44d013bc5e | ||
|
fdd3a22807 | ||
|
cf69f1394a | ||
|
2d9a43b076 | ||
|
8958dca939 | ||
|
47508e7969 | ||
|
4ace00736b | ||
|
0b8553d0c2 | ||
|
4ab95055d6 | ||
|
cbc3f7b65a | ||
|
344025ae2f | ||
|
eeae9e3ad7 | ||
|
8289aeaf41 | ||
|
53d012c9d0 | ||
|
239bdcf97d | ||
|
4d271353c4 | ||
|
5d4cfab00a | ||
|
831739d215 | ||
|
ad1a658806 | ||
|
6002e58425 | ||
|
0ab374e9ea | ||
|
3f89d97a5c | ||
|
b91e6e77b5 | ||
|
151b068d97 | ||
|
79404fdc71 | ||
|
09b16599d9 | ||
|
cc43adedcf | ||
|
4559f74881 | ||
|
8bb786c763 | ||
|
e784476344 | ||
|
0b442f6a2c | ||
|
5a3ef41277 | ||
|
01c894da9d | ||
|
bc19a524c4 | ||
|
86beb532e2 | ||
|
f142bee72b | ||
|
70455e5eeb | ||
|
f55431f8b8 | ||
|
6f38eba80e | ||
|
9e9207f748 | ||
|
08c55cc71c | ||
|
f0ae82c0db | ||
|
385a11d6a7 | ||
|
1f9669ad7c | ||
|
01f956b6ce | ||
|
2ea14635d2 | ||
|
771f45baab | ||
|
b82c2f3fc6 | ||
|
9fc87803e5 | ||
|
780b165a8b | ||
|
f52a55a387 | ||
|
ce8f597501 | ||
|
8dfb040b5f | ||
|
200675464e | ||
|
e78e3c5102 | ||
|
fe6d99c5b1 | ||
|
d128918bdb | ||
|
cdd80f207b | ||
|
b2198985c8 | ||
|
e24f01a2b5 | ||
|
42329328ec | ||
|
203582f801 | ||
|
d09770913b | ||
|
be67972760 | ||
|
97145efad9 | ||
|
f6026293a5 | ||
|
75579eae4f | ||
|
e8500b982b | ||
|
b958b8fb4c | ||
|
e3b51d0752 | ||
|
99d45ee048 | ||
|
6c6eb731f9 | ||
|
c26fc9235d | ||
|
05f6bb6091 | ||
|
eb825bca04 | ||
|
2dda00e36c | ||
|
441264f34c | ||
|
6d6a41d3e5 | ||
|
52112996c5 | ||
|
6af5b8c24d | ||
|
01f56d9ef7 | ||
|
0e68ea263c | ||
|
fcb6554ebb | ||
|
10c38400e4 | ||
|
6ba8434f40 | ||
|
578629f8bd | ||
|
56e75895bd | ||
|
7b8a56dd8f | ||
|
5064afd766 | ||
|
5809cffa71 | ||
|
73145a9af6 | ||
|
3ed610969e | ||
|
4d29c83365 | ||
|
143879caf9 | ||
|
a22ae40d8e | ||
|
41fdb8abb0 | ||
|
72637fe80c | ||
|
53138ea1c7 | ||
|
7c07cd79bc | ||
|
85dbb6f392 | ||
|
51cb72d142 | ||
|
5c0672884a | ||
|
cbc78f9532 | ||
|
5c2685c785 | ||
|
143e6c2417 | ||
|
48de6218eb | ||
|
6d4a6806f4 | ||
|
56df3d82a0 | ||
|
7218864563 | ||
|
2385601e12 | ||
|
0ede2093c5 | ||
|
22cf00fd98 | ||
|
7247441693 | ||
|
4c97246e3f | ||
|
1511b5f22b | ||
|
af332283ed | ||
|
c953d66c95 | ||
|
5fec0c187d | ||
|
8d41a94b94 | ||
|
228299c5b7 |
339 changed files with 15362 additions and 13588 deletions
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
|
|
@ -13,6 +13,9 @@ indent_size = 2
|
|||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[*.toml]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
indent_size = 2
|
||||
max_line_length = off
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,5 +1,9 @@
|
|||
.env.local
|
||||
config.yaml
|
||||
/secret/*
|
||||
/files/*
|
||||
!/files/.gitkeep
|
||||
/build/*
|
||||
!/build/.gitkeep
|
||||
/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
|
349
CHANGELOG.md
349
CHANGELOG.md
|
@ -6,6 +6,355 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.22.0] - 2023-04-22
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for content warnings.
|
||||
- Support integrity proofs with `DataIntegrityProof` type.
|
||||
- Add `federation.i2p_proxy_url` configuration parameter.
|
||||
|
||||
### Changed
|
||||
|
||||
- Ignore errors when importing activities from outbox.
|
||||
- Make activity limit in outbox fetcher adjustable.
|
||||
- Updated actix to latest version. MSRV changed to 1.57.
|
||||
- Add replies and reposts to outbox collection.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Make `/api/v1/accounts/{account_id}/follow` work with form-data.
|
||||
- Make `onion_proxy_url` override `proxy_url` setting if request target is onion.
|
||||
|
||||
## [1.21.0] - 2023-04-12
|
||||
|
||||
### Added
|
||||
|
||||
- Added `create-user` command.
|
||||
- Added `read-outbox` command.
|
||||
|
||||
### Changed
|
||||
|
||||
- Added emoji count check to profile data validator.
|
||||
- Check mention and link counts when creating post.
|
||||
- Re-fetch object if `attributedTo` value doesn't match `actor` of `Create` activity.
|
||||
- Added actor validation to `Update(Note)` and `Undo(Follow)` handlers.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed database query error in `Create` activity handler.
|
||||
|
||||
## [1.20.0] - 2023-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Support calling `/api/v1/accounts/search` with `resolve` parameter.
|
||||
- Created `/api/v1/accounts/aliases/all` API endpoint.
|
||||
- Created API endpoint for adding aliases.
|
||||
- Populate `alsoKnownAs` property on actor object with declared aliases.
|
||||
- Support account migration from Mastodon.
|
||||
- Created API endpoint for managing client configurations.
|
||||
- Reject unsolicited public posts.
|
||||
|
||||
### Changed
|
||||
|
||||
- Increase maximum number of custom emojis per post to 50.
|
||||
- Validate actor aliases before saving into database.
|
||||
- Process incoming `Move()` activities in background.
|
||||
- Allow custom emojis with `image/webp` media type.
|
||||
- Increase object ID size limit to 2000 chars.
|
||||
- Increase fetcher timeout to 15 seconds when processing search queries.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added missing `CHECK` constraints to database tables.
|
||||
- Validate object ID length before saving post to database.
|
||||
- Validate emoji name length before saving to database.
|
||||
|
||||
## [1.19.1] - 2023-03-31
|
||||
|
||||
### Changed
|
||||
|
||||
- Limit number of mentions and links in remote posts.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Process queued background jobs before re-trying stalled.
|
||||
- Remove activity from queue if handler times out.
|
||||
- Order attachments by creation date when new post is created.
|
||||
|
||||
## [1.19.0] - 2023-03-30
|
||||
|
||||
### Added
|
||||
|
||||
- Added `prune-remote-emojis` command.
|
||||
- Prune remote emojis in background.
|
||||
- Added `limits.media.emoji_size_limit` configuration parameter.
|
||||
- Added `federation.fetcher_timeout` and `federation.deliverer_timeout` configuration parameters.
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow emoji names containing hyphens.
|
||||
- Increased remote emoji size limit to 500 kB.
|
||||
- Set fetcher timeout to 5 seconds when processing search queries.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed error in emoji update SQL query.
|
||||
- Restart stalled background jobs.
|
||||
- Order attachments by creation date.
|
||||
- Don't reopen monero wallet on each subscription monitor run.
|
||||
|
||||
### Security
|
||||
|
||||
- Updated markdown parser to latest version.
|
||||
|
||||
## [1.18.0] - 2023-03-21
|
||||
|
||||
### Added
|
||||
|
||||
- Added `fep-e232` feature flag (disabled by default).
|
||||
- Added `account_index` parameter to Monero configuration.
|
||||
- Added `/api/v1/instance/peers` API endpoint.
|
||||
- Added `federation.enabled` configuration parameter that can be used to disable federation.
|
||||
|
||||
### Changed
|
||||
|
||||
- Documented valid role names for `set-role` command.
|
||||
- Granted `delete_any_post` and `delete_any_profile` permissions to admin role.
|
||||
- Updated profile page URL template to match mitra-web.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Make webclient-to-object redirects work for remote profiles and posts.
|
||||
- Added webclient redirection rule for `/@username` routes.
|
||||
- Don't allow migration if user doesn't have identity proofs.
|
||||
|
||||
## [1.17.0] - 2023-03-15
|
||||
|
||||
### Added
|
||||
|
||||
- Enabled audio and video uploads.
|
||||
- Added `audio/ogg` and `audio/x-wav` to the list of supported media types.
|
||||
|
||||
### Changed
|
||||
|
||||
- Save latest ethereum block number to database instead of file.
|
||||
- Removed hardcoded upload size limit.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Reading ethereum block number from `current_block` file.
|
||||
|
||||
### Removed
|
||||
|
||||
- Disabled post tokenization (can be re-enabled with `ethereum-extras` feature).
|
||||
- Removed ability to switch from Ethereum devnet to another chain without resetting subscriptions.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Allow `!` after hashtags and mentions.
|
||||
- Ignore emojis with non-unique names in remote posts.
|
||||
|
||||
## [1.16.0] - 2023-03-08
|
||||
|
||||
### Added
|
||||
|
||||
- Allow to add notes to generated invite codes.
|
||||
- Added `registration.default_role` configuration option.
|
||||
- Save emojis attached to actor objects.
|
||||
- Added `emojis` field to Mastodon API Account entity.
|
||||
- Support audio attachments.
|
||||
- Added CLI command for viewing unreachable actors.
|
||||
- Implemented NodeInfo 2.1.
|
||||
- Added `federation.onion_proxy_url` configuration parameter (enables proxy for requests to `.onion` domains).
|
||||
|
||||
### Changed
|
||||
|
||||
- Use .jpg extension for files with image/jpeg media type.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `default_role_read_only_user` configuration option (replaced by `registration.default_role`).
|
||||
|
||||
## [1.15.0] - 2023-02-27
|
||||
|
||||
### Added
|
||||
|
||||
- Set fetcher timeout to 3 minutes.
|
||||
- Set deliverer timeout to 30 seconds.
|
||||
- Added `federation` parameter group to configuration.
|
||||
- Add empty `spoiler_text` property to Mastodon API Status object.
|
||||
- Added `error` and `error_description` fields to Mastodon API error responses.
|
||||
- Store information about failed activity deliveries in database.
|
||||
- Added `/api/v1/accounts/{account_id}/aliases` API endpoint.
|
||||
|
||||
### Changed
|
||||
|
||||
- Put activities generated by CLI commands in a queue instead of immediately sending them.
|
||||
- Changed path of user's Atom feed to `/feeds/users/{username}`.
|
||||
- Increase number of delivery attempts and increase intervals between them.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `proxy_url` configuration parameter (replaced by `federation.proxy_url`).
|
||||
- Deprecated Atom feeds at `/feeds/{username}`.
|
||||
- Deprecated `message` field in Mastodon API error response.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent `delete-extraneous-posts` command from removing locally-linked posts.
|
||||
- Make webfinger response compatible with GNU Social account lookup.
|
||||
- Prefer `Group` actor when doing webfinger query on Lemmy server.
|
||||
- Fetch missing profiles before doing follower migration.
|
||||
- Follow FEP-e232 links when importing post.
|
||||
|
||||
## [1.14.0] - 2023-02-22
|
||||
|
||||
### Added
|
||||
|
||||
- Added `/api/v1/apps` endpoint.
|
||||
- Added OAuth authorization page.
|
||||
- Support `authorization_code` OAuth grant type.
|
||||
- Documented `http_cors_allowlist` configuration parameter.
|
||||
- Added `/api/v1/statuses/{status_id}/thread` API endpoint (replaces `/api/v1/statuses/{status_id}/context`).
|
||||
- Accept webfinger requests where `resource` is instance actor ID.
|
||||
- Added `proxy_set_header X-Forwarded-Proto $scheme;` directive to nginx config example.
|
||||
- Add `Content-Security-Policy` and `X-Content-Type-Options` headers to all responses.
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow `instance_uri` configuration value to contain URI scheme.
|
||||
- Changed `/api/v1/statuses/{status_id}/context` response format to match Mastodon API.
|
||||
- Changed status code of `/api/v1/statuses` response to 200 to match Mastodon API.
|
||||
- Removed `add_header` directives for `Content-Security-Policy` and `X-Content-Type-Options` headers from nginx config example.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated protocol guessing on incoming requests (use `X-Forwarded-Proto` header).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed actor object JSON-LD validation errors.
|
||||
- Fixed activity JSON-LD validation errors.
|
||||
- Make media URLs in Mastodon API responses relative to current origin.
|
||||
|
||||
## [1.13.1] - 2023-02-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed permission error on subscription settings update.
|
||||
|
||||
## [1.13.0] - 2023-02-06
|
||||
|
||||
### Added
|
||||
|
||||
- Replace post attachments and other related objects when processing `Update(Note)` activity.
|
||||
- Append attachment URL to post content if attachment size exceeds limit.
|
||||
- Added `/api/v1/custom_emojis` endpoint.
|
||||
- Added `limits` parameter group to configuration.
|
||||
- Made file size limit adjustable with `limits.media.file_size_limit` configuration option.
|
||||
- Added `limits.posts.character_limit` configuration parameter (replaces `post_character_limit`).
|
||||
- Implemented automatic pruning of remote posts and empty profiles (disabled by default).
|
||||
|
||||
### Changed
|
||||
|
||||
- Use proof suites with prefix `Mitra`.
|
||||
- Added `https://w3id.org/security/data-integrity/v1` to JSON-LD context.
|
||||
- Return `202 Accepted` when activity is accepted by inbox endpoint.
|
||||
- Ignore forwarded `Like` activities.
|
||||
- Set 10 minute timeout on background job that processes incoming activities.
|
||||
- Use "warn" log level for delivery errors.
|
||||
- Don't allow read-only users to manage subscriptions.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `post_character_limit` configuration option.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Change max body size in nginx example config to match app limit.
|
||||
- Don't create invoice if recipient can't accept subscription payments.
|
||||
- Ignore `Announce(Delete)` activities.
|
||||
|
||||
## [1.12.0] - 2023-01-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added `approval_required` and `invites_enabled` flags to `/api/v1/instance` endpoint response.
|
||||
- Added `registration.type` configuration option (replaces `registrations_open`).
|
||||
- Implemented roles & permissions.
|
||||
- Added "read-only user" role.
|
||||
- Added configuration option for automatic assigning of "read-only user" role after registration.
|
||||
- Added `set-role` command.
|
||||
|
||||
### Changed
|
||||
|
||||
- Don't retry activity if fetcher recursion limit has been reached.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `registrations_open` configuration option.
|
||||
|
||||
### Removed
|
||||
|
||||
- Dropped support for `blockchain` configuration parameter.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added missing `<link rel="self">` element to Atom feeds.
|
||||
- Added missing `<link rel="alternate">` element to Atom feed entries.
|
||||
|
||||
## [1.11.0] - 2023-01-23
|
||||
|
||||
### Added
|
||||
|
||||
- Save sizes of media attachments and other files to database.
|
||||
- Added `import-emoji` command.
|
||||
- Added support for emoji shortcodes.
|
||||
- Allowed custom emojis with `image/apng` media type.
|
||||
|
||||
### Changed
|
||||
|
||||
- Make `delete-emoji` command accept emoji name and hostname instead of ID.
|
||||
- Replaced client-side tag URLs with collection IDs.
|
||||
|
||||
### Security
|
||||
|
||||
- Validate emoji name before saving.
|
||||
|
||||
## [1.10.0] - 2023-01-18
|
||||
|
||||
### Added
|
||||
|
||||
- Added `/api/v1/settings/move_followers` API endpoint (replaces `/api/v1/accounts/move_followers`).
|
||||
- Added `/api/v1/settings/import_follows` API endpoint.
|
||||
- Validation of Monero subscription payout address.
|
||||
- Accept webfinger requests where `resource` is actor ID.
|
||||
- Adeed support for `as:Public` and `Public` audience identifiers.
|
||||
- Displaying custom emojis.
|
||||
|
||||
### Changed
|
||||
|
||||
- Save downloaded media as "unknown" if its media type is not supported.
|
||||
- Use `mediaType` property value to determine file extension when saving downloaded media.
|
||||
- Added `mediaType` property to images in actor object.
|
||||
- Prevent `delete-extraneous-posts` command from deleting post if there's a recent reply or repost.
|
||||
- Changed max actor image size to 5 MB.
|
||||
|
||||
### Removed
|
||||
|
||||
- `/api/v1/accounts/move_followers` API endpoint.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't ignore `Delete(Person)` verification errors if database error subtype is not `NotFound`.
|
||||
- Don't stop activity processing on invalid local mentions.
|
||||
- Accept actor objects where `attachment` property value is not an array.
|
||||
- Don't download HTML pages attached by GNU Social.
|
||||
- Ignore `Like()` activity if local post doesn't exist.
|
||||
- Fixed `.well-known` paths returning `400 Bad Request` errors.
|
||||
|
||||
## [1.9.0] - 2023-01-08
|
||||
|
||||
### Added
|
||||
|
|
2312
Cargo.lock
generated
2312
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
91
Cargo.toml
91
Cargo.toml
|
@ -1,77 +1,66 @@
|
|||
[package]
|
||||
name = "mitra"
|
||||
version = "1.9.0"
|
||||
description = "Federated micro-blogging platform and content subscription service"
|
||||
name = "fedimovies"
|
||||
version = "1.22.0"
|
||||
description = "Movies reviews and ratings for the fediverse"
|
||||
license = "AGPL-3.0"
|
||||
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.68"
|
||||
publish = false
|
||||
default-run = "mitra"
|
||||
default-run = "fedimovies"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"fedimovies-cli",
|
||||
"fedimovies-config",
|
||||
"fedimovies-models",
|
||||
"fedimovies-utils",
|
||||
]
|
||||
default-members = [
|
||||
".",
|
||||
"fedimovies-cli",
|
||||
"fedimovies-config",
|
||||
"fedimovies-models",
|
||||
"fedimovies-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
fedimovies-config = { path = "fedimovies-config" }
|
||||
fedimovies-models = { path = "fedimovies-models" }
|
||||
fedimovies-utils = { path = "fedimovies-utils" }
|
||||
|
||||
# Used to handle incoming HTTP requests
|
||||
actix-cors = "0.6.2"
|
||||
actix-cors = "0.6.4"
|
||||
actix-files = "0.6.2"
|
||||
actix-web = "4.1.0"
|
||||
actix-web = "4.3.1"
|
||||
actix-web-httpauth = "0.8.0"
|
||||
# Used for HTML sanitization
|
||||
ammonia = "3.2.0"
|
||||
# Used for catching errors
|
||||
anyhow = "1.0.58"
|
||||
# Used for working with RSA keys, HTTP signatures and file uploads
|
||||
base64 = "0.13.0"
|
||||
# Used to decode base58btc
|
||||
bs58 = "0.4.0"
|
||||
# Used for working with dates
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["std", "serde"] }
|
||||
# Used to build admin CLI tool
|
||||
clap = { version = "3.2.18", default-features = false, features = ["std", "derive"] }
|
||||
# Used for parsing markdown
|
||||
comrak = { version = "0.15.0", default-features = false }
|
||||
# Used for pooling database connections
|
||||
deadpool = "0.9.2"
|
||||
deadpool-postgres = { version = "0.10.2", default-features = false }
|
||||
# Used to read .env files
|
||||
dotenv = "0.15.0"
|
||||
# Used to work with hexadecimal strings
|
||||
hex = { version = "0.4.3", features = ["serde"] }
|
||||
# Used for logging
|
||||
log = { version = "0.4.14", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
env_logger = { version = "0.9.0", default-features = false }
|
||||
# Used to verify minisign signatures
|
||||
ed25519-dalek = "1.0.1"
|
||||
ed25519 = "1.5.2"
|
||||
ed25519 = "1.5.3"
|
||||
blake2 = "0.10.5"
|
||||
# Used to guess media type of a file
|
||||
mime_guess = "2.0.3"
|
||||
mime-sniffer = "0.1.2"
|
||||
# Used to query Monero node
|
||||
monero-rpc = "0.3.2"
|
||||
# Used to determine the number of CPUs on the system
|
||||
num_cpus = "1.13.0"
|
||||
# Used for working with regular expressions
|
||||
regex = "1.6.0"
|
||||
# Used to generate random numbers
|
||||
rand = "0.8.4"
|
||||
# Used for managing database migrations
|
||||
refinery = { version = "0.8.4", features = ["tokio-postgres"] }
|
||||
# Used for making async HTTP requests
|
||||
reqwest = { version = "0.11.10", features = ["json", "multipart", "socks"] }
|
||||
reqwest = { version = "0.11.13", features = ["json", "multipart", "socks"] }
|
||||
# Used for working with RSA keys
|
||||
rsa = "0.5.0"
|
||||
pem = "1.0.2"
|
||||
# Used for hashing passwords
|
||||
rust-argon2 = "1.0.0"
|
||||
# Used for working with ethereum keys
|
||||
secp256k1 = { version = "0.21.3", features = ["rand", "rand-std"] }
|
||||
# Used for serialization/deserialization
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.89"
|
||||
# Used to create JCS representations
|
||||
serde_jcs = "0.1.0"
|
||||
# Used to parse config file
|
||||
serde_yaml = "0.8.17"
|
||||
# Used to calculate SHA2 hashes
|
||||
sha2 = "0.9.5"
|
||||
# Used to verify EIP-4361 signatures
|
||||
|
@ -79,26 +68,18 @@ siwe = "0.4.0"
|
|||
# Used for creating error types
|
||||
thiserror = "1.0.37"
|
||||
# Async runtime
|
||||
tokio = { version = "1.17.0", features = ["macros"] }
|
||||
# Used for working with Postgresql database
|
||||
tokio-postgres = { version = "0.7.6", features = ["with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
|
||||
postgres-types = { version = "0.2.3", features = ["derive", "with-chrono-0_4", "with-uuid-1", "with-serde_json-1"] }
|
||||
postgres-protocol = "0.6.4"
|
||||
# Used to construct PostgreSQL queries
|
||||
postgres_query = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
|
||||
postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query", rev = "b4422051c8a31fbba4a35f88004c1cefb1878dd5" }
|
||||
tokio = { version = "=1.20.4", features = ["macros"] }
|
||||
# Used to work with URLs
|
||||
url = "2.2.2"
|
||||
# Used to generate lexicographically sortable IDs
|
||||
ulid = { version = "1.0.0", features = ["uuid"] }
|
||||
# Used to work with UUIDs
|
||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||
# Used to query ethereum node
|
||||
web3 = { version = "0.18.0", default-features = false, features = ["http", "http-tls", "signing"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
fedimovies-config = { path = "fedimovies-config", features = ["test-utils"] }
|
||||
fedimovies-models = { path = "fedimovies-models", features = ["test-utils"] }
|
||||
fedimovies-utils = { path = "fedimovies-utils", features = ["test-utils"] }
|
||||
|
||||
serial_test = "0.7.0"
|
||||
|
||||
[features]
|
||||
production = []
|
||||
production = ["fedimovies-config/production"]
|
||||
|
|
|
@ -29,12 +29,67 @@ And these additional standards:
|
|||
|
||||
Activities are implemented in way that is compatible with Pleroma, Mastodon and other popular ActivityPub servers.
|
||||
|
||||
## Supported FEPs
|
||||
Supported FEPs:
|
||||
|
||||
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-f1d5.md)
|
||||
- [FEP-e232: Object Links](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-e232.md)
|
||||
- [FEP-8b32: Object Integrity Proofs](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8b32.md)
|
||||
|
||||
## Object integrity proofs
|
||||
|
||||
All outgoing activities are signed with actor's key in accordance with [FEP-8b32](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8b32.md) document.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/data-integrity/v1"
|
||||
],
|
||||
"actor": "https://example.com/users/alice",
|
||||
"cc": [],
|
||||
"id": "https://example.com/objects/0185f5f8-10b5-1b69-f45e-25f06792f411",
|
||||
"object": "https://example.net/users/bob/posts/141892712081205472",
|
||||
"proof": {
|
||||
"created": "2023-01-28T01:22:40.183273595Z",
|
||||
"proofPurpose": "assertionMethod",
|
||||
"proofValue": "z5djAdMSrV...",
|
||||
"type": "MitraJcsRsaSignature2022",
|
||||
"verificationMethod": "https://example.com/users/alice#main-key"
|
||||
},
|
||||
"to": [
|
||||
"https://example.net/users/bob",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type":"Like"
|
||||
}
|
||||
```
|
||||
|
||||
### Supported proof suites
|
||||
|
||||
#### MitraJcsRsaSignature2022
|
||||
|
||||
Canonicalization algorithm: JCS
|
||||
Hashing algorithm: SHA-256
|
||||
Signature algorithm: RSASSA-PKCS1-v1_5
|
||||
|
||||
#### MitraJcsEip191Signature2022
|
||||
|
||||
Canonicalization algorithm: JCS
|
||||
Hashing algorithm: KECCAK-256 (EIP-191)
|
||||
Signature algorithm: ECDSA (EIP-191)
|
||||
|
||||
#### MitraJcsEd25519Signature2022
|
||||
|
||||
Canonicalization algorithm: JCS
|
||||
Hashing algorithm: BLAKE2b-512
|
||||
Signature algorithm: EdDSA
|
||||
|
||||
## Custom emojis
|
||||
|
||||
Custom emojis are implemented as described in Mastodon documentation: https://docs.joinmastodon.org/spec/activitypub/#emoji.
|
||||
|
||||
## Profile extensions
|
||||
|
||||
### Cryptocurrency addresses
|
||||
|
|
119
README.md
119
README.md
|
@ -1,46 +1,39 @@
|
|||
# Mitra
|
||||
# FediMovies
|
||||
[](https://ci.caric.io/FediMovies/fedimovies)
|
||||
|
||||
Federated micro-blogging platform and content subscription service.
|
||||
Lively federated movies reviews platform.
|
||||
|
||||
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
|
||||
Subscriptions provide a way to receive monthly payments from subscribers and to publish private content made exclusively for them.
|
||||
Features:
|
||||
|
||||
Supported payment methods:
|
||||
- Micro-blogging service (includes support for quote posts, custom emojis and more).
|
||||
- Mastodon API.
|
||||
- Account migrations (from one server to another). Identity can be detached from the server.
|
||||
- Federation over Tor.
|
||||
|
||||
- [Monero](https://www.getmonero.org/get-started/what-is-monero/).
|
||||
- [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens (on Ethereum and other EVM-compatible blockchains).
|
||||
## Instances
|
||||
|
||||
Other features:
|
||||
- [FediList](http://demo.fedilist.com/instance?software=fedimovies)
|
||||
- [Fediverse Observer](https://fedimovies.fediverse.observer/list)
|
||||
|
||||
- [Sign-in with a wallet](https://eips.ethereum.org/EIPS/eip-4361).
|
||||
- Account migrations (from one server to another).
|
||||
- Donation buttons.
|
||||
- Token-gated registration (can be used to verify membership in some group or to stop bots).
|
||||
- Converting posts into NFTs.
|
||||
- Saving posts to IPFS.
|
||||
|
||||
Demo instance: https://public.mitra.social/ ([invite-only](https://public.mitra.social/about))
|
||||
|
||||
Network stats: https://the-federation.info/mitra
|
||||
|
||||
Matrix chat: [#mitra:halogen.city](https://matrix.to/#/#mitra:halogen.city)
|
||||
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
|
||||
|
||||
## Code
|
||||
|
||||
Server: https://codeberg.org/silverpill/mitra (this repo)
|
||||
Server: https://code.caric.io/reef/reef (this repo)
|
||||
|
||||
Web client: https://codeberg.org/silverpill/mitra-web
|
||||
Web client:
|
||||
|
||||
Ethereum contracts: https://codeberg.org/silverpill/mitra-contracts
|
||||
|
||||
## Requirements
|
||||
|
||||
- Rust 1.56+ (when building from source)
|
||||
- Rust 1.57+ (when building from source)
|
||||
- PostgreSQL 12+
|
||||
- IPFS node (optional, see [guide](./docs/ipfs.md))
|
||||
- Ethereum node (optional)
|
||||
- Monero node and Monero wallet (optional)
|
||||
|
||||
Optional:
|
||||
|
||||
- IPFS node (see [guide](./docs/ipfs.md))
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -52,71 +45,57 @@ Run:
|
|||
cargo build --release --features production
|
||||
```
|
||||
|
||||
This command will produce two binaries in `target/release` directory, `mitra` and `mitractl`.
|
||||
This command will produce two binaries in `target/release` directory, `fedimovies` and `fedimoviesctl`.
|
||||
|
||||
Install PostgreSQL and create the database:
|
||||
|
||||
```sql
|
||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
||||
CREATE DATABASE mitra OWNER mitra;
|
||||
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||
```
|
||||
|
||||
Create configuration file by copying `contrib/mitra_config.yaml` and configure the instance. Default config file path is `/etc/mitra/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
|
||||
Create configuration file by copying `contrib/fedimovies_config.yaml` and configure the instance. Default config file path is `/etc/fedimovies/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
|
||||
|
||||
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
|
||||
./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
|
||||
|
||||
Download and install Mitra package:
|
||||
Download and install Fedimovies package:
|
||||
|
||||
```shell
|
||||
dpkg -i mitra.deb
|
||||
dpkg -i fedimovies.deb
|
||||
```
|
||||
|
||||
Install PostgreSQL and create the database:
|
||||
|
||||
```sql
|
||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
||||
CREATE DATABASE mitra OWNER mitra;
|
||||
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||
```
|
||||
|
||||
Open configuration file `/etc/mitra/config.yaml` and configure the instance.
|
||||
Open configuration file `/etc/fedimovies/config.yaml` and configure the instance.
|
||||
|
||||
Start Mitra:
|
||||
Start Fedimovies:
|
||||
|
||||
```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).
|
||||
|
||||
### Monero
|
||||
### Tor federation
|
||||
|
||||
Install Monero node or choose a [public one](https://monero.fail/).
|
||||
|
||||
Configure and start [monero-wallet-rpc](https://monerodocs.org/interacting/monero-wallet-rpc-reference/) daemon. 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.
|
||||
See [guide](./docs/onion.md).
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -131,15 +110,7 @@ docker-compose up -d
|
|||
Test connection:
|
||||
|
||||
```shell
|
||||
psql -h localhost -p 55432 -U mitra mitra
|
||||
```
|
||||
|
||||
### Start Monero node and wallet server
|
||||
|
||||
(this step is optional)
|
||||
|
||||
```shell
|
||||
docker-compose --profile monero up -d
|
||||
psql -h localhost -p 55432 -U fedimovies fedimovies
|
||||
```
|
||||
|
||||
### Run web service
|
||||
|
@ -159,7 +130,7 @@ cargo run
|
|||
### Run CLI
|
||||
|
||||
```shell
|
||||
cargo run --bin mitractl
|
||||
cargo run --bin fedimoviesctl
|
||||
```
|
||||
|
||||
### Run linter
|
||||
|
@ -180,20 +151,16 @@ See [FEDERATION.md](./FEDERATION.md)
|
|||
|
||||
## 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)
|
||||
|
||||
## 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
|
||||
|
||||
[AGPL-3.0](./LICENSE)
|
||||
|
||||
## Support
|
||||
|
||||
Monero: 8Ahza5RM4JQgtdqvpcF1U628NN5Q87eryXQad3Fy581YWTZU8o3EMbtScuioQZSkyNNEEE1Lkj2cSbG4VnVYCW5L1N4os5p
|
||||
|
|
0
build/.gitkeep
Normal file
0
build/.gitkeep
Normal file
|
@ -5,34 +5,14 @@ web_client_dir: null
|
|||
http_host: '127.0.0.1'
|
||||
http_port: 8380
|
||||
|
||||
instance_uri: 127.0.0.1:8380
|
||||
instance_uri: http://127.0.0.1:8380
|
||||
|
||||
instance_title: Mitra
|
||||
instance_short_description: My instance
|
||||
instance_description: My instance
|
||||
registrations_open: true
|
||||
|
||||
blockchains:
|
||||
# Parameters for hardhat local node
|
||||
- chain_id: eip155:31337
|
||||
chain_metadata:
|
||||
chain_name: localhost
|
||||
currency_name: ETH
|
||||
currency_symbol: ETH
|
||||
currency_decimals: 18
|
||||
public_api_url: 'http://127.0.0.1:8546'
|
||||
explorer_url: null
|
||||
contract_address: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
|
||||
contract_dir: contracts
|
||||
api_url: 'http://127.0.0.1:8546'
|
||||
signing_key: 'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
|
||||
chain_sync_step: 100
|
||||
chain_reorg_max_depth: 0
|
||||
# # Parameters for local Monero node
|
||||
# - chain_id: monero:regtest
|
||||
# node_url: 'http://127.0.0.1:58081'
|
||||
# wallet_url: 'http://127.0.0.1:58083'
|
||||
# wallet_name: test
|
||||
# wallet_password: test
|
||||
registration:
|
||||
type: open
|
||||
|
||||
ipfs_api_url: 'http://127.0.0.1:5001'
|
||||
ipfs_gateway_url: 'http://127.0.0.1:8001'
|
||||
|
|
|
@ -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"]
|
54
contrib/mitra-alt-fe.nginx
Normal file
54
contrib/mitra-alt-fe.nginx
Normal file
|
@ -0,0 +1,54 @@
|
|||
server {
|
||||
server_name example.tld;
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
location / {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
server_name example.tld;
|
||||
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
|
||||
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
|
||||
ssl_session_tickets off;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
|
||||
# CSP header can't be added in location block
|
||||
add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; img-src 'self' data:; media-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'nonce-oauth-authorization'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
client_max_body_size 40M;
|
||||
|
||||
location / {
|
||||
# Frontend
|
||||
root /usr/share/mitra/www;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
location ~ ^/(actor|api|collections|contracts|feeds|media|nodeinfo|oauth|objects|users|.well-known) {
|
||||
# Backend
|
||||
proxy_pass http://127.0.0.1:8383;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
|
@ -31,13 +31,13 @@ server {
|
|||
ssl_stapling_verify on;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
# script-src unsafe-inline required by MetaMask
|
||||
add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; img-src 'self' data:; media-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
client_max_body_size 40M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8383;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,54 +8,54 @@ web_client_dir: /usr/share/mitra/www
|
|||
http_host: '127.0.0.1'
|
||||
http_port: 8383
|
||||
|
||||
# List of allowed origins for CORS (in addition to main)
|
||||
#http_cors_allowlist: []
|
||||
|
||||
# Log level (debug, info, warn)
|
||||
#log_level: info
|
||||
|
||||
# Domain name
|
||||
instance_uri: example.tld
|
||||
instance_uri: https://example.tld
|
||||
|
||||
instance_title: example
|
||||
instance_short_description: my instance
|
||||
# Long description can contain markdown syntax
|
||||
instance_description: my instance
|
||||
instance_description: |
|
||||
# My instance
|
||||
Welcome!
|
||||
|
||||
registrations_open: false
|
||||
registration:
|
||||
# Possible values: open, invite
|
||||
type: invite
|
||||
# Possible values: user, read_only_user
|
||||
default_role: user
|
||||
|
||||
# EIP-4361 login message
|
||||
#login_message: 'Do not sign this message on other sites!'
|
||||
|
||||
#post_character_limit: 2000
|
||||
# Limits
|
||||
#limits:
|
||||
# media:
|
||||
# file_size_limit: 20M
|
||||
# posts:
|
||||
# character_limit: 2000
|
||||
|
||||
# Proxy for outgoing requests
|
||||
#proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
# Data retention parameters
|
||||
retention:
|
||||
extraneous_posts: 50
|
||||
empty_profiles: 150
|
||||
|
||||
# Federation parameters
|
||||
#federation:
|
||||
# enabled: true
|
||||
# # Proxy for outgoing requests
|
||||
# #proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
# # Proxy for outgoing requests to .onion targets
|
||||
# #onion_proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
|
||||
# List of blocked domains
|
||||
#blocked_instances: []
|
||||
|
||||
# Blockchain integrations
|
||||
# Multiple configuration are currently not allowed.
|
||||
# Chain metadata for EVM chains can be found at https://github.com/ethereum-lists/chains
|
||||
# Signing key for ethereum integration can be generated with `mitractl generate-ethereum-address`
|
||||
#blockchains:
|
||||
# - chain_id: monero:mainnet
|
||||
# node_url: 'http://opennode.xmr-tw.org:18089'
|
||||
# wallet_url: 'http://127.0.0.1:18083'
|
||||
# wallet_name: null
|
||||
# wallet_password: null
|
||||
# - chain_id: eip155:31337
|
||||
# chain_metadata:
|
||||
# chain_name: localhost
|
||||
# currency_name: ETH
|
||||
# currency_symbol: ETH
|
||||
# currency_decimals: 18
|
||||
# public_api_url: 'http://127.0.0.1:8545'
|
||||
# explorer_url: null
|
||||
# contract_address: '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9'
|
||||
# contract_dir: /usr/share/mitra/contracts
|
||||
# api_url: 'http://127.0.0.1:8545'
|
||||
# signing_key: null
|
||||
# chain_sync_step: 1000
|
||||
# chain_reorg_max_depth: 10
|
||||
|
||||
# IPFS integration
|
||||
#ipfs_api_url: 'http://127.0.0.1:5001'
|
||||
# IPFS gateway (for clients)
|
||||
|
|
16
docs/ipfs.md
16
docs/ipfs.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
This guide explains how to run IPFS node in resource-constrained environment (such as cheap VPS or single-board computer).
|
||||
|
||||
The recommended IPFS implementation is [go-ipfs](https://github.com/ipfs/go-ipfs), version 0.12 or higher. Normally go-ipfs requires at least 2 GB RAM, but after tweaking it can run on a machine with only 512 MB.
|
||||
The recommended IPFS implementation is [kubo](https://github.com/ipfs/kubo), version 0.18.1 or higher. Normally **kubo** requires at least 2 GB RAM, but after tweaking it can run on a machine with only 512 MB.
|
||||
|
||||
## Configuration profiles
|
||||
|
||||
|
@ -14,24 +14,26 @@ ipfs init --profile server
|
|||
|
||||
If you're running it on single-board computer, the recommended profile is `lowpower`.
|
||||
|
||||
Documentation on configuration profiles: https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#profiles.
|
||||
Documentation on configuration profiles: https://github.com/ipfs/kubo/blob/master/docs/config.md#profiles
|
||||
|
||||
## Configuration options
|
||||
|
||||
- `Datastore.StorageMax`. Recommended value is `1G`.
|
||||
- `Gateway.NoFetch`. Configures gateway to not fetch files from the network. Recommended value is `true`.
|
||||
- `RelayService.Enabled`. Enables providing p2p relay service to other peers on the network. Recommended value is `false`.
|
||||
- `Routing.Type`. Should be set to `dht` otherwise the node will not respond to requests from other peers.
|
||||
- `Swarm.ConnMgr.LowWater`. Recommended value is `10`.
|
||||
- `Swarm.ConnMgr.HighWater`. Recommended value is `20`.
|
||||
- `Swarm.ConnMgr.GracePeriod`. Recommended value is `15s`.
|
||||
- `Swarm.DisableBandwidthMetrics`. Disabling bandwidth metrics can slightly improve performance. Recommended value is `true`.
|
||||
- `Swarm.RelayService.Enabled`. Enables providing p2p relay service to other peers on the network. Recommended value is `false`.
|
||||
- `Swarm.ResourceMgr.Enabled`. Enables the libp2p Resource Manager. Recommended value is `true`.
|
||||
- `Swarm.ResourceMgr.MaxMemory`. Recommended value is `150MB`.
|
||||
|
||||
Documentation: https://github.com/ipfs/go-ipfs/blob/master/docs/config.md
|
||||
Documentation: https://github.com/ipfs/kubo/blob/master/docs/config.md
|
||||
|
||||
## Systemd service
|
||||
|
||||
When go-ipfs starts, its memory usage is around 100 MB and then it slowly increases. To keep memory usage within reasonable bounds the service needs to be restarted regularly.
|
||||
When **kubo** starts, its memory usage is around 100 MB and then it slowly increases, often beyond the Resource Manager's limit. To keep memory usage within reasonable bounds the service needs to be restarted regularly.
|
||||
|
||||
This can be achieved by using systemd process supervison features:
|
||||
|
||||
|
@ -44,8 +46,8 @@ ExecStart=/usr/local/bin/ipfs daemon
|
|||
User=ipfs
|
||||
Group=ipfs
|
||||
|
||||
# Terminate service every 20 minutes and restart automatically
|
||||
RuntimeMaxSec=1200
|
||||
# Terminate service every 30 minutes and restart automatically
|
||||
RuntimeMaxSec=1800
|
||||
Restart=on-failure
|
||||
|
||||
# Specify the absolute limit on memory usage
|
||||
|
|
|
@ -20,10 +20,10 @@ Generate RSA private key:
|
|||
mitractl generate-rsa-key
|
||||
```
|
||||
|
||||
Generate invite code:
|
||||
Generate invite code (note is optional):
|
||||
|
||||
```shell
|
||||
mitractl generate-invite-code
|
||||
mitractl generate-invite-code <note>
|
||||
```
|
||||
|
||||
List generated invites:
|
||||
|
@ -32,12 +32,24 @@ List generated invites:
|
|||
mitractl list-invite-codes
|
||||
```
|
||||
|
||||
Create user:
|
||||
|
||||
```shell
|
||||
mitractl create-user <username> <password> <role-name>
|
||||
```
|
||||
|
||||
Set or change password:
|
||||
|
||||
```shell
|
||||
mitractl set-password <user-id> <password>
|
||||
```
|
||||
|
||||
Change user's role (admin, user or read_only_user).
|
||||
|
||||
```shell
|
||||
mitractl set-role <user-id> <role-name>
|
||||
```
|
||||
|
||||
Delete profile:
|
||||
|
||||
```shell
|
||||
|
@ -50,6 +62,12 @@ Delete post:
|
|||
mitractl delete-post 55a3005f-f293-4168-ab70-6ab09a879679
|
||||
```
|
||||
|
||||
Delete custom emoji:
|
||||
|
||||
```shell
|
||||
mitractl delete-emoji emoji_name example.org
|
||||
```
|
||||
|
||||
Remove remote posts and media older than 30 days:
|
||||
|
||||
```shell
|
||||
|
@ -68,6 +86,18 @@ Delete empty remote profiles:
|
|||
mitractl delete-empty-profiles 100
|
||||
```
|
||||
|
||||
Delete unused remote emojis:
|
||||
|
||||
```shell
|
||||
mitractl prune-remote-emojis
|
||||
```
|
||||
|
||||
Import custom emoji from another instance:
|
||||
|
||||
```shell
|
||||
mitractl import-emoji emoji_name example.org
|
||||
```
|
||||
|
||||
Generate ethereum address:
|
||||
|
||||
```shell
|
||||
|
|
42
docs/onion.md
Normal file
42
docs/onion.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Tor federation
|
||||
|
||||
## Tor-only instance
|
||||
|
||||
Install Tor.
|
||||
|
||||
Install Mitra. Uncomment or add the following block to Mitra configuration file:
|
||||
|
||||
```yaml
|
||||
federation:
|
||||
proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
```
|
||||
|
||||
Where `127.0.0.1:9050` is the address and the port where Tor proxy is listening.
|
||||
|
||||
Configure the onion service by adding these lines to `torrc` configuration file:
|
||||
|
||||
```
|
||||
HiddenServiceDir /var/lib/tor/mitra/
|
||||
HiddenServicePort 80 127.0.0.1:8383
|
||||
```
|
||||
|
||||
Where `8383` should correspond to `http_port` setting in Mitra configuration file.
|
||||
|
||||
Restart the Tor service. Inside the `HiddenServiceDir` directory find the `hostname` file. This file contains the hostname of your onion service. Change the value of `instance_uri` parameter in Mitra configuration file to that hostname (it should end with `.onion`).
|
||||
|
||||
Start Mitra.
|
||||
|
||||
For more information about running onion services, visit https://community.torproject.org/onion-services/setup/
|
||||
|
||||
## Clearnet + Tor
|
||||
|
||||
Clearnet instances can federate with Tor-only instances.
|
||||
|
||||
Add the following block to Mitra configuration file:
|
||||
|
||||
```yaml
|
||||
federation:
|
||||
onion_proxy_url: 'socks5h://127.0.0.1:9050'
|
||||
```
|
||||
|
||||
Where `127.0.0.1:9050` is the address and the port where Tor proxy is listening.
|
|
@ -18,9 +18,15 @@ paths:
|
|||
grant_type:
|
||||
type: string
|
||||
enum:
|
||||
- authorization_code
|
||||
- password
|
||||
- eip4361
|
||||
example: eip4361
|
||||
code:
|
||||
description: A user authorization code, obtained via GET /oauth/authorize (required if grant type is "authorization_code").
|
||||
type: string
|
||||
nullable: true
|
||||
example: null
|
||||
username:
|
||||
description: User name (required if grant type is "password").
|
||||
type: string
|
||||
|
@ -114,7 +120,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AccountWithSource'
|
||||
$ref: '#/components/schemas/CredentialAccount'
|
||||
400:
|
||||
description: Invalid user data
|
||||
/api/v1/accounts/verify_credentials:
|
||||
|
@ -128,7 +134,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AccountWithSource'
|
||||
$ref: '#/components/schemas/CredentialAccount'
|
||||
/api/v1/accounts/update_credentials:
|
||||
patch:
|
||||
summary: Update the user's display and preferences.
|
||||
|
@ -183,7 +189,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AccountWithSource'
|
||||
$ref: '#/components/schemas/CredentialAccount'
|
||||
400:
|
||||
description: Invalid user data.
|
||||
/api/v1/accounts/signed_update:
|
||||
|
@ -204,42 +210,6 @@ paths:
|
|||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Update"}'
|
||||
/api/v1/accounts/move_followers:
|
||||
post:
|
||||
summary: Build Move(Person) activity for signing (experimental).
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
from_actor_id:
|
||||
description: The actor ID to move from.
|
||||
type: string
|
||||
example: 'https://xyz.com/users/test'
|
||||
followers_csv:
|
||||
description: The list of followers in CSV format.
|
||||
type: string
|
||||
example: |
|
||||
user1@example.org
|
||||
user2@test.com
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
params:
|
||||
description: Activity parameters
|
||||
$ref: '#/components/schemas/ActivityParameters'
|
||||
message:
|
||||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Move"}'
|
||||
400:
|
||||
description: Invalid data.
|
||||
/api/v1/accounts/send_activity:
|
||||
post:
|
||||
summary: Send signed activity (experimental).
|
||||
|
@ -382,6 +352,12 @@ paths:
|
|||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: resolve
|
||||
in: query
|
||||
description: Attempt WebFinger lookup.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- name: limit
|
||||
in: query
|
||||
description: Maximum number of results. Defaults to 40.
|
||||
|
@ -620,6 +596,91 @@ paths:
|
|||
$ref: '#/components/schemas/Relationship'
|
||||
404:
|
||||
description: Profile not found
|
||||
/api/v1/accounts/{account_id}/aliases:
|
||||
get:
|
||||
summary: Get actor's verified aliases.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/account_id'
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: Profile list
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Account'
|
||||
404:
|
||||
description: Profile not found
|
||||
/api/v1/accounts/{account_id}/aliases/all:
|
||||
get:
|
||||
summary: Get actor's aliases.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/account_id'
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Aliases'
|
||||
404:
|
||||
description: Profile not found
|
||||
/api/v1/apps:
|
||||
post:
|
||||
summary: Create a new application to obtain OAuth2 credentials.
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
client_name:
|
||||
description: A name for your application.
|
||||
type: string
|
||||
redirect_uris:
|
||||
description: Where the user should be redirected after authorization.
|
||||
type: string
|
||||
scopes:
|
||||
description: Space separated list of scopes.
|
||||
type: string
|
||||
example: 'read write'
|
||||
website:
|
||||
description: An URL to the homepage of your app.
|
||||
type: string
|
||||
nullable: true
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Application'
|
||||
- type: object
|
||||
properties:
|
||||
client_id:
|
||||
description: Client ID key, to be used for obtaining OAuth tokens.
|
||||
type: string
|
||||
client_secret:
|
||||
description: Client secret key, to be used for obtaining OAuth tokens.
|
||||
type: string
|
||||
400:
|
||||
description: Invalid request data.
|
||||
/api/v1/custom_emojis:
|
||||
get:
|
||||
summary: Returns custom emojis that are available on the server.
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: Emoji list
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CustomEmoji'
|
||||
/api/v1/directory:
|
||||
get:
|
||||
summary: List profiles visible in the directory.
|
||||
|
@ -719,6 +780,29 @@ paths:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Notification'
|
||||
/api/v1/settings/client_config:
|
||||
post:
|
||||
summary: Update client configuration.
|
||||
security:
|
||||
- tokenAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: |
|
||||
Client configuration.
|
||||
Should contain a single key identifying type of client.
|
||||
type: object
|
||||
example: {"mitra-web":{"theme":"dark"}}
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CredentialAccount'
|
||||
400:
|
||||
description: Invalid request data.
|
||||
/api/v1/settings/change_password:
|
||||
post:
|
||||
summary: Set or change user's password.
|
||||
|
@ -739,9 +823,31 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AccountWithSource'
|
||||
$ref: '#/components/schemas/CredentialAccount'
|
||||
400:
|
||||
description: Invalid request data.
|
||||
/api/v1/settings/aliases:
|
||||
post:
|
||||
summary: Add alias (not verified).
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
acct:
|
||||
description: Actor address.
|
||||
type: string
|
||||
example: user@example.com
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Aliases'
|
||||
404:
|
||||
description: Profile not found.
|
||||
/api/v1/settings/export_followers:
|
||||
get:
|
||||
summary: Export followers to CSV file
|
||||
|
@ -772,6 +878,53 @@ paths:
|
|||
example: |
|
||||
user1@example.org
|
||||
user2@example.org
|
||||
/api/v1/settings/import_follows:
|
||||
post:
|
||||
summary: Import follows from CSV file.
|
||||
security:
|
||||
- tokenAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
follows_csv:
|
||||
description: The list of followers in CSV format.
|
||||
type: string
|
||||
responses:
|
||||
204:
|
||||
description: Successful operation
|
||||
400:
|
||||
description: Invalid data.
|
||||
/api/v1/settings/move_followers:
|
||||
post:
|
||||
summary: Move followers from remote alias.
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
from_actor_id:
|
||||
description: The actor ID to move from.
|
||||
type: string
|
||||
example: 'https://xyz.com/users/test'
|
||||
followers_csv:
|
||||
description: The list of followers in CSV format.
|
||||
type: string
|
||||
example: |
|
||||
user1@example.org
|
||||
user2@test.com
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CredentialAccount'
|
||||
400:
|
||||
description: Invalid data.
|
||||
/api/v1/statuses:
|
||||
post:
|
||||
summary: Create new post.
|
||||
|
@ -806,6 +959,10 @@ paths:
|
|||
visibility:
|
||||
description: Visibility of the post.
|
||||
$ref: '#/components/schemas/Visibility'
|
||||
sensitiive:
|
||||
description: Mark post and attached media as sensitive?
|
||||
type: boolean
|
||||
default: false
|
||||
mentions:
|
||||
description: Array of profile IDs to be mentioned
|
||||
type: array
|
||||
|
@ -815,7 +972,7 @@ paths:
|
|||
required:
|
||||
- status
|
||||
responses:
|
||||
201:
|
||||
200:
|
||||
description: Post created
|
||||
content:
|
||||
application/json:
|
||||
|
@ -872,6 +1029,47 @@ paths:
|
|||
description: Post does not belong to user
|
||||
404:
|
||||
description: Post not found
|
||||
/api/v1/statuses/{status_id}/context:
|
||||
get:
|
||||
summary: View statuses above and below this status in the thread.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/status_id'
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ancestors:
|
||||
description: Parents in the thread.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Status'
|
||||
descendants:
|
||||
description: Parents in the thread.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Status'
|
||||
404:
|
||||
description: Post not found
|
||||
/api/v1/statuses/{status_id}/thread:
|
||||
get:
|
||||
summary: Get thread that contains given post.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/status_id'
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Status'
|
||||
404:
|
||||
description: Post not found
|
||||
/api/v1/statuses/{status_id}/favourite:
|
||||
post:
|
||||
summary: Add post to your favourites list
|
||||
|
@ -1304,6 +1502,11 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Field'
|
||||
emojis:
|
||||
description: Custom emoji entities to be used when rendering the profile.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CustomEmoji'
|
||||
followers_count:
|
||||
description: The reported followers of this profile.
|
||||
type: number
|
||||
|
@ -1313,7 +1516,7 @@ components:
|
|||
subscribers_count:
|
||||
description: The reported subscribers of this profile.
|
||||
type: number
|
||||
AccountWithSource:
|
||||
CredentialAccount:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Account'
|
||||
- type: object
|
||||
|
@ -1325,6 +1528,13 @@ components:
|
|||
note:
|
||||
description: Profile bio.
|
||||
type: string
|
||||
role:
|
||||
description: The role assigned to the currently authorized user.
|
||||
$ref: '#/components/schemas/Role'
|
||||
client_config:
|
||||
description: Client configurations.
|
||||
type: object
|
||||
example: {"mitra-web":{"theme":"dark"}}
|
||||
ActivityParameters:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1332,8 +1542,33 @@ components:
|
|||
description: Activity type
|
||||
type: string
|
||||
enum:
|
||||
- move
|
||||
- update
|
||||
Aliases:
|
||||
type: object
|
||||
properties:
|
||||
declared:
|
||||
description: Aliases declared by user.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Account'
|
||||
verified:
|
||||
description: Cryptographically verified aliases.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Account'
|
||||
Application:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: The name of your application.
|
||||
type: string
|
||||
website:
|
||||
description: The website associated with your application.
|
||||
type: string
|
||||
nullable: true
|
||||
redirect_uri:
|
||||
description: Where the user should be redirected after authorization.
|
||||
type: string
|
||||
Attachment:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1348,9 +1583,25 @@ components:
|
|||
- unknown
|
||||
- image
|
||||
- video
|
||||
- audio
|
||||
url:
|
||||
description: The location of the original full-size attachment.
|
||||
type: string
|
||||
CustomEmoji:
|
||||
type: object
|
||||
properties:
|
||||
shortcode:
|
||||
description: The name of the custom emoji.
|
||||
type: string
|
||||
url:
|
||||
description: A link to the custom emoji.
|
||||
type: string
|
||||
static_url:
|
||||
description: A link to a static copy of the custom emoji.
|
||||
type: string
|
||||
visible_in_picker:
|
||||
description: Whether this Emoji should be visible in the picker or unlisted.
|
||||
type: boolean
|
||||
Field:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1389,6 +1640,12 @@ components:
|
|||
registrations:
|
||||
description: Whether registrations are enabled.
|
||||
type: boolean
|
||||
approval_required:
|
||||
description: Whether registrations require moderator approval.
|
||||
type: boolean
|
||||
invites_enabled:
|
||||
description: Whether invites are enabled.
|
||||
type: boolean
|
||||
stats:
|
||||
description: Statistics about how much information the instance contains.
|
||||
type: object
|
||||
|
@ -1589,6 +1846,31 @@ components:
|
|||
description: Are you receiving this user's replies in your home timeline?
|
||||
type: boolean
|
||||
default: true
|
||||
Role:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: The ID of the role in the database.
|
||||
type: integer
|
||||
example: 1
|
||||
name:
|
||||
description: The name of the role.
|
||||
type: string
|
||||
enum:
|
||||
- user
|
||||
- admin
|
||||
- read_only_user
|
||||
permissions:
|
||||
description: A list of all permissions granted to the role.
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- create_follow_request
|
||||
- create_post
|
||||
- delete_any_post
|
||||
- delete_any_profile
|
||||
- manage_subscription_options
|
||||
Signature:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1629,6 +1911,13 @@ components:
|
|||
visibility:
|
||||
description: Visibility of this post.
|
||||
$ref: '#/components/schemas/Visibility'
|
||||
sensitiive:
|
||||
description: Is this post marked as sensitive content?
|
||||
type: boolean
|
||||
example: false
|
||||
spoiler_text:
|
||||
description: Subject or summary line, below which post content is collapsed until expanded.
|
||||
type: string
|
||||
media_attachments:
|
||||
description: Media that is attached to this post.
|
||||
type: array
|
||||
|
@ -1644,6 +1933,11 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Tag'
|
||||
emojis:
|
||||
description: Custom emoji to be used when rendering post content.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CustomEmoji'
|
||||
reblog:
|
||||
description: The post being reposted.
|
||||
type: object
|
||||
|
|
27
fedimovies-cli/Cargo.toml
Normal file
27
fedimovies-cli/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "fedimovies-cli"
|
||||
version = "1.22.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
|
||||
[[bin]]
|
||||
name = "fedimoviesctl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
fedimovies-config = { path = "../fedimovies-config" }
|
||||
fedimovies-models = { path = "../fedimovies-models" }
|
||||
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||
fedimovies = { path = ".." }
|
||||
|
||||
# Used for catching errors
|
||||
anyhow = "1.0.58"
|
||||
# Used to build admin CLI tool
|
||||
clap = { version = "3.2.18", default-features = false, features = ["std", "derive"] }
|
||||
# Used for logging
|
||||
log = "0.4.14"
|
||||
# Async runtime
|
||||
tokio = { version = "1.20.4", features = ["macros"] }
|
||||
# Used to work with UUIDs
|
||||
uuid = "1.1.2"
|
570
fedimovies-cli/src/cli.rs
Normal file
570
fedimovies-cli/src/cli.rs
Normal file
|
@ -0,0 +1,570 @@
|
|||
use anyhow::Error;
|
||||
use clap::Parser;
|
||||
use uuid::Uuid;
|
||||
|
||||
use fedimovies::activitypub::{
|
||||
actors::helpers::update_remote_profile, builders::delete_note::prepare_delete_note,
|
||||
builders::delete_person::prepare_delete_person, fetcher::fetchers::fetch_actor,
|
||||
fetcher::helpers::import_from_outbox,
|
||||
};
|
||||
use fedimovies::admin::roles::{role_from_str, ALLOWED_ROLES};
|
||||
use fedimovies::media::{remove_files, remove_media, MediaStorage};
|
||||
use fedimovies::validators::{emojis::EMOJI_LOCAL_MAX_SIZE, users::validate_local_username};
|
||||
use fedimovies_config::Config;
|
||||
use fedimovies_models::{
|
||||
attachments::queries::delete_unused_attachments,
|
||||
cleanup::find_orphaned_files,
|
||||
database::DatabaseClient,
|
||||
emojis::helpers::get_emoji_by_name,
|
||||
emojis::queries::{
|
||||
create_emoji, delete_emoji, find_unused_remote_emojis, get_emoji_by_name_and_hostname,
|
||||
},
|
||||
oauth::queries::delete_oauth_tokens,
|
||||
posts::queries::{delete_post, find_extraneous_posts, get_post_by_id},
|
||||
profiles::queries::{
|
||||
delete_profile, find_empty_profiles, find_unreachable, get_profile_by_id,
|
||||
get_profile_by_remote_actor_id,
|
||||
},
|
||||
subscriptions::queries::reset_subscriptions,
|
||||
users::queries::{
|
||||
create_invite_code, create_user, get_invite_codes, get_user_by_id, set_user_password,
|
||||
set_user_role,
|
||||
},
|
||||
users::types::UserCreateData,
|
||||
};
|
||||
use fedimovies_utils::{
|
||||
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||
datetime::{days_before_now, get_min_datetime},
|
||||
passwords::hash_password,
|
||||
};
|
||||
|
||||
/// Admin CLI tool
|
||||
#[derive(Parser)]
|
||||
pub struct Opts {
|
||||
#[clap(subcommand)]
|
||||
pub subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub enum SubCommand {
|
||||
GenerateRsaKey(GenerateRsaKey),
|
||||
GenerateEthereumAddress(GenerateEthereumAddress),
|
||||
|
||||
GenerateInviteCode(GenerateInviteCode),
|
||||
ListInviteCodes(ListInviteCodes),
|
||||
CreateUser(CreateUser),
|
||||
SetPassword(SetPassword),
|
||||
SetRole(SetRole),
|
||||
RefetchActor(RefetchActor),
|
||||
ReadOutbox(ReadOutbox),
|
||||
DeleteProfile(DeleteProfile),
|
||||
DeletePost(DeletePost),
|
||||
DeleteEmoji(DeleteEmoji),
|
||||
DeleteExtraneousPosts(DeleteExtraneousPosts),
|
||||
DeleteUnusedAttachments(DeleteUnusedAttachments),
|
||||
DeleteOrphanedFiles(DeleteOrphanedFiles),
|
||||
DeleteEmptyProfiles(DeleteEmptyProfiles),
|
||||
PruneRemoteEmojis(PruneRemoteEmojis),
|
||||
ListUnreachableActors(ListUnreachableActors),
|
||||
ImportEmoji(ImportEmoji),
|
||||
UpdateCurrentBlock(UpdateCurrentBlock),
|
||||
ResetSubscriptions(ResetSubscriptions),
|
||||
CreateMoneroWallet(CreateMoneroWallet),
|
||||
CheckExpiredInvoice(CheckExpiredInvoice),
|
||||
}
|
||||
|
||||
/// Generate RSA private key
|
||||
#[derive(Parser)]
|
||||
pub struct GenerateRsaKey;
|
||||
|
||||
impl GenerateRsaKey {
|
||||
pub fn execute(&self) -> () {
|
||||
let private_key = generate_rsa_key().unwrap();
|
||||
let private_key_str = serialize_private_key(&private_key).unwrap();
|
||||
println!("{}", private_key_str);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate ethereum address
|
||||
#[derive(Parser)]
|
||||
pub struct GenerateEthereumAddress;
|
||||
|
||||
impl GenerateEthereumAddress {
|
||||
pub fn execute(&self) -> () {
|
||||
println!("dummy");
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate invite code
|
||||
#[derive(Parser)]
|
||||
pub struct GenerateInviteCode {
|
||||
note: Option<String>,
|
||||
}
|
||||
|
||||
impl GenerateInviteCode {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let invite_code = create_invite_code(db_client, self.note.as_deref()).await?;
|
||||
println!("generated invite code: {}", invite_code);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// List invite codes
|
||||
#[derive(Parser)]
|
||||
pub struct ListInviteCodes;
|
||||
|
||||
impl ListInviteCodes {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let invite_codes = get_invite_codes(db_client).await?;
|
||||
if invite_codes.is_empty() {
|
||||
println!("no invite codes found");
|
||||
return Ok(());
|
||||
};
|
||||
for invite_code in invite_codes {
|
||||
if let Some(note) = invite_code.note {
|
||||
println!("{} ({})", invite_code.code, note);
|
||||
} else {
|
||||
println!("{}", invite_code.code);
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new user
|
||||
#[derive(Parser)]
|
||||
pub struct CreateUser {
|
||||
username: String,
|
||||
password: String,
|
||||
#[clap(value_parser = ALLOWED_ROLES)]
|
||||
role: String,
|
||||
}
|
||||
|
||||
impl CreateUser {
|
||||
pub async fn execute(&self, db_client: &mut impl DatabaseClient) -> Result<(), Error> {
|
||||
validate_local_username(&self.username)?;
|
||||
let password_hash = hash_password(&self.password)?;
|
||||
let private_key = generate_rsa_key()?;
|
||||
let private_key_pem = serialize_private_key(&private_key)?;
|
||||
let role = role_from_str(&self.role)?;
|
||||
let user_data = UserCreateData {
|
||||
username: self.username.clone(),
|
||||
password_hash: Some(password_hash),
|
||||
private_key_pem,
|
||||
wallet_address: None,
|
||||
invite_code: None,
|
||||
role,
|
||||
};
|
||||
create_user(db_client, user_data).await?;
|
||||
println!("user created");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Set password
|
||||
#[derive(Parser)]
|
||||
pub struct SetPassword {
|
||||
id: Uuid,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl SetPassword {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let password_hash = hash_password(&self.password)?;
|
||||
set_user_password(db_client, &self.id, password_hash).await?;
|
||||
// Revoke all sessions
|
||||
delete_oauth_tokens(db_client, &self.id).await?;
|
||||
println!("password updated");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Change user's role
|
||||
#[derive(Parser)]
|
||||
pub struct SetRole {
|
||||
id: Uuid,
|
||||
#[clap(value_parser = ALLOWED_ROLES)]
|
||||
role: String,
|
||||
}
|
||||
|
||||
impl SetRole {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let role = role_from_str(&self.role)?;
|
||||
set_user_role(db_client, &self.id, role).await?;
|
||||
println!("role changed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-fetch actor profile by actor ID
|
||||
#[derive(Parser)]
|
||||
pub struct RefetchActor {
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl RefetchActor {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?;
|
||||
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
||||
update_remote_profile(
|
||||
db_client,
|
||||
&config.instance(),
|
||||
&MediaStorage::from(config),
|
||||
profile,
|
||||
actor,
|
||||
)
|
||||
.await?;
|
||||
println!("profile updated");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pull activities from actor's outbox
|
||||
#[derive(Parser)]
|
||||
pub struct ReadOutbox {
|
||||
actor_id: String,
|
||||
#[clap(long, default_value_t = 5)]
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl ReadOutbox {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
import_from_outbox(config, db_client, &self.actor_id, self.limit).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete profile
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteProfile {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
impl DeleteProfile {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let profile = get_profile_by_id(db_client, &self.id).await?;
|
||||
let mut maybe_delete_person = None;
|
||||
if profile.is_local() {
|
||||
let user = get_user_by_id(db_client, &profile.id).await?;
|
||||
let activity = prepare_delete_person(db_client, &config.instance(), &user).await?;
|
||||
maybe_delete_person = Some(activity);
|
||||
};
|
||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
// Send Delete(Person) activities
|
||||
if let Some(activity) = maybe_delete_person {
|
||||
activity.enqueue(db_client).await?;
|
||||
};
|
||||
println!("profile deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete post
|
||||
#[derive(Parser)]
|
||||
pub struct DeletePost {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
impl DeletePost {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let post = get_post_by_id(db_client, &self.id).await?;
|
||||
let mut maybe_delete_note = None;
|
||||
if post.author.is_local() {
|
||||
let author = get_user_by_id(db_client, &post.author.id).await?;
|
||||
let activity =
|
||||
prepare_delete_note(db_client, &config.instance(), &author, &post).await?;
|
||||
maybe_delete_note = Some(activity);
|
||||
};
|
||||
let deletion_queue = delete_post(db_client, &post.id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
// Send Delete(Note) activity
|
||||
if let Some(activity) = maybe_delete_note {
|
||||
activity.enqueue(db_client).await?;
|
||||
};
|
||||
println!("post deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete custom emoji
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteEmoji {
|
||||
emoji_name: String,
|
||||
hostname: Option<String>,
|
||||
}
|
||||
|
||||
impl DeleteEmoji {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let emoji =
|
||||
get_emoji_by_name(db_client, &self.emoji_name, self.hostname.as_deref()).await?;
|
||||
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("emoji deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete old remote posts
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteExtraneousPosts {
|
||||
days: u32,
|
||||
}
|
||||
|
||||
impl DeleteExtraneousPosts {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let updated_before = days_before_now(self.days);
|
||||
let posts = find_extraneous_posts(db_client, &updated_before).await?;
|
||||
for post_id in posts {
|
||||
let deletion_queue = delete_post(db_client, &post_id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("post {} deleted", post_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete attachments that don't belong to any post
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteUnusedAttachments {
|
||||
days: u32,
|
||||
}
|
||||
|
||||
impl DeleteUnusedAttachments {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let created_before = days_before_now(self.days);
|
||||
let deletion_queue = delete_unused_attachments(db_client, &created_before).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("unused attachments deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Find and delete orphaned files
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteOrphanedFiles;
|
||||
|
||||
impl DeleteOrphanedFiles {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let media_dir = config.media_dir();
|
||||
let mut files = vec![];
|
||||
for maybe_path in std::fs::read_dir(&media_dir)? {
|
||||
let file_name = maybe_path?.file_name().to_string_lossy().to_string();
|
||||
files.push(file_name);
|
||||
}
|
||||
println!("found {} files", files.len());
|
||||
let orphaned = find_orphaned_files(db_client, files).await?;
|
||||
if !orphaned.is_empty() {
|
||||
remove_files(orphaned, &media_dir);
|
||||
println!("orphaned files deleted");
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete empty remote profiles
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteEmptyProfiles {
|
||||
days: u32,
|
||||
}
|
||||
|
||||
impl DeleteEmptyProfiles {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let updated_before = days_before_now(self.days);
|
||||
let profiles = find_empty_profiles(db_client, &updated_before).await?;
|
||||
for profile_id in profiles {
|
||||
let profile = get_profile_by_id(db_client, &profile_id).await?;
|
||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("profile {} deleted", profile.acct);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete unused remote emojis
|
||||
#[derive(Parser)]
|
||||
pub struct PruneRemoteEmojis;
|
||||
|
||||
impl PruneRemoteEmojis {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let emojis = find_unused_remote_emojis(db_client).await?;
|
||||
for emoji_id in emojis {
|
||||
let deletion_queue = delete_emoji(db_client, &emoji_id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("emoji {} deleted", emoji_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// List unreachable actors
|
||||
#[derive(Parser)]
|
||||
pub struct ListUnreachableActors {
|
||||
days: u32,
|
||||
}
|
||||
|
||||
impl ListUnreachableActors {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let unreachable_since = days_before_now(self.days);
|
||||
let profiles = find_unreachable(db_client, &unreachable_since).await?;
|
||||
println!(
|
||||
"{0: <60} | {1: <35} | {2: <35}",
|
||||
"ID", "unreachable since", "updated at",
|
||||
);
|
||||
for profile in profiles {
|
||||
println!(
|
||||
"{0: <60} | {1: <35} | {2: <35}",
|
||||
profile.actor_id.unwrap(),
|
||||
profile.unreachable_since.unwrap().to_string(),
|
||||
profile.updated_at.to_string(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Import custom emoji from another instance
|
||||
#[derive(Parser)]
|
||||
pub struct ImportEmoji {
|
||||
emoji_name: String,
|
||||
hostname: String,
|
||||
}
|
||||
|
||||
impl ImportEmoji {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let emoji =
|
||||
get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?;
|
||||
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
|
||||
println!("emoji is too big");
|
||||
return Ok(());
|
||||
};
|
||||
create_emoji(
|
||||
db_client,
|
||||
&emoji.emoji_name,
|
||||
None,
|
||||
emoji.image,
|
||||
None,
|
||||
&get_min_datetime(),
|
||||
)
|
||||
.await?;
|
||||
println!("added emoji to local collection");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Update blockchain synchronization starting block
|
||||
#[derive(Parser)]
|
||||
pub struct UpdateCurrentBlock {
|
||||
number: u64,
|
||||
}
|
||||
|
||||
impl UpdateCurrentBlock {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
_db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
println!("current block updated");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset all subscriptions
|
||||
/// (can be used during development or when switching between chains)
|
||||
#[derive(Parser)]
|
||||
pub struct ResetSubscriptions {
|
||||
#[clap(long)]
|
||||
ethereum_contract_replaced: bool,
|
||||
}
|
||||
|
||||
impl ResetSubscriptions {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
reset_subscriptions(db_client, self.ethereum_contract_replaced).await?;
|
||||
println!("subscriptions deleted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Monero wallet
|
||||
/// (can be used when monero-wallet-rpc runs with --wallet-dir option)
|
||||
#[derive(Parser)]
|
||||
pub struct CreateMoneroWallet {
|
||||
name: String,
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
impl CreateMoneroWallet {
|
||||
pub async fn execute(&self, _config: &Config) -> Result<(), Error> {
|
||||
println!("wallet created");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Check expired invoice
|
||||
#[derive(Parser)]
|
||||
pub struct CheckExpiredInvoice {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
impl CheckExpiredInvoice {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
_db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
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!(),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
30
fedimovies-config/Cargo.toml
Normal file
30
fedimovies-config/Cargo.toml
Normal file
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "fedimovies-config"
|
||||
version = "1.22.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.68"
|
||||
|
||||
[dependencies]
|
||||
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||
|
||||
# Used to read .env files
|
||||
dotenv = "0.15.0"
|
||||
# Used for logging
|
||||
log = { version = "0.4.14", features = ["serde"] }
|
||||
# Used for working with regular expressions
|
||||
regex = "1.6.0"
|
||||
# Used for working with RSA keys
|
||||
rsa = "0.5.0"
|
||||
# Used for serialization/deserialization
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
# Used to parse config file
|
||||
serde_yaml = "0.8.17"
|
||||
# Used for creating error types
|
||||
thiserror = "1.0.37"
|
||||
# Used to work with URLs
|
||||
url = "2.2.2"
|
||||
|
||||
[features]
|
||||
production = []
|
||||
test-utils = []
|
221
fedimovies-config/src/config.rs
Normal file
221
fedimovies-config/src/config.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use log::Level as LogLevel;
|
||||
use rsa::RsaPrivateKey;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use fedimovies_utils::urls::normalize_url;
|
||||
|
||||
use super::environment::Environment;
|
||||
use super::federation::FederationConfig;
|
||||
use super::limits::Limits;
|
||||
use super::registration::RegistrationConfig;
|
||||
use super::retention::RetentionConfig;
|
||||
use super::REEF_VERSION;
|
||||
|
||||
fn default_log_level() -> LogLevel {
|
||||
LogLevel::Info
|
||||
}
|
||||
|
||||
fn default_login_message() -> String {
|
||||
"What?!".to_string()
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
// Properties auto-populated from the environment
|
||||
#[serde(skip)]
|
||||
pub environment: Environment,
|
||||
|
||||
#[serde(skip)]
|
||||
pub config_path: String,
|
||||
|
||||
// Core settings
|
||||
pub database_url: String,
|
||||
#[serde(default)]
|
||||
pub tls_ca_file: Option<PathBuf>,
|
||||
pub storage_dir: PathBuf,
|
||||
pub web_client_dir: Option<PathBuf>,
|
||||
|
||||
pub http_host: String,
|
||||
pub http_port: u32,
|
||||
|
||||
#[serde(default)]
|
||||
pub http_cors_allowlist: Vec<String>,
|
||||
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: LogLevel,
|
||||
|
||||
// Domain name or <IP address>:<port>
|
||||
// URI scheme is optional
|
||||
instance_uri: String,
|
||||
|
||||
pub instance_title: String,
|
||||
pub instance_short_description: String,
|
||||
pub instance_description: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub tmdb_api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub movie_user_password: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(super) instance_rsa_key: Option<RsaPrivateKey>,
|
||||
|
||||
pub(super) registrations_open: Option<bool>, // deprecated
|
||||
|
||||
#[serde(default)]
|
||||
pub registration: RegistrationConfig,
|
||||
|
||||
// EIP-4361 login message
|
||||
#[serde(default = "default_login_message")]
|
||||
pub login_message: String,
|
||||
|
||||
pub(super) post_character_limit: Option<usize>, // deprecated
|
||||
|
||||
#[serde(default)]
|
||||
pub limits: Limits,
|
||||
|
||||
#[serde(default)]
|
||||
pub retention: RetentionConfig,
|
||||
|
||||
pub(super) proxy_url: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub federation: FederationConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub blocked_instances: Vec<String>,
|
||||
|
||||
// IPFS
|
||||
pub ipfs_api_url: Option<String>,
|
||||
pub ipfs_gateway_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(super) fn try_instance_url(&self) -> Result<Url, url::ParseError> {
|
||||
normalize_url(&self.instance_uri)
|
||||
}
|
||||
|
||||
pub fn instance(&self) -> Instance {
|
||||
Instance {
|
||||
_url: self.try_instance_url().unwrap(),
|
||||
actor_key: self.instance_rsa_key.clone().unwrap(),
|
||||
proxy_url: self.federation.proxy_url.clone(),
|
||||
onion_proxy_url: self.federation.onion_proxy_url.clone(),
|
||||
i2p_proxy_url: self.federation.i2p_proxy_url.clone(),
|
||||
// Private instance doesn't send activities and sign requests
|
||||
is_private: !self.federation.enabled,
|
||||
// || matches!(self.environment, Environment::Development),
|
||||
fetcher_timeout: self.federation.fetcher_timeout,
|
||||
deliverer_timeout: self.federation.deliverer_timeout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance_url(&self) -> String {
|
||||
self.instance().url()
|
||||
}
|
||||
|
||||
pub fn media_dir(&self) -> PathBuf {
|
||||
self.storage_dir.join("media")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Instance {
|
||||
_url: Url,
|
||||
// Instance actor
|
||||
pub actor_key: RsaPrivateKey,
|
||||
// Proxy for outgoing requests
|
||||
pub proxy_url: Option<String>,
|
||||
pub onion_proxy_url: Option<String>,
|
||||
pub i2p_proxy_url: Option<String>,
|
||||
// Private instance won't send signed HTTP requests
|
||||
pub is_private: bool,
|
||||
pub fetcher_timeout: u64,
|
||||
pub deliverer_timeout: u64,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn url(&self) -> String {
|
||||
self._url.origin().ascii_serialization()
|
||||
}
|
||||
|
||||
pub fn hostname(&self) -> String {
|
||||
self._url.host_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
pub fn agent(&self) -> String {
|
||||
format!(
|
||||
"Reef {version}; {instance_url}",
|
||||
version = REEF_VERSION,
|
||||
instance_url = self.url(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-utils")]
|
||||
impl Instance {
|
||||
pub fn for_test(url: &str) -> Self {
|
||||
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
Self {
|
||||
_url: Url::parse(url).unwrap(),
|
||||
actor_key: generate_weak_rsa_key().unwrap(),
|
||||
proxy_url: None,
|
||||
onion_proxy_url: None,
|
||||
i2p_proxy_url: None,
|
||||
is_private: true,
|
||||
fetcher_timeout: 0,
|
||||
deliverer_timeout: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
|
||||
#[test]
|
||||
fn test_instance_url_https_dns() {
|
||||
let instance_url = Url::parse("https://example.com/").unwrap();
|
||||
let instance_rsa_key = generate_weak_rsa_key().unwrap();
|
||||
let instance = Instance {
|
||||
_url: instance_url,
|
||||
actor_key: instance_rsa_key,
|
||||
proxy_url: None,
|
||||
onion_proxy_url: None,
|
||||
i2p_proxy_url: None,
|
||||
is_private: true,
|
||||
fetcher_timeout: 0,
|
||||
deliverer_timeout: 0,
|
||||
};
|
||||
|
||||
assert_eq!(instance.url(), "https://example.com");
|
||||
assert_eq!(instance.hostname(), "example.com");
|
||||
assert_eq!(
|
||||
instance.agent(),
|
||||
format!("Mitra {}; https://example.com", REEF_VERSION),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instance_url_http_ipv4() {
|
||||
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
|
||||
let instance_rsa_key = generate_weak_rsa_key().unwrap();
|
||||
let instance = Instance {
|
||||
_url: instance_url,
|
||||
actor_key: instance_rsa_key,
|
||||
proxy_url: None,
|
||||
onion_proxy_url: None,
|
||||
i2p_proxy_url: None,
|
||||
is_private: true,
|
||||
fetcher_timeout: 0,
|
||||
deliverer_timeout: 0,
|
||||
};
|
||||
|
||||
assert_eq!(instance.url(), "http://1.2.3.4:3777");
|
||||
assert_eq!(instance.hostname(), "1.2.3.4");
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::errors::ConversionError;
|
||||
use super::ConfigError;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Environment {
|
||||
|
@ -10,19 +10,23 @@ pub enum Environment {
|
|||
|
||||
impl Default for Environment {
|
||||
#[cfg(feature = "production")]
|
||||
fn default() -> Self { Self::Production }
|
||||
fn default() -> Self {
|
||||
Self::Production
|
||||
}
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn default() -> Self { Self::Development }
|
||||
fn default() -> Self {
|
||||
Self::Development
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Environment {
|
||||
type Err = ConversionError;
|
||||
type Err = ConfigError;
|
||||
|
||||
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
||||
let environment = match val {
|
||||
"development" => Environment::Development,
|
||||
"production" => Environment::Production,
|
||||
_ => return Err(ConversionError),
|
||||
_ => return Err(ConfigError("invalid environment type")),
|
||||
};
|
||||
Ok(environment)
|
||||
}
|
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,
|
||||
}
|
||||
}
|
||||
}
|
18
fedimovies-config/src/lib.rs
Normal file
18
fedimovies-config/src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
mod config;
|
||||
mod environment;
|
||||
mod federation;
|
||||
mod limits;
|
||||
mod loader;
|
||||
mod registration;
|
||||
mod retention;
|
||||
|
||||
pub use config::{Config, Instance};
|
||||
pub use environment::Environment;
|
||||
pub use loader::parse_config;
|
||||
pub use registration::{DefaultRole, RegistrationType};
|
||||
|
||||
pub const REEF_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("{0}")]
|
||||
pub struct ConfigError(&'static str);
|
106
fedimovies-config/src/limits.rs
Normal file
106
fedimovies-config/src/limits.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use super::ConfigError;
|
||||
use regex::Regex;
|
||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
|
||||
|
||||
const FILE_SIZE_RE: &str = r#"^(?i)(?P<size>\d+)(?P<unit>[kmg]?)b?$"#;
|
||||
|
||||
fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
|
||||
let file_size_re = Regex::new(FILE_SIZE_RE).expect("regexp should be valid");
|
||||
let caps = file_size_re
|
||||
.captures(value)
|
||||
.ok_or(ConfigError("invalid file size"))?;
|
||||
let size: usize = caps["size"]
|
||||
.to_string()
|
||||
.parse()
|
||||
.map_err(|_| ConfigError("invalid file size"))?;
|
||||
let unit = caps["unit"].to_string().to_lowercase();
|
||||
let multiplier = match unit.as_str() {
|
||||
"k" => usize::pow(10, 3),
|
||||
"m" => usize::pow(10, 6),
|
||||
"g" => usize::pow(10, 9),
|
||||
"" => 1,
|
||||
_ => return Err(ConfigError("invalid file size unit")),
|
||||
};
|
||||
Ok(size * multiplier)
|
||||
}
|
||||
|
||||
fn deserialize_file_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let file_size_str = String::deserialize(deserializer)?;
|
||||
let file_size = parse_file_size(&file_size_str).map_err(DeserializerError::custom)?;
|
||||
Ok(file_size)
|
||||
}
|
||||
|
||||
const fn default_file_size_limit() -> usize {
|
||||
20_000_000
|
||||
} // 20 MB
|
||||
const fn default_emoji_size_limit() -> usize {
|
||||
500_000
|
||||
} // 500 kB
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct MediaLimits {
|
||||
#[serde(
|
||||
default = "default_file_size_limit",
|
||||
deserialize_with = "deserialize_file_size"
|
||||
)]
|
||||
pub file_size_limit: usize,
|
||||
|
||||
#[serde(
|
||||
default = "default_emoji_size_limit",
|
||||
deserialize_with = "deserialize_file_size"
|
||||
)]
|
||||
pub emoji_size_limit: usize,
|
||||
}
|
||||
|
||||
impl Default for MediaLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file_size_limit: default_file_size_limit(),
|
||||
emoji_size_limit: default_emoji_size_limit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn default_post_character_limit() -> usize {
|
||||
2000
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct PostLimits {
|
||||
#[serde(default = "default_post_character_limit")]
|
||||
pub character_limit: usize,
|
||||
}
|
||||
|
||||
impl Default for PostLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
character_limit: default_post_character_limit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
pub struct Limits {
|
||||
#[serde(default)]
|
||||
pub media: MediaLimits,
|
||||
#[serde(default)]
|
||||
pub posts: PostLimits,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_size() {
|
||||
let file_size = parse_file_size("1234").unwrap();
|
||||
assert_eq!(file_size, 1234);
|
||||
let file_size = parse_file_size("89kB").unwrap();
|
||||
assert_eq!(file_size, 89_000);
|
||||
let file_size = parse_file_size("12M").unwrap();
|
||||
assert_eq!(file_size, 12_000_000);
|
||||
}
|
||||
}
|
134
fedimovies-config/src/loader.rs
Normal file
134
fedimovies-config/src/loader.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use rsa::RsaPrivateKey;
|
||||
|
||||
use fedimovies_utils::{
|
||||
crypto_rsa::{deserialize_private_key, generate_rsa_key, serialize_private_key},
|
||||
files::{set_file_permissions, write_file},
|
||||
};
|
||||
|
||||
use super::config::Config;
|
||||
use super::environment::Environment;
|
||||
use super::registration::{DefaultRole, RegistrationType};
|
||||
|
||||
struct EnvConfig {
|
||||
config_path: String,
|
||||
environment: Option<Environment>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "production")]
|
||||
const DEFAULT_CONFIG_PATH: &str = "/etc/fedimovies/config.yaml";
|
||||
#[cfg(not(feature = "production"))]
|
||||
const DEFAULT_CONFIG_PATH: &str = "config.yaml";
|
||||
|
||||
fn parse_env() -> EnvConfig {
|
||||
dotenv::from_filename(".env.local").ok();
|
||||
dotenv::dotenv().ok();
|
||||
let config_path = std::env::var("CONFIG_PATH").unwrap_or(DEFAULT_CONFIG_PATH.to_string());
|
||||
let environment = std::env::var("ENVIRONMENT")
|
||||
.ok()
|
||||
.map(|val| Environment::from_str(&val).expect("invalid environment type"));
|
||||
EnvConfig {
|
||||
config_path,
|
||||
environment,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn geteuid() -> u32;
|
||||
}
|
||||
|
||||
fn check_directory_owner(path: &Path) -> () {
|
||||
let metadata = std::fs::metadata(path).expect("can't read file metadata");
|
||||
let owner_uid = metadata.uid();
|
||||
let current_uid = unsafe { geteuid() };
|
||||
if owner_uid != current_uid {
|
||||
panic!(
|
||||
"{} owner ({}) is different from the current user ({})",
|
||||
path.display(),
|
||||
owner_uid,
|
||||
current_uid,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates new instance RSA key or returns existing key
|
||||
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
||||
let private_key_path = storage_dir.join("instance_rsa_key");
|
||||
if private_key_path.exists() {
|
||||
let private_key_str =
|
||||
std::fs::read_to_string(&private_key_path).expect("failed to read instance RSA key");
|
||||
let private_key =
|
||||
deserialize_private_key(&private_key_str).expect("failed to read instance RSA key");
|
||||
private_key
|
||||
} else {
|
||||
let private_key = generate_rsa_key().expect("failed to generate RSA key");
|
||||
let private_key_str =
|
||||
serialize_private_key(&private_key).expect("failed to serialize RSA key");
|
||||
write_file(private_key_str.as_bytes(), &private_key_path)
|
||||
.expect("failed to write instance RSA key");
|
||||
set_file_permissions(&private_key_path, 0o600)
|
||||
.expect("failed to set permissions on RSA key file");
|
||||
private_key
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_config() -> (Config, Vec<&'static str>) {
|
||||
let env = parse_env();
|
||||
let config_yaml =
|
||||
std::fs::read_to_string(&env.config_path).expect("failed to load config file");
|
||||
let mut config = serde_yaml::from_str::<Config>(&config_yaml).expect("invalid yaml data");
|
||||
let mut warnings = vec![];
|
||||
|
||||
// Set parameters from environment
|
||||
config.config_path = env.config_path;
|
||||
if let Some(environment) = env.environment {
|
||||
// Overwrite default only if ENVIRONMENT variable is set
|
||||
config.environment = environment;
|
||||
};
|
||||
|
||||
// Validate config
|
||||
if !config.storage_dir.exists() {
|
||||
panic!("storage directory does not exist");
|
||||
};
|
||||
check_directory_owner(&config.storage_dir);
|
||||
config.try_instance_url().expect("invalid instance URI");
|
||||
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
||||
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
||||
};
|
||||
|
||||
// Migrations
|
||||
if let Some(registrations_open) = config.registrations_open {
|
||||
// Change type if 'registrations_open' parameter is used
|
||||
warnings
|
||||
.push("'registrations_open' setting is deprecated, use 'registration.type' instead");
|
||||
if registrations_open {
|
||||
config.registration.registration_type = RegistrationType::Open;
|
||||
} else {
|
||||
config.registration.registration_type = RegistrationType::Invite;
|
||||
};
|
||||
};
|
||||
if let Some(read_only_user) = config.registration.default_role_read_only_user {
|
||||
warnings.push("'default_role_read_only_user' setting is deprecated, use 'registration.default_role' instead");
|
||||
if read_only_user {
|
||||
config.registration.default_role = DefaultRole::ReadOnlyUser;
|
||||
} else {
|
||||
config.registration.default_role = DefaultRole::NormalUser;
|
||||
};
|
||||
};
|
||||
if let Some(post_character_limit) = config.post_character_limit {
|
||||
warnings.push("'post_character_limit' setting is deprecated, use 'limits.posts.character_limit' instead");
|
||||
config.limits.posts.character_limit = post_character_limit;
|
||||
};
|
||||
if let Some(ref proxy_url) = config.proxy_url {
|
||||
warnings.push("'proxy_url' setting is deprecated, use 'federation.proxy_url' instead");
|
||||
config.federation.proxy_url = Some(proxy_url.to_string());
|
||||
};
|
||||
|
||||
// Insert instance RSA key
|
||||
config.instance_rsa_key = Some(read_instance_rsa_key(&config.storage_dir));
|
||||
|
||||
(config, warnings)
|
||||
}
|
66
fedimovies-config/src/registration.rs
Normal file
66
fedimovies-config/src/registration.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum RegistrationType {
|
||||
Open,
|
||||
Invite,
|
||||
}
|
||||
|
||||
impl Default for RegistrationType {
|
||||
fn default() -> Self {
|
||||
Self::Invite
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RegistrationType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let registration_type_str = String::deserialize(deserializer)?;
|
||||
let registration_type = match registration_type_str.as_str() {
|
||||
"open" => Self::Open,
|
||||
"invite" => Self::Invite,
|
||||
_ => return Err(DeserializerError::custom("unknown registration type")),
|
||||
};
|
||||
Ok(registration_type)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DefaultRole {
|
||||
NormalUser,
|
||||
ReadOnlyUser,
|
||||
}
|
||||
|
||||
impl Default for DefaultRole {
|
||||
fn default() -> Self {
|
||||
Self::NormalUser
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DefaultRole {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let role_str = String::deserialize(deserializer)?;
|
||||
let role = match role_str.as_str() {
|
||||
"user" => Self::NormalUser,
|
||||
"read_only_user" => Self::ReadOnlyUser,
|
||||
_ => return Err(DeserializerError::custom("unknown role name")),
|
||||
};
|
||||
Ok(role)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
pub struct RegistrationConfig {
|
||||
#[serde(rename = "type")]
|
||||
pub registration_type: RegistrationType,
|
||||
|
||||
pub(super) default_role_read_only_user: Option<bool>, // deprecated
|
||||
|
||||
#[serde(default)]
|
||||
pub default_role: DefaultRole,
|
||||
}
|
17
fedimovies-config/src/retention.rs
Normal file
17
fedimovies-config/src/retention.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct RetentionConfig {
|
||||
pub extraneous_posts: Option<u32>,
|
||||
pub empty_profiles: Option<u32>,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for RetentionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
extraneous_posts: None,
|
||||
empty_profiles: None,
|
||||
}
|
||||
}
|
||||
}
|
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 = []
|
16
fedimovies-models/migrations/V0039__emoji.sql
Normal file
16
fedimovies-models/migrations/V0039__emoji.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE emoji (
|
||||
id UUID PRIMARY KEY,
|
||||
emoji_name VARCHAR(100) NOT NULL,
|
||||
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
|
||||
image JSONB NOT NULL,
|
||||
object_id VARCHAR(250) UNIQUE,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
UNIQUE (emoji_name, hostname),
|
||||
CHECK ((hostname IS NULL) = (object_id IS NULL))
|
||||
);
|
||||
|
||||
CREATE TABLE post_emoji (
|
||||
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, emoji_id)
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE media_attachment ADD COLUMN file_size INTEGER;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE user_account ADD COLUMN user_role SMALLINT NOT NULL DEFAULT 1;
|
||||
ALTER TABLE user_account ALTER COLUMN user_role DROP DEFAULT;
|
10
fedimovies-models/migrations/V0042__oauth_application.sql
Normal file
10
fedimovies-models/migrations/V0042__oauth_application.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE oauth_application (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
app_name VARCHAR(100) NOT NULL,
|
||||
website VARCHAR(100),
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
redirect_uri VARCHAR(200) NOT NULL,
|
||||
client_id UUID UNIQUE NOT NULL,
|
||||
client_secret VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE oauth_authorization (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
code VARCHAR(100) UNIQUE NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
|
||||
application_id INTEGER NOT NULL REFERENCES oauth_application (id) ON DELETE CASCADE,
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE user_invite_code ADD COLUMN note VARCHAR(200);
|
||||
ALTER TABLE user_invite_code ADD COLUMN created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP;
|
5
fedimovies-models/migrations/V0045__profile_emoji.sql
Normal file
5
fedimovies-models/migrations/V0045__profile_emoji.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE profile_emoji (
|
||||
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (profile_id, emoji_id)
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE actor_profile ADD COLUMN emojis JSONB NOT NULL DEFAULT '[]';
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE internal_property (
|
||||
property_name VARCHAR(100) PRIMARY KEY,
|
||||
property_value JSONB NOT NULL
|
||||
);
|
|
@ -0,0 +1,18 @@
|
|||
UPDATE actor_profile
|
||||
SET identity_proofs = replaced.identity_proofs
|
||||
FROM (
|
||||
SELECT
|
||||
actor_profile.id,
|
||||
jsonb_agg(
|
||||
CASE
|
||||
WHEN identity_proof ->> 'proof_type' = 'ethereum-eip191-00'
|
||||
THEN jsonb_set(identity_proof, '{proof_type}', '1')
|
||||
WHEN identity_proof ->> 'proof_type' = 'MitraMinisignSignature2022A'
|
||||
THEN jsonb_set(identity_proof, '{proof_type}', '2')
|
||||
END
|
||||
) AS identity_proofs
|
||||
FROM actor_profile
|
||||
CROSS JOIN jsonb_array_elements(actor_profile.identity_proofs) AS identity_proof
|
||||
GROUP BY actor_profile.id
|
||||
) AS replaced
|
||||
WHERE actor_profile.id = replaced.id;
|
|
@ -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;
|
|
@ -1,3 +1,8 @@
|
|||
CREATE TABLE internal_property (
|
||||
property_name VARCHAR(100) PRIMARY KEY,
|
||||
property_value JSONB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE background_job (
|
||||
id UUID PRIMARY KEY,
|
||||
job_type SMALLINT NOT NULL,
|
||||
|
@ -21,15 +26,18 @@ CREATE TABLE actor_profile (
|
|||
bio_source TEXT,
|
||||
avatar JSONB,
|
||||
banner JSONB,
|
||||
manually_approves_followers BOOLEAN NOT NULL,
|
||||
identity_proofs JSONB NOT NULL DEFAULT '[]',
|
||||
payment_options JSONB NOT NULL DEFAULT '[]',
|
||||
extra_fields JSONB NOT NULL DEFAULT '[]',
|
||||
aliases JSONB NOT NULL DEFAULT '[]',
|
||||
follower_count INTEGER NOT NULL CHECK (follower_count >= 0) DEFAULT 0,
|
||||
following_count INTEGER NOT NULL CHECK (following_count >= 0) DEFAULT 0,
|
||||
subscriber_count INTEGER NOT NULL CHECK (subscriber_count >= 0) DEFAULT 0,
|
||||
post_count INTEGER NOT NULL CHECK (post_count >= 0) DEFAULT 0,
|
||||
emojis JSONB NOT NULL DEFAULT '[]',
|
||||
actor_json JSONB,
|
||||
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
||||
actor_id VARCHAR(2000) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
unreachable_since TIMESTAMP WITH TIME ZONE,
|
||||
|
@ -38,7 +46,9 @@ CREATE TABLE actor_profile (
|
|||
|
||||
CREATE TABLE user_invite_code (
|
||||
code VARCHAR(100) PRIMARY KEY,
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
note VARCHAR(200),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE user_account (
|
||||
|
@ -47,9 +57,32 @@ CREATE TABLE user_account (
|
|||
password_hash VARCHAR(200),
|
||||
private_key TEXT NOT NULL,
|
||||
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
|
||||
user_role SMALLINT NOT NULL,
|
||||
client_config JSONB NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE oauth_application (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
app_name VARCHAR(100) NOT NULL,
|
||||
website VARCHAR(100),
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
redirect_uri VARCHAR(2000) NOT NULL,
|
||||
client_id UUID UNIQUE NOT NULL,
|
||||
client_secret VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE oauth_authorization (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
code VARCHAR(100) UNIQUE NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
|
||||
application_id INTEGER NOT NULL REFERENCES oauth_application (id) ON DELETE CASCADE,
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE oauth_token (
|
||||
id SERIAL PRIMARY KEY,
|
||||
owner_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
|
||||
|
@ -63,16 +96,18 @@ CREATE TABLE relationship (
|
|||
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
target_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
relationship_type SMALLINT NOT NULL,
|
||||
UNIQUE (source_id, target_id, relationship_type)
|
||||
UNIQUE (source_id, target_id, relationship_type),
|
||||
CHECK (source_id != target_id)
|
||||
);
|
||||
|
||||
CREATE TABLE follow_request (
|
||||
id UUID PRIMARY KEY,
|
||||
source_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
target_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
activity_id VARCHAR(250) UNIQUE,
|
||||
activity_id VARCHAR(2000) UNIQUE,
|
||||
request_status SMALLINT NOT NULL,
|
||||
UNIQUE (source_id, target_id)
|
||||
UNIQUE (source_id, target_id),
|
||||
CHECK (source_id != target_id)
|
||||
);
|
||||
|
||||
CREATE TABLE post (
|
||||
|
@ -81,11 +116,12 @@ CREATE TABLE post (
|
|||
content TEXT NOT NULL,
|
||||
in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||
repost_of_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||
visilibity SMALLINT NOT NULL,
|
||||
visibility SMALLINT NOT NULL,
|
||||
is_sensitive BOOLEAN NOT NULL,
|
||||
reply_count INTEGER NOT NULL CHECK (reply_count >= 0) DEFAULT 0,
|
||||
reaction_count INTEGER NOT NULL CHECK (reaction_count >= 0) DEFAULT 0,
|
||||
repost_count INTEGER NOT NULL CHECK (repost_count >= 0) DEFAULT 0,
|
||||
object_id VARCHAR(200) UNIQUE,
|
||||
object_id VARCHAR(2000) UNIQUE,
|
||||
ipfs_cid VARCHAR(200),
|
||||
token_id INTEGER,
|
||||
token_tx_id VARCHAR(200),
|
||||
|
@ -98,7 +134,7 @@ CREATE TABLE post_reaction (
|
|||
id UUID PRIMARY KEY,
|
||||
author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||
activity_id VARCHAR(250) UNIQUE,
|
||||
activity_id VARCHAR(2000) UNIQUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
UNIQUE (author_id, post_id)
|
||||
);
|
||||
|
@ -106,8 +142,9 @@ CREATE TABLE post_reaction (
|
|||
CREATE TABLE media_attachment (
|
||||
id UUID PRIMARY KEY,
|
||||
owner_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
media_type VARCHAR(50),
|
||||
file_name VARCHAR(200) NOT NULL,
|
||||
file_size INTEGER,
|
||||
media_type VARCHAR(50),
|
||||
ipfs_cid VARCHAR(200),
|
||||
post_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
|
@ -133,7 +170,31 @@ CREATE TABLE post_tag (
|
|||
CREATE TABLE post_link (
|
||||
source_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||
target_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (source_id, target_id)
|
||||
PRIMARY KEY (source_id, target_id),
|
||||
CHECK (source_id != target_id)
|
||||
);
|
||||
|
||||
CREATE TABLE emoji (
|
||||
id UUID PRIMARY KEY,
|
||||
emoji_name VARCHAR(100) NOT NULL,
|
||||
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
|
||||
image JSONB NOT NULL,
|
||||
object_id VARCHAR(2000) UNIQUE,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
UNIQUE (emoji_name, hostname),
|
||||
CHECK ((hostname IS NULL) = (object_id IS NULL))
|
||||
);
|
||||
|
||||
CREATE TABLE post_emoji (
|
||||
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
|
||||
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, emoji_id)
|
||||
);
|
||||
|
||||
CREATE TABLE profile_emoji (
|
||||
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (profile_id, emoji_id)
|
||||
);
|
||||
|
||||
CREATE TABLE notification (
|
||||
|
@ -163,7 +224,8 @@ CREATE TABLE invoice (
|
|||
amount BIGINT NOT NULL CHECK (amount >= 0),
|
||||
invoice_status SMALLINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (chain_id, payment_address)
|
||||
UNIQUE (chain_id, payment_address),
|
||||
CHECK (sender_id != recipient_id)
|
||||
);
|
||||
|
||||
CREATE TABLE subscription (
|
||||
|
@ -174,5 +236,6 @@ CREATE TABLE subscription (
|
|||
chain_id VARCHAR(50) NOT NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
UNIQUE (sender_id, recipient_id)
|
||||
UNIQUE (sender_id, recipient_id),
|
||||
CHECK (sender_id != recipient_id)
|
||||
);
|
132
fedimovies-models/src/attachments/queries.rs
Normal file
132
fedimovies-models/src/attachments/queries.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use fedimovies_utils::id::generate_ulid;
|
||||
|
||||
use crate::cleanup::{find_orphaned_files, find_orphaned_ipfs_objects, DeletionQueue};
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
|
||||
use super::types::DbMediaAttachment;
|
||||
|
||||
pub async fn create_attachment(
|
||||
db_client: &impl DatabaseClient,
|
||||
owner_id: &Uuid,
|
||||
file_name: String,
|
||||
file_size: usize,
|
||||
media_type: Option<String>,
|
||||
) -> Result<DbMediaAttachment, DatabaseError> {
|
||||
let attachment_id = generate_ulid();
|
||||
let file_size: i32 = file_size.try_into().expect("value should be within bounds");
|
||||
let inserted_row = db_client
|
||||
.query_one(
|
||||
"
|
||||
INSERT INTO media_attachment (
|
||||
id,
|
||||
owner_id,
|
||||
file_name,
|
||||
file_size,
|
||||
media_type
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING media_attachment
|
||||
",
|
||||
&[
|
||||
&attachment_id,
|
||||
&owner_id,
|
||||
&file_name,
|
||||
&file_size,
|
||||
&media_type,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
|
||||
Ok(db_attachment)
|
||||
}
|
||||
|
||||
pub async fn set_attachment_ipfs_cid(
|
||||
db_client: &impl DatabaseClient,
|
||||
attachment_id: &Uuid,
|
||||
ipfs_cid: &str,
|
||||
) -> Result<DbMediaAttachment, DatabaseError> {
|
||||
let maybe_row = db_client
|
||||
.query_opt(
|
||||
"
|
||||
UPDATE media_attachment
|
||||
SET ipfs_cid = $1
|
||||
WHERE id = $2 AND ipfs_cid IS NULL
|
||||
RETURNING media_attachment
|
||||
",
|
||||
&[&ipfs_cid, &attachment_id],
|
||||
)
|
||||
.await?;
|
||||
let row = maybe_row.ok_or(DatabaseError::NotFound("attachment"))?;
|
||||
let db_attachment = row.try_get("media_attachment")?;
|
||||
Ok(db_attachment)
|
||||
}
|
||||
|
||||
pub async fn delete_unused_attachments(
|
||||
db_client: &impl DatabaseClient,
|
||||
created_before: &DateTime<Utc>,
|
||||
) -> Result<DeletionQueue, DatabaseError> {
|
||||
let rows = db_client
|
||||
.query(
|
||||
"
|
||||
DELETE FROM media_attachment
|
||||
WHERE post_id IS NULL AND created_at < $1
|
||||
RETURNING file_name, ipfs_cid
|
||||
",
|
||||
&[&created_before],
|
||||
)
|
||||
.await?;
|
||||
let mut files = vec![];
|
||||
let mut ipfs_objects = vec![];
|
||||
for row in rows {
|
||||
let file_name = row.try_get("file_name")?;
|
||||
files.push(file_name);
|
||||
if let Some(ipfs_cid) = row.try_get("ipfs_cid")? {
|
||||
ipfs_objects.push(ipfs_cid);
|
||||
};
|
||||
}
|
||||
let orphaned_files = find_orphaned_files(db_client, files).await?;
|
||||
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(db_client, ipfs_objects).await?;
|
||||
Ok(DeletionQueue {
|
||||
files: orphaned_files,
|
||||
ipfs_objects: orphaned_ipfs_objects,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database::test_utils::create_test_database;
|
||||
use crate::profiles::{queries::create_profile, types::ProfileCreateData};
|
||||
use serial_test::serial;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_attachment() {
|
||||
let db_client = &mut create_test_database().await;
|
||||
let profile_data = ProfileCreateData {
|
||||
username: "test".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let profile = create_profile(db_client, profile_data).await.unwrap();
|
||||
let file_name = "test.jpg";
|
||||
let file_size = 10000;
|
||||
let media_type = "image/png";
|
||||
let attachment = create_attachment(
|
||||
db_client,
|
||||
&profile.id,
|
||||
file_name.to_string(),
|
||||
file_size,
|
||||
Some(media_type.to_string()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(attachment.owner_id, profile.id);
|
||||
assert_eq!(attachment.file_name, file_name);
|
||||
assert_eq!(attachment.file_size.unwrap(), file_size as i32);
|
||||
assert_eq!(attachment.media_type.unwrap(), media_type);
|
||||
assert!(attachment.post_id.is_none());
|
||||
}
|
||||
}
|
|
@ -7,8 +7,9 @@ use uuid::Uuid;
|
|||
pub struct DbMediaAttachment {
|
||||
pub id: Uuid,
|
||||
pub owner_id: Uuid,
|
||||
pub media_type: Option<String>,
|
||||
pub file_name: String,
|
||||
pub file_size: Option<i32>,
|
||||
pub media_type: Option<String>,
|
||||
pub ipfs_cid: Option<String>,
|
||||
pub post_id: Option<Uuid>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
@ -18,6 +19,7 @@ pub enum AttachmentType {
|
|||
Unknown,
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
}
|
||||
|
||||
impl AttachmentType {
|
||||
|
@ -28,10 +30,12 @@ impl AttachmentType {
|
|||
Self::Image
|
||||
} else if media_type.starts_with("video/") {
|
||||
Self::Video
|
||||
} else if media_type.starts_with("audio/") {
|
||||
Self::Audio
|
||||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
},
|
||||
}
|
||||
None => Self::Unknown,
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde_json::Value;
|
||||
use tokio_postgres::GenericClient;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::DatabaseError;
|
||||
use super::types::{DbBackgroundJob, JobStatus, JobType};
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
|
||||
pub async fn enqueue_job(
|
||||
db_client: &impl GenericClient,
|
||||
db_client: &impl DatabaseClient,
|
||||
job_type: &JobType,
|
||||
job_data: &Value,
|
||||
scheduled_for: &DateTime<Utc>,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let job_id = Uuid::new_v4();
|
||||
db_client.execute(
|
||||
"
|
||||
db_client
|
||||
.execute(
|
||||
"
|
||||
INSERT INTO background_job (
|
||||
id,
|
||||
job_type,
|
||||
|
@ -23,18 +23,23 @@ pub async fn enqueue_job(
|
|||
)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
",
|
||||
&[&job_id, &job_type, &job_data, &scheduled_for],
|
||||
).await?;
|
||||
&[&job_id, &job_type, &job_data, &scheduled_for],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_job_batch(
|
||||
db_client: &impl GenericClient,
|
||||
db_client: &impl DatabaseClient,
|
||||
job_type: &JobType,
|
||||
batch_size: i64,
|
||||
batch_size: u32,
|
||||
job_timeout: u32,
|
||||
) -> 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
|
||||
SET
|
||||
job_status = $1,
|
||||
|
@ -44,37 +49,51 @@ pub async fn get_job_batch(
|
|||
FROM background_job
|
||||
WHERE
|
||||
job_type = $2
|
||||
AND job_status = $3
|
||||
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
|
||||
)
|
||||
RETURNING background_job
|
||||
",
|
||||
&[
|
||||
&JobStatus::Running,
|
||||
&job_type,
|
||||
&JobStatus::Queued,
|
||||
&batch_size,
|
||||
],
|
||||
).await?;
|
||||
let jobs = rows.iter()
|
||||
&[
|
||||
&JobStatus::Running,
|
||||
&job_type,
|
||||
&JobStatus::Queued,
|
||||
&i64::from(batch_size),
|
||||
&job_timeout_pg,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
let jobs = rows
|
||||
.iter()
|
||||
.map(|row| row.try_get("background_job"))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(jobs)
|
||||
}
|
||||
|
||||
pub async fn delete_job_from_queue(
|
||||
db_client: &impl GenericClient,
|
||||
db_client: &impl DatabaseClient,
|
||||
job_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let deleted_count = db_client.execute(
|
||||
"
|
||||
let deleted_count = db_client
|
||||
.execute(
|
||||
"
|
||||
DELETE FROM background_job
|
||||
WHERE id = $1
|
||||
",
|
||||
&[&job_id],
|
||||
).await?;
|
||||
&[&job_id],
|
||||
)
|
||||
.await?;
|
||||
if deleted_count == 0 {
|
||||
return Err(DatabaseError::NotFound("background job"));
|
||||
};
|
||||
|
@ -83,10 +102,10 @@ pub async fn delete_job_from_queue(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database::test_utils::create_test_database;
|
||||
use serde_json::json;
|
||||
use serial_test::serial;
|
||||
use crate::database::test_utils::create_test_database;
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
|
@ -99,20 +118,22 @@ mod tests {
|
|||
"failure_count": 0,
|
||||
});
|
||||
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);
|
||||
let job = &batch_1[0];
|
||||
assert_eq!(job.job_type, job_type);
|
||||
assert_eq!(job.job_data, job_data);
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde_json::Value;
|
||||
use postgres_types::FromSql;
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::{
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue