Compare commits
59 commits
Author | SHA1 | Date | |
---|---|---|---|
Rafael Caricio | d2075dcbd2 | ||
Rafael Caricio | 7281340423 | ||
Rafael Caricio | 406781570c | ||
Rafael Caricio | 205ea8c5b8 | ||
Rafael Caricio | 9944095959 | ||
Rafael Caricio | 13d6fa25bc | ||
Rafael Caricio | 456d0789fb | ||
Rafael Caricio | f73a05439d | ||
Rafael Caricio | 4df257d024 | ||
Rafael Caricio | 44004de576 | ||
Rafael Caricio | 93be6f8559 | ||
Rafael Caricio | 3860b4780d | ||
Rafael Caricio | de6072026b | ||
Rafael Caricio | 2b44c8e20d | ||
Rafael Caricio | d8bea2c868 | ||
Rafael Caricio | 5d0b03c2a3 | ||
Rafael Caricio | a2bc297f0e | ||
Rafael Caricio | fe8380e359 | ||
Rafael Caricio | 22883798b3 | ||
Rafael Caricio | cb61f4a86b | ||
Rafael Caricio | bb28bf800d | ||
Rafael Caricio | 60a27b5b11 | ||
Rafael Caricio | 0d77557ad6 | ||
Rafael Caricio | 1e40a42524 | ||
Rafael Caricio | b7fafe6458 | ||
Rafael Caricio | 17d8c11726 | ||
Rafael Caricio | b049b75873 | ||
Rafael Caricio | 272d06897a | ||
Rafael Caricio | 47529ff703 | ||
b4ff7abbc1 | |||
5906185154 | |||
b3b62a9c7f | |||
b77d4a9bdf | |||
b6e7fa5d13 | |||
05eeb5ae2a | |||
f41b205084 | |||
1302611731 | |||
469a5484a1 | |||
7471c03ed1 | |||
e2ea58d33a | |||
a3f44cf678 | |||
69caf0b5bc | |||
01cefa6ea1 | |||
1092319f6e | |||
f8df50934c | |||
7f6ebb89c0 | |||
c022e0d320 | |||
b6abcf252a | |||
55c0b1eb6b | |||
8daf566eb2 | |||
533ef48393 | |||
8533a892bf | |||
Rafael Caricio | ad3ea0e7ca | ||
Rafael Caricio | 83286b7522 | ||
Rafael Caricio | c5dbb0257f | ||
Rafael Caricio | c0049e6d49 | ||
Rafael Caricio | e5be1326de | ||
Rafael Caricio | 89e3f93592 | ||
Rafael Caricio | 5ef024d923 |
21
.dockerignore
Normal file
21
.dockerignore
Normal file
|
@ -0,0 +1,21 @@
|
|||
# flyctl launch added from .gitignore
|
||||
**/.env.local
|
||||
**/config.yaml
|
||||
target
|
||||
|
||||
# other things
|
||||
docs/*
|
||||
fedimovies-*
|
||||
scripts/*
|
||||
src/*
|
||||
|
||||
# flyctl launch added from .idea/.gitignore
|
||||
# Default ignored files
|
||||
.idea/shelf
|
||||
.idea/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
.idea/httpRequests
|
||||
# Datasource local storage ignored files
|
||||
.idea/dataSources
|
||||
.idea/dataSources.local.xml
|
||||
fly.toml
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,5 +1,9 @@
|
|||
.env.local
|
||||
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
|
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -6,7 +6,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.20.0] - 2023-03-07
|
||||
## [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
|
||||
|
||||
|
|
2094
Cargo.lock
generated
2094
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
55
Cargo.toml
55
Cargo.toml
|
@ -1,39 +1,39 @@
|
|||
[package]
|
||||
name = "mitra"
|
||||
version = "1.20.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 = [
|
||||
".",
|
||||
"mitra-cli",
|
||||
"mitra-config",
|
||||
"mitra-models",
|
||||
"mitra-utils",
|
||||
"fedimovies-cli",
|
||||
"fedimovies-config",
|
||||
"fedimovies-models",
|
||||
"fedimovies-utils",
|
||||
]
|
||||
default-members = [
|
||||
".",
|
||||
"mitra-cli",
|
||||
"mitra-config",
|
||||
"mitra-models",
|
||||
"mitra-utils",
|
||||
"fedimovies-cli",
|
||||
"fedimovies-config",
|
||||
"fedimovies-models",
|
||||
"fedimovies-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
mitra-config = { path = "mitra-config" }
|
||||
mitra-models = { path = "mitra-models" }
|
||||
mitra-utils = { path = "mitra-utils" }
|
||||
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 catching errors
|
||||
anyhow = "1.0.58"
|
||||
|
@ -50,8 +50,6 @@ env_logger = { version = "0.9.0", default-features = false }
|
|||
ed25519-dalek = "1.0.1"
|
||||
ed25519 = "1.5.3"
|
||||
blake2 = "0.10.5"
|
||||
# Used to query Monero node
|
||||
monero-rpc = "0.3.2"
|
||||
# Used to determine the number of CPUs on the system
|
||||
num_cpus = "1.13.0"
|
||||
# Used for working with regular expressions
|
||||
|
@ -60,8 +58,6 @@ regex = "1.6.0"
|
|||
reqwest = { version = "0.11.13", features = ["json", "multipart", "socks"] }
|
||||
# Used for working with RSA keys
|
||||
rsa = "0.5.0"
|
||||
# Used for working with ethereum keys
|
||||
secp256k1 = { version = "0.21.3", features = ["rand", "rand-std"] }
|
||||
# Used for serialization/deserialization
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.89"
|
||||
|
@ -72,23 +68,18 @@ siwe = "0.4.0"
|
|||
# Used for creating error types
|
||||
thiserror = "1.0.37"
|
||||
# Async runtime
|
||||
tokio = { version = "1.20.4", features = ["macros"] }
|
||||
tokio = { version = "=1.20.4", features = ["macros"] }
|
||||
# Used to work with URLs
|
||||
url = "2.2.2"
|
||||
# Used to work with UUIDs
|
||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||
# Used to query ethereum node
|
||||
web3 = { version = "0.18.0", default-features = false, features = ["http", "http-tls", "signing"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mitra-config = { path = "mitra-config", features = ["test-utils"] }
|
||||
mitra-models = { path = "mitra-models", features = ["test-utils"] }
|
||||
mitra-utils = { path = "mitra-utils", features = ["test-utils"] }
|
||||
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]
|
||||
ethereum-extras = []
|
||||
fep-e232 = []
|
||||
|
||||
production = ["mitra-config/production"]
|
||||
production = ["fedimovies-config/production"]
|
||||
|
|
89
README.md
89
README.md
|
@ -1,6 +1,7 @@
|
|||
# Mitra
|
||||
# FediMovies
|
||||
[![status-badge](https://ci.caric.io/api/badges/FediMovies/fedimovies/status.svg)](https://ci.caric.io/FediMovies/fedimovies)
|
||||
|
||||
Federated micro-blogging platform.
|
||||
Lively federated movies reviews platform.
|
||||
|
||||
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||
|
||||
|
@ -8,41 +9,30 @@ Features:
|
|||
|
||||
- Micro-blogging service (includes support for quote posts, custom emojis and more).
|
||||
- Mastodon API.
|
||||
- Content subscription service. Subscriptions provide a way to receive monthly payments from subscribers and to publish private content made exclusively for them.
|
||||
- Supported payment methods: [Monero](https://www.getmonero.org/get-started/what-is-monero/) and [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens (on Ethereum and other EVM-compatible blockchains).
|
||||
- [Sign-in with a wallet](https://eips.ethereum.org/EIPS/eip-4361).
|
||||
- Donation buttons.
|
||||
- Account migrations (from one server to another). Identity can be detached from the server.
|
||||
- Federation over Tor.
|
||||
|
||||
Follow: [@mitra@mitra.social](https://mitra.social/@mitra)
|
||||
|
||||
Matrix chat: [#mitra:halogen.city](https://matrix.to/#/#mitra:halogen.city)
|
||||
|
||||
## Instances
|
||||
|
||||
- [FediList](http://demo.fedilist.com/instance?software=mitra)
|
||||
- [Fediverse Observer](https://mitra.fediverse.observer/list)
|
||||
- [FediList](http://demo.fedilist.com/instance?software=fedimovies)
|
||||
- [Fediverse Observer](https://fedimovies.fediverse.observer/list)
|
||||
|
||||
Demo instance: https://public.mitra.social/ ([invite-only](https://public.mitra.social/about))
|
||||
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
|
||||
|
||||
## Code
|
||||
|
||||
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+
|
||||
|
||||
Optional:
|
||||
|
||||
- Monero node and Monero wallet service
|
||||
- Ethereum node
|
||||
- IPFS node (see [guide](./docs/ipfs.md))
|
||||
|
||||
## Installation
|
||||
|
@ -55,63 +45,58 @@ 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).
|
||||
|
||||
### Tor federation
|
||||
|
||||
See [guide](./docs/onion.md).
|
||||
|
||||
### Blockchain integrations
|
||||
|
||||
- [Monero](./docs/monero.md)
|
||||
- [Ethereum](./docs/ethereum.md)
|
||||
|
||||
## Development
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
|
@ -125,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
|
||||
|
@ -153,7 +130,7 @@ cargo run
|
|||
### Run CLI
|
||||
|
||||
```shell
|
||||
cargo run --bin mitractl
|
||||
cargo run --bin fedimoviesctl
|
||||
```
|
||||
|
||||
### Run linter
|
||||
|
@ -174,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
|
@ -14,29 +14,5 @@ instance_description: My instance
|
|||
registration:
|
||||
type: open
|
||||
|
||||
blockchains:
|
||||
# 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
|
||||
account_index: 0
|
||||
# # 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
|
||||
|
||||
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"]
|
|
@ -56,32 +56,6 @@ retention:
|
|||
# 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
|
||||
# account_index: 0
|
||||
# - 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)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# https://monerodocs.org/interacting/monero-wallet-rpc-reference/
|
||||
|
||||
daemon-address=http://example.tld:18081
|
||||
|
||||
untrusted-daemon=1
|
||||
non-interactive=1
|
||||
|
||||
rpc-bind-port=18082
|
||||
disable-rpc-login=1
|
||||
|
||||
wallet-dir=/var/lib/monero-wallet/wallet
|
||||
|
||||
log-file=/var/lib/monero-wallet/wallet-rpc.log
|
||||
log-level=0
|
||||
max-log-file-size=10000000
|
||||
max-log-files=50
|
|
@ -1,7 +0,0 @@
|
|||
# Ethereum integration
|
||||
|
||||
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.
|
|
@ -32,6 +32,12 @@ List generated invites:
|
|||
mitractl list-invite-codes
|
||||
```
|
||||
|
||||
Create user:
|
||||
|
||||
```shell
|
||||
mitractl create-user <username> <password> <role-name>
|
||||
```
|
||||
|
||||
Set or change password:
|
||||
|
||||
```shell
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# Monero integration
|
||||
|
||||
Install Monero node or choose a [public one](https://monero.fail/).
|
||||
|
||||
Install and configure [monero-wallet-rpc](https://monerodocs.org/interacting/monero-wallet-rpc-reference/) service. Add `disable-rpc-login=1` to your `monero-wallet-rpc` configuration file (currently RPC auth is not supported in Mitra). See [example](../contrib/monero/wallet.conf).
|
||||
|
||||
Start `monero-wallet-rpc`. Create a wallet for your instance:
|
||||
|
||||
```
|
||||
mitractl create-monero-wallet "mitra-wallet" "passw0rd"
|
||||
```
|
||||
|
||||
Add blockchain configuration to `blockchains` array in your configuration file.
|
|
@ -959,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
|
||||
|
@ -1907,6 +1911,10 @@ 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
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
[package]
|
||||
name = "mitra-cli"
|
||||
version = "1.20.0"
|
||||
name = "fedimovies-cli"
|
||||
version = "1.22.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.68"
|
||||
|
||||
[[bin]]
|
||||
name = "mitractl"
|
||||
name = "fedimoviesctl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
mitra-config = { path = "../mitra-config" }
|
||||
mitra-models = { path = "../mitra-models" }
|
||||
mitra-utils = { path = "../mitra-utils" }
|
||||
mitra = { path = ".." }
|
||||
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"
|
|
@ -1,64 +1,39 @@
|
|||
use anyhow::{anyhow, Error};
|
||||
use anyhow::Error;
|
||||
use clap::Parser;
|
||||
use uuid::Uuid;
|
||||
|
||||
use mitra::activitypub::{
|
||||
actors::helpers::update_remote_profile,
|
||||
builders::delete_note::prepare_delete_note,
|
||||
builders::delete_person::prepare_delete_person,
|
||||
fetcher::fetchers::fetch_actor,
|
||||
use fedimovies::activitypub::{
|
||||
actors::helpers::update_remote_profile, builders::delete_note::prepare_delete_note,
|
||||
builders::delete_person::prepare_delete_person, fetcher::fetchers::fetch_actor,
|
||||
fetcher::helpers::import_from_outbox,
|
||||
};
|
||||
use mitra::ethereum::{
|
||||
signatures::generate_ecdsa_key,
|
||||
sync::save_current_block_number,
|
||||
utils::key_to_ethereum_address,
|
||||
};
|
||||
use mitra::media::{
|
||||
remove_files,
|
||||
remove_media,
|
||||
MediaStorage,
|
||||
};
|
||||
use mitra::monero::{
|
||||
helpers::check_expired_invoice,
|
||||
wallet::create_monero_wallet,
|
||||
};
|
||||
use mitra::validators::emojis::EMOJI_LOCAL_MAX_SIZE;
|
||||
use mitra_config::Config;
|
||||
use mitra_models::{
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
get_invite_codes,
|
||||
get_user_by_id,
|
||||
set_user_password,
|
||||
create_invite_code, create_user, get_invite_codes, get_user_by_id, set_user_password,
|
||||
set_user_role,
|
||||
},
|
||||
users::types::Role,
|
||||
users::types::UserCreateData,
|
||||
};
|
||||
use mitra_utils::{
|
||||
crypto_rsa::{
|
||||
generate_rsa_key,
|
||||
serialize_private_key,
|
||||
},
|
||||
use fedimovies_utils::{
|
||||
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||
datetime::{days_before_now, get_min_datetime},
|
||||
passwords::hash_password,
|
||||
};
|
||||
|
@ -77,9 +52,11 @@ pub enum SubCommand {
|
|||
|
||||
GenerateInviteCode(GenerateInviteCode),
|
||||
ListInviteCodes(ListInviteCodes),
|
||||
CreateUser(CreateUser),
|
||||
SetPassword(SetPassword),
|
||||
SetRole(SetRole),
|
||||
RefetchActor(RefetchActor),
|
||||
ReadOutbox(ReadOutbox),
|
||||
DeleteProfile(DeleteProfile),
|
||||
DeletePost(DeletePost),
|
||||
DeleteEmoji(DeleteEmoji),
|
||||
|
@ -114,12 +91,7 @@ pub struct GenerateEthereumAddress;
|
|||
|
||||
impl GenerateEthereumAddress {
|
||||
pub fn execute(&self) -> () {
|
||||
let private_key = generate_ecdsa_key();
|
||||
let address = key_to_ethereum_address(&private_key);
|
||||
println!(
|
||||
"address {:?}; private key {}",
|
||||
address, private_key.display_secret(),
|
||||
);
|
||||
println!("dummy");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,14 +102,8 @@ pub struct GenerateInviteCode {
|
|||
}
|
||||
|
||||
impl GenerateInviteCode {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let invite_code = create_invite_code(
|
||||
db_client,
|
||||
self.note.as_deref(),
|
||||
).await?;
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let invite_code = create_invite_code(db_client, self.note.as_deref()).await?;
|
||||
println!("generated invite code: {}", invite_code);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -148,10 +114,7 @@ impl GenerateInviteCode {
|
|||
pub struct ListInviteCodes;
|
||||
|
||||
impl ListInviteCodes {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let invite_codes = get_invite_codes(db_client).await?;
|
||||
if invite_codes.is_empty() {
|
||||
println!("no invite codes found");
|
||||
|
@ -163,7 +126,37 @@ impl ListInviteCodes {
|
|||
} 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(())
|
||||
}
|
||||
}
|
||||
|
@ -176,10 +169,7 @@ pub struct SetPassword {
|
|||
}
|
||||
|
||||
impl SetPassword {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||
let password_hash = hash_password(&self.password)?;
|
||||
set_user_password(db_client, &self.id, password_hash).await?;
|
||||
// Revoke all sessions
|
||||
|
@ -193,21 +183,13 @@ impl SetPassword {
|
|||
#[derive(Parser)]
|
||||
pub struct SetRole {
|
||||
id: Uuid,
|
||||
#[clap(value_parser = ["admin", "user", "read_only_user"])]
|
||||
#[clap(value_parser = ALLOWED_ROLES)]
|
||||
role: String,
|
||||
}
|
||||
|
||||
impl SetRole {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let role = match self.role.as_str() {
|
||||
"user" => Role::NormalUser,
|
||||
"admin" => Role::Admin,
|
||||
"read_only_user" => Role::ReadOnlyUser,
|
||||
_ => return Err(anyhow!("unknown role")),
|
||||
};
|
||||
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(())
|
||||
|
@ -226,10 +208,7 @@ impl RefetchActor {
|
|||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let profile = get_profile_by_remote_actor_id(
|
||||
db_client,
|
||||
&self.id,
|
||||
).await?;
|
||||
let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?;
|
||||
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
||||
update_remote_profile(
|
||||
db_client,
|
||||
|
@ -237,12 +216,32 @@ impl RefetchActor {
|
|||
&MediaStorage::from(config),
|
||||
profile,
|
||||
actor,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
println!("profile updated");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pull activities from actor's outbox
|
||||
#[derive(Parser)]
|
||||
pub struct ReadOutbox {
|
||||
actor_id: String,
|
||||
#[clap(long, default_value_t = 5)]
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl ReadOutbox {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &mut impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
import_from_outbox(config, db_client, &self.actor_id, self.limit).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete profile
|
||||
#[derive(Parser)]
|
||||
pub struct DeleteProfile {
|
||||
|
@ -259,8 +258,7 @@ impl DeleteProfile {
|
|||
let mut maybe_delete_person = None;
|
||||
if profile.is_local() {
|
||||
let user = get_user_by_id(db_client, &profile.id).await?;
|
||||
let activity =
|
||||
prepare_delete_person(db_client, &config.instance(), &user).await?;
|
||||
let activity = prepare_delete_person(db_client, &config.instance(), &user).await?;
|
||||
maybe_delete_person = Some(activity);
|
||||
};
|
||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||
|
@ -290,12 +288,8 @@ impl DeletePost {
|
|||
let mut maybe_delete_note = None;
|
||||
if post.author.is_local() {
|
||||
let author = get_user_by_id(db_client, &post.author.id).await?;
|
||||
let activity = prepare_delete_note(
|
||||
db_client,
|
||||
&config.instance(),
|
||||
&author,
|
||||
&post,
|
||||
).await?;
|
||||
let activity =
|
||||
prepare_delete_note(db_client, &config.instance(), &author, &post).await?;
|
||||
maybe_delete_note = Some(activity);
|
||||
};
|
||||
let deletion_queue = delete_post(db_client, &post.id).await?;
|
||||
|
@ -322,11 +316,8 @@ impl DeleteEmoji {
|
|||
config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let emoji = get_emoji_by_name(
|
||||
db_client,
|
||||
&self.emoji_name,
|
||||
self.hostname.as_deref(),
|
||||
).await?;
|
||||
let emoji =
|
||||
get_emoji_by_name(db_client, &self.emoji_name, self.hostname.as_deref()).await?;
|
||||
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("emoji deleted");
|
||||
|
@ -352,7 +343,7 @@ impl DeleteExtraneousPosts {
|
|||
let deletion_queue = delete_post(db_client, &post_id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("post {} deleted", post_id);
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -370,10 +361,7 @@ impl DeleteUnusedAttachments {
|
|||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let created_before = days_before_now(self.days);
|
||||
let deletion_queue = delete_unused_attachments(
|
||||
db_client,
|
||||
&created_before,
|
||||
).await?;
|
||||
let deletion_queue = delete_unused_attachments(db_client, &created_before).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("unused attachments deleted");
|
||||
Ok(())
|
||||
|
@ -393,10 +381,9 @@ impl DeleteOrphanedFiles {
|
|||
let media_dir = config.media_dir();
|
||||
let mut files = vec![];
|
||||
for maybe_path in std::fs::read_dir(&media_dir)? {
|
||||
let file_name = maybe_path?.file_name()
|
||||
.to_string_lossy().to_string();
|
||||
let file_name = maybe_path?.file_name().to_string_lossy().to_string();
|
||||
files.push(file_name);
|
||||
};
|
||||
}
|
||||
println!("found {} files", files.len());
|
||||
let orphaned = find_orphaned_files(db_client, files).await?;
|
||||
if !orphaned.is_empty() {
|
||||
|
@ -426,7 +413,7 @@ impl DeleteEmptyProfiles {
|
|||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("profile {} deleted", profile.acct);
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -446,7 +433,7 @@ impl PruneRemoteEmojis {
|
|||
let deletion_queue = delete_emoji(db_client, &emoji_id).await?;
|
||||
remove_media(config, deletion_queue).await;
|
||||
println!("emoji {} deleted", emoji_id);
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +463,7 @@ impl ListUnreachableActors {
|
|||
profile.unreachable_since.unwrap().to_string(),
|
||||
profile.updated_at.to_string(),
|
||||
);
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -494,11 +481,8 @@ impl ImportEmoji {
|
|||
_config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let emoji = get_emoji_by_name_and_hostname(
|
||||
db_client,
|
||||
&self.emoji_name,
|
||||
&self.hostname,
|
||||
).await?;
|
||||
let emoji =
|
||||
get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?;
|
||||
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
|
||||
println!("emoji is too big");
|
||||
return Ok(());
|
||||
|
@ -510,7 +494,8 @@ impl ImportEmoji {
|
|||
emoji.image,
|
||||
None,
|
||||
&get_min_datetime(),
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
println!("added emoji to local collection");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -526,9 +511,8 @@ impl UpdateCurrentBlock {
|
|||
pub async fn execute(
|
||||
&self,
|
||||
_config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
_db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
save_current_block_number(db_client, self.number).await?;
|
||||
println!("current block updated");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -563,18 +547,7 @@ pub struct CreateMoneroWallet {
|
|||
}
|
||||
|
||||
impl CreateMoneroWallet {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
) -> Result<(), Error> {
|
||||
let monero_config = config.blockchain()
|
||||
.and_then(|conf| conf.monero_config())
|
||||
.ok_or(anyhow!("monero configuration not found"))?;
|
||||
create_monero_wallet(
|
||||
monero_config,
|
||||
self.name.clone(),
|
||||
self.password.clone(),
|
||||
).await?;
|
||||
pub async fn execute(&self, _config: &Config) -> Result<(), Error> {
|
||||
println!("wallet created");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -589,17 +562,9 @@ pub struct CheckExpiredInvoice {
|
|||
impl CheckExpiredInvoice {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &Config,
|
||||
db_client: &impl DatabaseClient,
|
||||
_config: &Config,
|
||||
_db_client: &impl DatabaseClient,
|
||||
) -> Result<(), Error> {
|
||||
let monero_config = config.blockchain()
|
||||
.and_then(|conf| conf.monero_config())
|
||||
.ok_or(anyhow!("monero configuration not found"))?;
|
||||
check_expired_invoice(
|
||||
monero_config,
|
||||
db_client,
|
||||
&self.id,
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
76
fedimovies-cli/src/main.rs
Normal file
76
fedimovies-cli/src/main.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use clap::Parser;
|
||||
|
||||
use fedimovies::logger::configure_logger;
|
||||
use fedimovies_config::parse_config;
|
||||
use fedimovies_models::database::create_database_client;
|
||||
use fedimovies_models::database::migrate::apply_migrations;
|
||||
|
||||
mod cli;
|
||||
use cli::{Opts, SubCommand};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let opts: Opts = Opts::parse();
|
||||
|
||||
match opts.subcmd {
|
||||
SubCommand::GenerateRsaKey(cmd) => cmd.execute(),
|
||||
SubCommand::GenerateEthereumAddress(cmd) => cmd.execute(),
|
||||
subcmd => {
|
||||
// Other commands require initialized app
|
||||
let (config, config_warnings) = parse_config();
|
||||
configure_logger(config.log_level);
|
||||
log::info!("config loaded from {}", config.config_path);
|
||||
for warning in config_warnings {
|
||||
log::warn!("{}", warning);
|
||||
}
|
||||
|
||||
let db_config = config.database_url.parse().unwrap();
|
||||
let db_client =
|
||||
&mut create_database_client(&db_config, config.tls_ca_file.as_deref()).await;
|
||||
apply_migrations(db_client).await;
|
||||
|
||||
match subcmd {
|
||||
SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||
SubCommand::ListInviteCodes(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||
SubCommand::CreateUser(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||
SubCommand::SetPassword(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||
SubCommand::SetRole(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||
SubCommand::RefetchActor(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||
SubCommand::ReadOutbox(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||
SubCommand::DeleteProfile(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||
SubCommand::DeletePost(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||
SubCommand::DeleteEmoji(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||
SubCommand::DeleteExtraneousPosts(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::DeleteUnusedAttachments(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::DeleteOrphanedFiles(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::DeleteEmptyProfiles(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::PruneRemoteEmojis(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::ListUnreachableActors(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::ImportEmoji(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||
SubCommand::UpdateCurrentBlock(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::ResetSubscriptions(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
|
||||
SubCommand::CheckExpiredInvoice(cmd) => {
|
||||
cmd.execute(&config, db_client).await.unwrap()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "mitra-config"
|
||||
version = "1.20.0"
|
||||
name = "fedimovies-config"
|
||||
version = "1.22.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.68"
|
||||
|
||||
[dependencies]
|
||||
mitra-utils = { path = "../mitra-utils" }
|
||||
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||
|
||||
# Used to read .env files
|
||||
dotenv = "0.15.0"
|
|
@ -1,23 +1,26 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use log::{Level as LogLevel};
|
||||
use log::Level as LogLevel;
|
||||
use rsa::RsaPrivateKey;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use mitra_utils::urls::normalize_url;
|
||||
use fedimovies_utils::urls::normalize_url;
|
||||
|
||||
use super::blockchain::BlockchainConfig;
|
||||
use super::environment::Environment;
|
||||
use super::federation::FederationConfig;
|
||||
use super::limits::Limits;
|
||||
use super::registration::RegistrationConfig;
|
||||
use super::retention::RetentionConfig;
|
||||
use super::MITRA_VERSION;
|
||||
use super::REEF_VERSION;
|
||||
|
||||
fn default_log_level() -> LogLevel { LogLevel::Info }
|
||||
fn default_log_level() -> LogLevel {
|
||||
LogLevel::Info
|
||||
}
|
||||
|
||||
fn default_login_message() -> String { "Do not sign this message on other sites!".to_string() }
|
||||
fn default_login_message() -> String {
|
||||
"What?!".to_string()
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
|
@ -30,6 +33,8 @@ pub struct Config {
|
|||
|
||||
// Core settings
|
||||
pub database_url: String,
|
||||
#[serde(default)]
|
||||
pub tls_ca_file: Option<PathBuf>,
|
||||
pub storage_dir: PathBuf,
|
||||
pub web_client_dir: Option<PathBuf>,
|
||||
|
||||
|
@ -50,6 +55,11 @@ pub struct Config {
|
|||
pub instance_short_description: String,
|
||||
pub instance_description: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub tmdb_api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub movie_user_password: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(super) instance_rsa_key: Option<RsaPrivateKey>,
|
||||
|
||||
|
@ -78,12 +88,6 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub blocked_instances: Vec<String>,
|
||||
|
||||
// Blockchain integrations
|
||||
#[serde(rename = "blockchain")]
|
||||
_blockchain: Option<BlockchainConfig>, // deprecated
|
||||
#[serde(default)]
|
||||
blockchains: Vec<BlockchainConfig>,
|
||||
|
||||
// IPFS
|
||||
pub ipfs_api_url: Option<String>,
|
||||
pub ipfs_gateway_url: Option<String>,
|
||||
|
@ -100,10 +104,10 @@ impl Config {
|
|||
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),
|
||||
is_private: !self.federation.enabled,
|
||||
// || matches!(self.environment, Environment::Development),
|
||||
fetcher_timeout: self.federation.fetcher_timeout,
|
||||
deliverer_timeout: self.federation.deliverer_timeout,
|
||||
}
|
||||
|
@ -116,18 +120,6 @@ impl Config {
|
|||
pub fn media_dir(&self) -> PathBuf {
|
||||
self.storage_dir.join("media")
|
||||
}
|
||||
|
||||
pub fn blockchain(&self) -> Option<&BlockchainConfig> {
|
||||
if let Some(ref _blockchain_config) = self._blockchain {
|
||||
panic!("'blockchain' setting is not supported anymore, use 'blockchains' instead");
|
||||
} else {
|
||||
match &self.blockchains[..] {
|
||||
[blockchain_config] => Some(blockchain_config),
|
||||
[] => None,
|
||||
_ => panic!("multichain deployments are not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -138,6 +130,7 @@ pub struct Instance {
|
|||
// 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,
|
||||
|
@ -155,9 +148,9 @@ impl Instance {
|
|||
|
||||
pub fn agent(&self) -> String {
|
||||
format!(
|
||||
"Mitra {version}; {instance_url}",
|
||||
version=MITRA_VERSION,
|
||||
instance_url=self.url(),
|
||||
"Reef {version}; {instance_url}",
|
||||
version = REEF_VERSION,
|
||||
instance_url = self.url(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -165,12 +158,13 @@ impl Instance {
|
|||
#[cfg(feature = "test-utils")]
|
||||
impl Instance {
|
||||
pub fn for_test(url: &str) -> Self {
|
||||
use mitra_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
Self {
|
||||
_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,
|
||||
|
@ -180,8 +174,8 @@ impl Instance {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mitra_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
use super::*;
|
||||
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
|
||||
|
||||
#[test]
|
||||
fn test_instance_url_https_dns() {
|
||||
|
@ -192,6 +186,7 @@ mod tests {
|
|||
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,
|
||||
|
@ -201,7 +196,7 @@ mod tests {
|
|||
assert_eq!(instance.hostname(), "example.com");
|
||||
assert_eq!(
|
||||
instance.agent(),
|
||||
format!("Mitra {}; https://example.com", MITRA_VERSION),
|
||||
format!("Mitra {}; https://example.com", REEF_VERSION),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -214,6 +209,7 @@ mod tests {
|
|||
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,
|
|
@ -10,9 +10,13 @@ pub enum Environment {
|
|||
|
||||
impl Default for Environment {
|
||||
#[cfg(feature = "production")]
|
||||
fn default() -> Self { Self::Production }
|
||||
fn default() -> Self {
|
||||
Self::Production
|
||||
}
|
||||
#[cfg(not(feature = "production"))]
|
||||
fn default() -> Self { Self::Development }
|
||||
fn default() -> Self {
|
||||
Self::Development
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Environment {
|
|
@ -1,9 +1,15 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
fn default_federation_enabled() -> bool { true }
|
||||
fn default_federation_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
const fn default_fetcher_timeout() -> u64 { 300 }
|
||||
const fn default_deliverer_timeout() -> u64 { 30 }
|
||||
const fn default_fetcher_timeout() -> u64 {
|
||||
300
|
||||
}
|
||||
const fn default_deliverer_timeout() -> u64 {
|
||||
30
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct FederationConfig {
|
||||
|
@ -15,6 +21,7 @@ pub struct FederationConfig {
|
|||
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 {
|
||||
|
@ -25,6 +32,7 @@ impl Default for FederationConfig {
|
|||
deliverer_timeout: default_deliverer_timeout(),
|
||||
proxy_url: None,
|
||||
onion_proxy_url: None,
|
||||
i2p_proxy_url: None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
mod blockchain;
|
||||
mod config;
|
||||
mod environment;
|
||||
mod federation;
|
||||
|
@ -7,17 +6,12 @@ mod loader;
|
|||
mod registration;
|
||||
mod retention;
|
||||
|
||||
pub use blockchain::{
|
||||
BlockchainConfig,
|
||||
EthereumConfig,
|
||||
MoneroConfig,
|
||||
};
|
||||
pub use config::{Config, Instance};
|
||||
pub use environment::Environment;
|
||||
pub use loader::parse_config;
|
||||
pub use registration::{DefaultRole, RegistrationType};
|
||||
|
||||
pub const MITRA_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const REEF_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("{0}")]
|
|
@ -1,19 +1,17 @@
|
|||
use regex::Regex;
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Deserializer,
|
||||
de::{Error as DeserializerError},
|
||||
};
|
||||
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)
|
||||
let file_size_re = Regex::new(FILE_SIZE_RE).expect("regexp should be valid");
|
||||
let caps = file_size_re
|
||||
.captures(value)
|
||||
.ok_or(ConfigError("invalid file size"))?;
|
||||
let size: usize = caps["size"].to_string().parse()
|
||||
let size: usize = caps["size"]
|
||||
.to_string()
|
||||
.parse()
|
||||
.map_err(|_| ConfigError("invalid file size"))?;
|
||||
let unit = caps["unit"].to_string().to_lowercase();
|
||||
let multiplier = match unit.as_str() {
|
||||
|
@ -26,31 +24,33 @@ fn parse_file_size(value: &str) -> Result<usize, ConfigError> {
|
|||
Ok(size * multiplier)
|
||||
}
|
||||
|
||||
fn deserialize_file_size<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<usize, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
fn deserialize_file_size<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let file_size_str = String::deserialize(deserializer)?;
|
||||
let file_size = parse_file_size(&file_size_str)
|
||||
.map_err(DeserializerError::custom)?;
|
||||
let file_size = parse_file_size(&file_size_str).map_err(DeserializerError::custom)?;
|
||||
Ok(file_size)
|
||||
}
|
||||
|
||||
const fn default_file_size_limit() -> usize { 20_000_000 } // 20 MB
|
||||
const fn default_emoji_size_limit() -> usize { 500_000 } // 500 kB
|
||||
const fn default_file_size_limit() -> usize {
|
||||
20_000_000
|
||||
} // 20 MB
|
||||
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",
|
||||
deserialize_with = "deserialize_file_size"
|
||||
)]
|
||||
pub file_size_limit: usize,
|
||||
|
||||
#[serde(
|
||||
default = "default_emoji_size_limit",
|
||||
deserialize_with = "deserialize_file_size",
|
||||
deserialize_with = "deserialize_file_size"
|
||||
)]
|
||||
pub emoji_size_limit: usize,
|
||||
}
|
||||
|
@ -64,7 +64,9 @@ impl Default for MediaLimits {
|
|||
}
|
||||
}
|
||||
|
||||
const fn default_post_character_limit() -> usize { 2000 }
|
||||
const fn default_post_character_limit() -> usize {
|
||||
2000
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct PostLimits {
|
|
@ -4,12 +4,8 @@ use std::str::FromStr;
|
|||
|
||||
use rsa::RsaPrivateKey;
|
||||
|
||||
use mitra_utils::{
|
||||
crypto_rsa::{
|
||||
deserialize_private_key,
|
||||
generate_rsa_key,
|
||||
serialize_private_key,
|
||||
},
|
||||
use fedimovies_utils::{
|
||||
crypto_rsa::{deserialize_private_key, generate_rsa_key, serialize_private_key},
|
||||
files::{set_file_permissions, write_file},
|
||||
};
|
||||
|
||||
|
@ -23,16 +19,16 @@ struct EnvConfig {
|
|||
}
|
||||
|
||||
#[cfg(feature = "production")]
|
||||
const DEFAULT_CONFIG_PATH: &str = "/etc/mitra/config.yaml";
|
||||
const DEFAULT_CONFIG_PATH: &str = "/etc/fedimovies/config.yaml";
|
||||
#[cfg(not(feature = "production"))]
|
||||
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()
|
||||
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,
|
||||
|
@ -45,8 +41,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
fn check_directory_owner(path: &Path) -> () {
|
||||
let metadata = std::fs::metadata(path)
|
||||
.expect("can't read file metadata");
|
||||
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 {
|
||||
|
@ -63,16 +58,15 @@ fn check_directory_owner(path: &Path) -> () {
|
|||
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");
|
||||
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");
|
||||
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)
|
||||
|
@ -83,10 +77,9 @@ fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
|||
|
||||
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 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
|
||||
|
@ -102,14 +95,6 @@ pub fn parse_config() -> (Config, Vec<&'static str>) {
|
|||
};
|
||||
check_directory_owner(&config.storage_dir);
|
||||
config.try_instance_url().expect("invalid instance URI");
|
||||
if let Some(blockchain_config) = config.blockchain() {
|
||||
if let Some(ethereum_config) = blockchain_config.ethereum_config() {
|
||||
ethereum_config.try_ethereum_chain_id().unwrap();
|
||||
if !ethereum_config.contract_dir.exists() {
|
||||
panic!("contract directory does not exist");
|
||||
};
|
||||
};
|
||||
};
|
||||
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
||||
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
||||
};
|
||||
|
@ -117,7 +102,8 @@ pub fn parse_config() -> (Config, Vec<&'static str>) {
|
|||
// Migrations
|
||||
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");
|
||||
warnings
|
||||
.push("'registrations_open' setting is deprecated, use 'registration.type' instead");
|
||||
if registrations_open {
|
||||
config.registration.registration_type = RegistrationType::Open;
|
||||
} else {
|
|
@ -1,8 +1,4 @@
|
|||
use serde::{
|
||||
Deserialize,
|
||||
Deserializer,
|
||||
de::Error as DeserializerError,
|
||||
};
|
||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum RegistrationType {
|
||||
|
@ -11,12 +7,15 @@ pub enum RegistrationType {
|
|||
}
|
||||
|
||||
impl Default for RegistrationType {
|
||||
fn default() -> Self { Self::Invite }
|
||||
fn default() -> Self {
|
||||
Self::Invite
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RegistrationType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let registration_type_str = String::deserialize(deserializer)?;
|
||||
let registration_type = match registration_type_str.as_str() {
|
||||
|
@ -35,12 +34,15 @@ pub enum DefaultRole {
|
|||
}
|
||||
|
||||
impl Default for DefaultRole {
|
||||
fn default() -> Self { Self::NormalUser }
|
||||
fn default() -> Self {
|
||||
Self::NormalUser
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DefaultRole {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let role_str = String::deserialize(deserializer)?;
|
||||
let role = match role_str.as_str() {
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "mitra-models"
|
||||
version = "1.20.0"
|
||||
name = "fedimovies-models"
|
||||
version = "1.22.0"
|
||||
license = "AGPL-3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.68"
|
||||
|
||||
[dependencies]
|
||||
mitra-utils = { path = "../mitra-utils" }
|
||||
fedimovies-utils = { path = "../fedimovies-utils" }
|
||||
|
||||
# Used for working with dates
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["std", "serde"] }
|
||||
|
@ -27,6 +27,8 @@ 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"
|
||||
|
@ -37,8 +39,7 @@ postgres_query_macro = { git = "https://github.com/nolanderc/rust-postgres-query
|
|||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mitra-utils = { path = "../mitra-utils", features = ["test-utils"] }
|
||||
|
||||
fedimovies-utils = { path = "../fedimovies-utils", features = ["test-utils"] }
|
||||
serial_test = "0.7.0"
|
||||
|
||||
[features]
|
|
@ -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;
|
|
@ -116,7 +116,8 @@ 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,
|
|
@ -1,13 +1,9 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use mitra_utils::id::generate_ulid;
|
||||
use fedimovies_utils::id::generate_ulid;
|
||||
|
||||
use crate::cleanup::{
|
||||
find_orphaned_files,
|
||||
find_orphaned_ipfs_objects,
|
||||
DeletionQueue,
|
||||
};
|
||||
use crate::cleanup::{find_orphaned_files, find_orphaned_ipfs_objects, DeletionQueue};
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
|
||||
use super::types::DbMediaAttachment;
|
||||
|
@ -20,10 +16,10 @@ pub async fn create_attachment(
|
|||
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(
|
||||
"
|
||||
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,
|
||||
|
@ -34,14 +30,15 @@ pub async fn create_attachment(
|
|||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING media_attachment
|
||||
",
|
||||
&[
|
||||
&attachment_id,
|
||||
&owner_id,
|
||||
&file_name,
|
||||
&file_size,
|
||||
&media_type,
|
||||
],
|
||||
).await?;
|
||||
&[
|
||||
&attachment_id,
|
||||
&owner_id,
|
||||
&file_name,
|
||||
&file_size,
|
||||
&media_type,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
let db_attachment: DbMediaAttachment = inserted_row.try_get("media_attachment")?;
|
||||
Ok(db_attachment)
|
||||
}
|
||||
|
@ -51,15 +48,17 @@ pub async fn set_attachment_ipfs_cid(
|
|||
attachment_id: &Uuid,
|
||||
ipfs_cid: &str,
|
||||
) -> Result<DbMediaAttachment, DatabaseError> {
|
||||
let maybe_row = db_client.query_opt(
|
||||
"
|
||||
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?;
|
||||
&[&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)
|
||||
|
@ -69,14 +68,16 @@ pub async fn delete_unused_attachments(
|
|||
db_client: &impl DatabaseClient,
|
||||
created_before: &DateTime<Utc>,
|
||||
) -> Result<DeletionQueue, DatabaseError> {
|
||||
let rows = db_client.query(
|
||||
"
|
||||
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?;
|
||||
&[&created_before],
|
||||
)
|
||||
.await?;
|
||||
let mut files = vec![];
|
||||
let mut ipfs_objects = vec![];
|
||||
for row in rows {
|
||||
|
@ -85,7 +86,7 @@ pub async fn delete_unused_attachments(
|
|||
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 {
|
||||
|
@ -96,13 +97,10 @@ pub async fn delete_unused_attachments(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serial_test::serial;
|
||||
use crate::database::test_utils::create_test_database;
|
||||
use crate::profiles::{
|
||||
queries::create_profile,
|
||||
types::ProfileCreateData,
|
||||
};
|
||||
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]
|
||||
|
@ -122,11 +120,13 @@ mod tests {
|
|||
file_name.to_string(),
|
||||
file_size,
|
||||
Some(media_type.to_string()),
|
||||
).await.unwrap();
|
||||
)
|
||||
.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_eq!(attachment.post_id.is_none(), true);
|
||||
assert!(attachment.post_id.is_none());
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ impl AttachmentType {
|
|||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
},
|
||||
}
|
||||
None => Self::Unknown,
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@ use chrono::{DateTime, Utc};
|
|||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
use super::types::{DbBackgroundJob, JobStatus, JobType};
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
|
||||
pub async fn enqueue_job(
|
||||
db_client: &impl DatabaseClient,
|
||||
|
@ -12,8 +12,9 @@ pub async fn enqueue_job(
|
|||
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,
|
||||
|
@ -22,8 +23,9 @@ 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(())
|
||||
}
|
||||
|
||||
|
@ -35,8 +37,9 @@ pub async fn get_job_batch(
|
|||
) -> Result<Vec<DbBackgroundJob>, DatabaseError> {
|
||||
// https://github.com/sfackler/rust-postgres/issues/60
|
||||
let job_timeout_pg = format!("{}S", job_timeout); // interval
|
||||
let rows = db_client.query(
|
||||
"
|
||||
let rows = db_client
|
||||
.query(
|
||||
"
|
||||
UPDATE background_job
|
||||
SET
|
||||
job_status = $1,
|
||||
|
@ -62,15 +65,17 @@ pub async fn get_job_batch(
|
|||
)
|
||||
RETURNING background_job
|
||||
",
|
||||
&[
|
||||
&JobStatus::Running,
|
||||
&job_type,
|
||||
&JobStatus::Queued,
|
||||
&i64::from(batch_size),
|
||||
&job_timeout_pg,
|
||||
],
|
||||
).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)
|
||||
|
@ -80,13 +85,15 @@ pub async fn delete_job_from_queue(
|
|||
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"));
|
||||
};
|
||||
|
@ -95,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]
|
||||
|
@ -111,7 +118,9 @@ 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, 3600).await.unwrap();
|
||||
assert_eq!(batch_1.len(), 1);
|
|
@ -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::{
|
|
@ -9,8 +9,9 @@ pub async fn find_orphaned_files(
|
|||
db_client: &impl DatabaseClient,
|
||||
files: Vec<String>,
|
||||
) -> Result<Vec<String>, DatabaseError> {
|
||||
let rows = db_client.query(
|
||||
"
|
||||
let rows = db_client
|
||||
.query(
|
||||
"
|
||||
SELECT DISTINCT fname
|
||||
FROM unnest($1::text[]) AS fname
|
||||
WHERE
|
||||
|
@ -27,9 +28,11 @@ pub async fn find_orphaned_files(
|
|||
WHERE image ->> 'file_name' = fname
|
||||
)
|
||||
",
|
||||
&[&files],
|
||||
).await?;
|
||||
let orphaned_files = rows.iter()
|
||||
&[&files],
|
||||
)
|
||||
.await?;
|
||||
let orphaned_files = rows
|
||||
.iter()
|
||||
.map(|row| row.try_get("fname"))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(orphaned_files)
|
||||
|
@ -39,8 +42,9 @@ pub async fn find_orphaned_ipfs_objects(
|
|||
db_client: &impl DatabaseClient,
|
||||
ipfs_objects: Vec<String>,
|
||||
) -> Result<Vec<String>, DatabaseError> {
|
||||
let rows = db_client.query(
|
||||
"
|
||||
let rows = db_client
|
||||
.query(
|
||||
"
|
||||
SELECT DISTINCT cid
|
||||
FROM unnest($1::text[]) AS cid
|
||||
WHERE
|
||||
|
@ -51,9 +55,11 @@ pub async fn find_orphaned_ipfs_objects(
|
|||
SELECT 1 FROM post WHERE ipfs_cid = cid
|
||||
)
|
||||
",
|
||||
&[&ipfs_objects],
|
||||
).await?;
|
||||
let orphaned_ipfs_objects = rows.iter()
|
||||
&[&ipfs_objects],
|
||||
)
|
||||
.await?;
|
||||
let orphaned_ipfs_objects = rows
|
||||
.iter()
|
||||
.map(|row| row.try_get("cid"))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(orphaned_ipfs_objects)
|
|
@ -12,7 +12,7 @@ macro_rules! int_enum_from_sql {
|
|||
|
||||
postgres_types::accepts!(INT2);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! int_enum_to_sql {
|
||||
|
@ -31,7 +31,7 @@ macro_rules! int_enum_to_sql {
|
|||
postgres_types::accepts!(INT2);
|
||||
postgres_types::to_sql_checked!();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use {int_enum_from_sql, int_enum_to_sql};
|
|
@ -14,7 +14,7 @@ macro_rules! json_from_sql {
|
|||
|
||||
postgres_types::accepts!(JSON, JSONB);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implements ToSql trait for any serializable type
|
||||
|
@ -33,7 +33,7 @@ macro_rules! json_to_sql {
|
|||
postgres_types::accepts!(JSON, JSONB);
|
||||
postgres_types::to_sql_checked!();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use {json_from_sql, json_to_sql};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue