Compare commits

...

59 commits

Author SHA1 Message Date
Rafael Caricio d2075dcbd2
Apply clippy suggestions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-28 00:15:44 +02:00
Rafael Caricio 7281340423
Apply clippy suggestions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-28 00:01:24 +02:00
Rafael Caricio 406781570c
Fix fmt
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:35:08 +02:00
Rafael Caricio 205ea8c5b8
Fix fmt
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:30:41 +02:00
Rafael Caricio 9944095959
Fix CI
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:29:27 +02:00
Rafael Caricio 13d6fa25bc
Fix formatting
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:28:32 +02:00
Rafael Caricio 456d0789fb
Better CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:27:00 +02:00
Rafael Caricio f73a05439d
Remove println
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-27 23:24:50 +02:00
Rafael Caricio 4df257d024
Noticed the branch was wrong.. 🤦
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:24:08 +02:00
Rafael Caricio 44004de576
OK, run CI always
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:23:03 +02:00
Rafael Caricio 93be6f8559
Fix imports
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 23:20:59 +02:00
Rafael Caricio 3860b4780d
CI fix loop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:46:23 +02:00
Rafael Caricio de6072026b
Fix missing import in tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:45:30 +02:00
Rafael Caricio 2b44c8e20d
Run test on Rust file changes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:41:40 +02:00
Rafael Caricio d8bea2c868
Fix test db setup
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:40:28 +02:00
Rafael Caricio 5d0b03c2a3
Only run cargo when Rust code is changed
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 22:13:05 +02:00
Rafael Caricio a2bc297f0e
Update readme 2023-04-27 22:00:02 +02:00
Rafael Caricio fe8380e359
Test CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 21:27:05 +02:00
Rafael Caricio 22883798b3
Don't notify when muted 2023-04-27 21:26:18 +02:00
Rafael Caricio cb61f4a86b
Allow mute accounts 2023-04-27 13:38:49 +02:00
Rafael Caricio bb28bf800d
Make API response compatible with what IceCubes iOS client expects 2023-04-26 22:10:34 +02:00
Rafael Caricio 60a27b5b11
Make generic errors carry more details 2023-04-26 12:55:42 +02:00
Rafael Caricio 0d77557ad6
Simplify deployment workflow 2023-04-26 12:54:48 +02:00
Rafael Caricio 1e40a42524
Support database connection via SSL
This is required to use managed Postgres databases. It is necessary to
use SSL connection to the remote host as the connection goes through the
open internet.
2023-04-26 12:07:36 +02:00
Rafael Caricio b7fafe6458
Rename to Fedimovies 2023-04-25 15:49:35 +02:00
Rafael Caricio 17d8c11726
Retoot reviews 2023-04-25 13:19:04 +02:00
Rafael Caricio b049b75873
TMDB API integration 2023-04-25 11:09:30 +02:00
Rafael Caricio 272d06897a
In this server all accounts are bots 2023-04-24 18:57:48 +02:00
Rafael Caricio 47529ff703
Apply cargo fmt 2023-04-24 17:35:32 +02:00
silverpill b4ff7abbc1
Bump version 2023-04-24 16:59:37 +02:00
silverpill 5906185154
Add federation.i2p_proxy_url configuration parameter 2023-04-24 16:59:26 +02:00
silverpill b3b62a9c7f
Make onion_proxy_url override proxy_url setting if request target is onion 2023-04-24 16:59:13 +02:00
silverpill b77d4a9bdf
Add replies and reposts to outbox collection 2023-04-24 16:59:01 +02:00
silverpill b6e7fa5d13
Support integrity proofs with DataIntegrityProof type 2023-04-24 16:58:05 +02:00
silverpill 05eeb5ae2a
Deserialize actor attachments into Value array 2023-04-24 16:57:14 +02:00
silverpill f41b205084
Add support for content warnings 2023-04-24 16:56:59 +02:00
silverpill 1302611731
Update actix 2023-04-24 16:56:47 +02:00
silverpill 469a5484a1
Update openssl crate 2023-04-24 16:55:20 +02:00
silverpill 7471c03ed1
Make /api/v1/accounts/{account_id}/follow work with form-data 2023-04-24 16:53:18 +02:00
silverpill e2ea58d33a
Make activity limit in outbox fetcher adjustable 2023-04-24 16:51:51 +02:00
silverpill a3f44cf678
Ignore errors when importing activities from outbox 2023-04-24 16:50:36 +02:00
silverpill 69caf0b5bc
Bump version 2023-04-24 16:50:02 +02:00
silverpill 01cefa6ea1
Disable spam detection when importing activities from outbox 2023-04-24 16:49:52 +02:00
silverpill 1092319f6e
Fix database query error in Create activity handler 2023-04-24 16:49:39 +02:00
silverpill f8df50934c
Explain database client errors 2023-04-24 16:49:22 +02:00
silverpill 7f6ebb89c0
Add read-outbox command 2023-04-24 16:49:08 +02:00
silverpill c022e0d320
Add actor validation to Update(Note) and Undo(Follow) handlers 2023-04-24 16:46:49 +02:00
silverpill b6abcf252a
Remove unused query_params parameter from send_request 2023-04-24 16:46:37 +02:00
silverpill 55c0b1eb6b
Re-fetch object if attributedTo value doesn't match actor of Create activity 2023-04-24 16:46:22 +02:00
silverpill 8daf566eb2
Add create-user command 2023-04-24 16:26:31 +02:00
silverpill 533ef48393
Check mention and link counts when creating post 2023-04-24 16:15:45 +02:00
silverpill 8533a892bf
Add emoji count check to profile data validator 2023-04-24 16:15:21 +02:00
Rafael Caricio ad3ea0e7ca
When running locally with .example.com domain use http 2023-04-24 16:10:50 +02:00
Rafael Caricio 83286b7522
No blockchain support 2023-04-24 16:10:25 +02:00
Rafael Caricio c5dbb0257f
No eth support 2023-04-24 16:10:08 +02:00
Rafael Caricio c0049e6d49
Delete some more 2023-04-09 01:34:07 +02:00
Rafael Caricio e5be1326de
Nop 2023-04-08 21:30:21 +02:00
Rafael Caricio 89e3f93592
Rm web3 lolz 2023-04-08 21:29:03 +02:00
Rafael Caricio 5ef024d923
Fuck blockchain 2023-04-08 21:20:12 +02:00
303 changed files with 6789 additions and 11564 deletions

21
.dockerignore Normal file
View file

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

4
.gitignore vendored
View file

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

63
.woodpecker.yml Normal file
View file

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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"]

View file

@ -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
View file

View 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'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

16
contrib/Dockerfile Normal file
View file

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

View file

@ -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)

View file

@ -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

View file

@ -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.

View 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

View file

@ -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.

View 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

View file

@ -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"

View file

@ -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(())
}
}

View file

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

View file

@ -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"

View file

@ -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,

View file

@ -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 {

View file

@ -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,
}
}
}

View file

@ -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}")]

View file

@ -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 {

View file

@ -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 {

View file

@ -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() {

View file

@ -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]

View file

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

View file

@ -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,

View file

@ -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());
}
}

View file

@ -35,7 +35,7 @@ impl AttachmentType {
} else {
Self::Unknown
}
},
}
None => Self::Unknown,
}
}

View file

@ -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);

View file

@ -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::{

View file

@ -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)

View file

@ -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};

View file

@ -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