forked from jmug/stoatchat
Compare commits
24 Commits
20241226-1
...
handmade
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35cfb9a489 | ||
|
|
7297a64856 | ||
|
|
720cd46879 | ||
|
|
7d099d5ae7 | ||
|
|
8847b3c5ba | ||
|
|
eb5f5f91cd | ||
|
|
52e1f0ddde | ||
|
|
defc9ec79b | ||
|
|
7026961df4 | ||
|
|
078380d305 | ||
|
|
add3f40b23 | ||
|
|
7216e9909b | ||
|
|
a1e6a19210 | ||
|
|
5f39403ce7 | ||
|
|
e525ffe5e4 | ||
|
|
e3723d647e | ||
|
|
5f84daa9db | ||
|
|
e957af4ca3 | ||
|
|
439bacf067 | ||
|
|
fa55e88dd9 | ||
|
|
71e7fe3086 | ||
|
|
03f2e3b1bf | ||
|
|
249749e14d | ||
|
|
acc4317246 |
230
.github/workflows/docker.yaml
vendored
230
.github/workflows/docker.yaml
vendored
@@ -1,137 +1,113 @@
|
||||
name: Docker Test & Publish
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
# branches:
|
||||
# - "main"
|
||||
tags:
|
||||
- "*"
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "!.github/workflows/docker.yml"
|
||||
- ".vscode/**"
|
||||
- "doc/**"
|
||||
- ".gitignore"
|
||||
- "LICENSE"
|
||||
- "README"
|
||||
branches:
|
||||
- "handmade"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "Dockerfile"
|
||||
workflow_dispatch:
|
||||
- "handmade"
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
base:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build base image
|
||||
steps:
|
||||
# Configure build environment
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
# Builds the shared base image: the whole Rust workspace compiled once.
|
||||
# Every service image (currently only pushd) is derived from this so the
|
||||
# workspace is built a single time per release. On non-release events this
|
||||
# still runs as a compile check, but nothing is pushed.
|
||||
base:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Resolve registry host and owner
|
||||
id: registry
|
||||
# Owner is lowercased: Docker repository names must be lowercase.
|
||||
run: |
|
||||
echo "host=${GITHUB_SERVER_URL#*://}" >> "$GITHUB_OUTPUT"
|
||||
echo "owner=$(echo "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
|
||||
- name: Base image ref
|
||||
id: base
|
||||
run: echo "image=${{ steps.registry.outputs.host }}/${{ steps.registry.outputs.owner }}/handmade-revolt-backend-base" >> "$GITHUB_OUTPUT"
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
registry: ${{ steps.registry.outputs.host }}
|
||||
username: ${{ secrets.PACKAGE_PUBLISH_USERNAME }}
|
||||
password: ${{ secrets.PACKAGE_PUBLISH_TOKEN }}
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
platforms: linux/amd64
|
||||
# Pinned to the commit so the publish job below consumes this
|
||||
# exact base.
|
||||
tags: |
|
||||
${{ steps.base.outputs.image }}:latest
|
||||
${{ steps.base.outputs.image }}:${{ github.sha }}
|
||||
|
||||
# Authenticate with GHCR
|
||||
- name: Login to Github Container Registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ghcr.io/${{ github.repository_owner }}/base:latest
|
||||
cache-from: type=gha,scope=buildx-base-multi-arch
|
||||
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
|
||||
|
||||
publish:
|
||||
needs: [base]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
project: [delta, bonfire, autumn, january, pushd]
|
||||
name: Build ${{ matrix.project }} image
|
||||
steps:
|
||||
# Configure build environment
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# Authenticate with Docker Hub and GHCR
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Resolve the correct project
|
||||
- uses: kanga333/variable-mapper@master
|
||||
id: export
|
||||
with:
|
||||
key: "${{ matrix.project }}"
|
||||
map: |
|
||||
{
|
||||
"delta": {
|
||||
"path": "crates/delta",
|
||||
"tag": "${{ github.repository_owner }}/server"
|
||||
},
|
||||
"bonfire": {
|
||||
"path": "crates/bonfire",
|
||||
"tag": "${{ github.repository_owner }}/bonfire"
|
||||
},
|
||||
"autumn": {
|
||||
"path": "crates/services/autumn",
|
||||
"tag": "${{ github.repository_owner }}/autumn"
|
||||
},
|
||||
"january": {
|
||||
"path": "crates/services/january",
|
||||
"tag": "${{ github.repository_owner }}/january"
|
||||
},
|
||||
"pushd": {
|
||||
"path": "crates/daemons/pushd",
|
||||
"tag": "${{ github.repository_owner }}/pushd"
|
||||
}
|
||||
}
|
||||
export_to: output
|
||||
|
||||
# Configure metadata
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
docker.io/${{ steps.export.outputs.tag }}
|
||||
ghcr.io/${{ steps.export.outputs.tag }}
|
||||
|
||||
# Build crate image
|
||||
- name: Publish
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: ${{ steps.export.outputs.path }}/Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
# Builds individual service images from the base. Only runs on release.
|
||||
publish:
|
||||
needs: [base]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { project: pushd, path: crates/daemons/pushd, image: handmade-revolt-backend-pushd }
|
||||
- { project: delta, path: crates/delta, image: handmade-revolt-backend-server }
|
||||
- { project: bonfire, path: crates/bonfire, image: handmade-revolt-backend-bonfire }
|
||||
- { project: autumn, path: crates/services/autumn, image: handmade-revolt-backend-autumn }
|
||||
- { project: january, path: crates/services/january, image: handmade-revolt-backend-january }
|
||||
- { project: crond, path: crates/daemons/crond, image: handmade-revolt-backend-crond }
|
||||
name: Build ${{ matrix.project }} image
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Resolve registry host and owner
|
||||
id: registry
|
||||
# Owner is lowercased: Docker repository names must be lowercase,
|
||||
# but the org (e.g. HMC) may be uppercase.
|
||||
run: |
|
||||
echo "host=${GITHUB_SERVER_URL#*://}" >> "$GITHUB_OUTPUT"
|
||||
echo "owner=$(echo "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ steps.registry.outputs.host }}/${{ steps.registry.outputs.owner }}/${{ matrix.image }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest
|
||||
env:
|
||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ steps.registry.outputs.host }}
|
||||
username: ${{ secrets.PACKAGE_PUBLISH_USERNAME }}
|
||||
password: ${{ secrets.PACKAGE_PUBLISH_TOKEN }}
|
||||
- name: Build and publish
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.path }}/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ steps.registry.outputs.host }}/${{ steps.registry.outputs.owner }}/handmade-revolt-backend-base:${{ github.sha }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
Rocket.toml
|
||||
Revolt.*.toml
|
||||
compose.override.yml
|
||||
|
||||
target
|
||||
.data
|
||||
|
||||
46
Cargo.lock
generated
46
Cargo.lock
generated
@@ -2410,9 +2410,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -2441,9 +2441,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.21"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -5523,7 +5523,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-autumn"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-macros",
|
||||
@@ -5560,7 +5560,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"async-channel 2.3.1",
|
||||
"async-std",
|
||||
@@ -5590,7 +5590,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-config"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"cached",
|
||||
@@ -5604,9 +5604,21 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revolt-crond"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"revolt-config",
|
||||
"revolt-database",
|
||||
"revolt-files",
|
||||
"revolt-result",
|
||||
"tokio 1.40.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revolt-database"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"amqprs",
|
||||
"async-lock 2.8.0",
|
||||
@@ -5655,7 +5667,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-delta"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"amqprs",
|
||||
"async-channel 1.6.1",
|
||||
@@ -5702,7 +5714,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-files"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"aws-config",
|
||||
@@ -5725,7 +5737,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-january"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"axum",
|
||||
@@ -5753,7 +5765,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-models"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"iso8601-timestamp 0.2.11",
|
||||
@@ -5771,7 +5783,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-nodejs-bindings"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"neon",
|
||||
@@ -5784,7 +5796,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-permissions"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -5799,7 +5811,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-presence"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"log",
|
||||
@@ -5810,7 +5822,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-pushd"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"amqprs",
|
||||
"async-trait",
|
||||
@@ -5834,7 +5846,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-result"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"revolt_okapi",
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -7,7 +7,7 @@ members = [
|
||||
"crates/core/*",
|
||||
"crates/services/*",
|
||||
"crates/bindings/*",
|
||||
"crates/daemons/pushd",
|
||||
"crates/daemons/*",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
@@ -16,5 +16,9 @@ redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/rev
|
||||
# authifier = { package = "authifier", version = "1.0.8", path = "../authifier/crates/authifier" }
|
||||
# rocket_authifier = { package = "rocket_authifier", version = "1.0.8", path = "../authifier/crates/rocket_authifier" }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
# I'm 99% sure this is overloading the GitHub worker
|
||||
# hence builds have been failing since, let's just
|
||||
# disable it for now. In the future, we could use this
|
||||
# if we were rolling our own CI.
|
||||
# [profile.release]
|
||||
# lto = true
|
||||
|
||||
@@ -29,6 +29,7 @@ COPY crates/core/presence/Cargo.toml ./crates/core/presence/
|
||||
COPY crates/core/result/Cargo.toml ./crates/core/result/
|
||||
COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/
|
||||
COPY crates/services/january/Cargo.toml ./crates/services/january/
|
||||
COPY crates/daemons/crond/Cargo.toml ./crates/daemons/crond/
|
||||
COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/
|
||||
RUN sh /tmp/build-image-layer.sh deps
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ COPY crates/core/presence/Cargo.toml ./crates/core/presence/
|
||||
COPY crates/core/result/Cargo.toml ./crates/core/result/
|
||||
COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/
|
||||
COPY crates/services/january/Cargo.toml ./crates/services/january/
|
||||
COPY crates/daemons/crond/Cargo.toml ./crates/daemons/crond/
|
||||
COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/
|
||||
RUN sh /tmp/build-image-layer.sh deps
|
||||
|
||||
|
||||
35
README.md
35
README.md
@@ -26,6 +26,7 @@ The services and libraries that power the Revolt service.<br/>
|
||||
| `services/january` | [crates/services/january](crates/services/january) | Proxy server |  |
|
||||
| `services/autumn` | [crates/services/autumn](crates/services/autumn) | File server |  |
|
||||
| `bindings/node` | [crates/bindings/node](crates/bindings/node) | Node.js bindings for the Revolt software |  |
|
||||
| `daemons/crond` | [crates/daemons/crond](crates/daemons/crond) | Timed data clean up daemon server |  |
|
||||
| `daemons/pushd` | [crates/daemons/pushd](crates/daemons/pushd) | Push notification daemon server |  |
|
||||
|
||||
</div>
|
||||
@@ -61,7 +62,7 @@ As a heads-up, the development environment uses the following ports:
|
||||
| MinIO | 14009 |
|
||||
| Maildev | 14025<br>14080 |
|
||||
| Revolt Web App | 14701 |
|
||||
| RabbitMQ | 5672<br>15672 |
|
||||
| RabbitMQ | 5672<br>15672 |
|
||||
| `crates/delta` | 14702 |
|
||||
| `crates/bonfire` | 14703 |
|
||||
| `crates/services/autumn` | 14704 |
|
||||
@@ -91,6 +92,38 @@ If you'd like to change anything, create a `Revolt.overrides.toml` file and spec
|
||||
> proxy = "https://abc@your.sentry/1"
|
||||
> ```
|
||||
|
||||
> [!TIP]
|
||||
> If you have port conflicts on common services, you can try the following:
|
||||
>
|
||||
> ```yaml
|
||||
> # compose.override.yml
|
||||
> services:
|
||||
> redis:
|
||||
> ports: !override
|
||||
> - "14079:6379"
|
||||
>
|
||||
> database:
|
||||
> ports: !override
|
||||
> - "14017:27017"
|
||||
>
|
||||
> rabbit:
|
||||
> ports: !override
|
||||
> - "14072:5672"
|
||||
> - "14672:15672"
|
||||
> ```
|
||||
>
|
||||
> And corresponding Revolt configuration:
|
||||
>
|
||||
> ```toml
|
||||
> # Revolt.overrides.toml
|
||||
> [database]
|
||||
> mongodb = "mongodb://127.0.0.1:14017"
|
||||
> redis = "redis://127.0.0.1:14079/"
|
||||
>
|
||||
> [rabbit]
|
||||
> port = 14072
|
||||
> ```
|
||||
|
||||
Then continue:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
[database]
|
||||
# MongoDB connection URL
|
||||
# Defaults to the container name specified in self-hosted
|
||||
mongodb = "mongodb://127.0.0.1:14017"
|
||||
mongodb = "mongodb://127.0.0.1:27017"
|
||||
# Redis connection URL
|
||||
# Defaults to the container name specified in self-hosted
|
||||
redis = "redis://127.0.0.1:14079/"
|
||||
redis = "redis://127.0.0.1:6379/"
|
||||
|
||||
[rabbit]
|
||||
host = "127.0.0.1"
|
||||
|
||||
[hosts]
|
||||
# Web locations of various services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-nodejs-bindings"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
description = "Node.js bindings for the Revolt software"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
license = "MIT"
|
||||
@@ -20,6 +20,6 @@ serde = { version = "1", features = ["derive"] }
|
||||
|
||||
async-std = "1.12.0"
|
||||
|
||||
revolt-config = { version = "0.8.0", path = "../../core/config" }
|
||||
revolt-result = { version = "0.8.0", path = "../../core/result" }
|
||||
revolt-database = { version = "0.8.0", path = "../../core/database" }
|
||||
revolt-config = { version = "0.8.2", path = "../../core/config" }
|
||||
revolt-result = { version = "0.8.2", path = "../../core/result" }
|
||||
revolt-database = { version = "0.8.2", path = "../../core/database" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
license = "AGPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
||||
@@ -41,7 +41,7 @@ revolt-result = { path = "../core/result" }
|
||||
revolt-models = { path = "../core/models" }
|
||||
revolt-config = { path = "../core/config" }
|
||||
revolt-database = { path = "../core/database" }
|
||||
revolt-permissions = { version = "0.8.0", path = "../core/permissions" }
|
||||
revolt-permissions = { version = "0.8.2", path = "../core/permissions" }
|
||||
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
||||
|
||||
# redis
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-config"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -34,4 +34,4 @@ pretty_env_logger = "0.4.0"
|
||||
sentry = "0.31.5"
|
||||
|
||||
# Core
|
||||
revolt-result = { version = "0.8.0", path = "../result", optional = true }
|
||||
revolt-result = { version = "0.8.2", path = "../result", optional = true }
|
||||
|
||||
@@ -258,3 +258,4 @@ api = ""
|
||||
events = ""
|
||||
files = ""
|
||||
proxy = ""
|
||||
crond = ""
|
||||
|
||||
@@ -324,6 +324,7 @@ pub struct Sentry {
|
||||
pub events: String,
|
||||
pub files: String,
|
||||
pub proxy: String,
|
||||
pub crond: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-database"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -24,15 +24,15 @@ default = ["mongodb", "async-std-runtime", "tasks"]
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-config = { version = "0.8.0", path = "../config", features = [
|
||||
revolt-config = { version = "0.8.2", path = "../config", features = [
|
||||
"report-macros",
|
||||
] }
|
||||
revolt-result = { version = "0.8.0", path = "../result" }
|
||||
revolt-models = { version = "0.8.0", path = "../models", features = [
|
||||
revolt-result = { version = "0.8.2", path = "../result" }
|
||||
revolt-models = { version = "0.8.2", path = "../models", features = [
|
||||
"validator",
|
||||
] }
|
||||
revolt-presence = { version = "0.8.0", path = "../presence" }
|
||||
revolt-permissions = { version = "0.8.0", path = "../permissions", features = [
|
||||
revolt-presence = { version = "0.8.2", path = "../presence" }
|
||||
revolt-permissions = { version = "0.8.2", path = "../permissions", features = [
|
||||
"serde",
|
||||
"bson",
|
||||
] }
|
||||
|
||||
@@ -30,7 +30,7 @@ impl AbstractChannelUnreads for MongoDb {
|
||||
doc! {
|
||||
"$pull": {
|
||||
"mentions": {
|
||||
"$lt": message_id
|
||||
"$lte": message_id
|
||||
}
|
||||
},
|
||||
"$set": {
|
||||
|
||||
@@ -15,4 +15,7 @@ pub trait AbstractAttachmentHashes: Sync + Send {
|
||||
|
||||
/// Update an attachment hash nonce value.
|
||||
async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>;
|
||||
|
||||
/// Delete attachment hash by id.
|
||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -48,4 +48,9 @@ impl AbstractAttachmentHashes for MongoDb {
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
|
||||
/// Delete attachment hash by id.
|
||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
||||
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ impl AbstractAttachmentHashes for ReferenceDb {
|
||||
let hashes = self.file_hashes.lock().await;
|
||||
hashes
|
||||
.values()
|
||||
.find(|&hash| hash.id == hash_value || hash.processed_hash == hash_value)
|
||||
.cloned()
|
||||
.find(|hash| hash.id == hash_value || hash.processed_hash == hash_value)
|
||||
.ok_or(create_error!(NotFound))
|
||||
}
|
||||
|
||||
@@ -38,4 +38,14 @@ impl AbstractAttachmentHashes for ReferenceDb {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete attachment hash by id.
|
||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
||||
let mut file_hashes = self.file_hashes.lock().await;
|
||||
if file_hashes.remove(id).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,15 @@ pub trait AbstractAttachments: Sync + Send {
|
||||
/// Fetch an attachment by its id.
|
||||
async fn fetch_attachment(&self, tag: &str, file_id: &str) -> Result<File>;
|
||||
|
||||
/// Fetch all deleted attachments.
|
||||
async fn fetch_deleted_attachments(&self) -> Result<Vec<File>>;
|
||||
|
||||
/// Fetch all dangling attachments.
|
||||
async fn fetch_dangling_files(&self) -> Result<Vec<File>>;
|
||||
|
||||
/// Count references to a given hash.
|
||||
async fn count_file_hash_references(&self, hash: &str) -> Result<usize>;
|
||||
|
||||
/// Find an attachment by its details and mark it as used by a given parent.
|
||||
async fn find_and_use_attachment(
|
||||
&self,
|
||||
@@ -32,4 +41,7 @@ pub trait AbstractAttachments: Sync + Send {
|
||||
|
||||
/// Mark multiple attachments as having been deleted.
|
||||
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()>;
|
||||
|
||||
/// Delete the attachment entry.
|
||||
async fn delete_attachment(&self, id: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,51 @@ impl AbstractAttachments for MongoDb {
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch all deleted attachments.
|
||||
async fn fetch_deleted_attachments(&self) -> Result<Vec<File>> {
|
||||
query!(
|
||||
self,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"deleted": true,
|
||||
"reported": {
|
||||
"$ne": true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Fetch all dangling attachments.
|
||||
async fn fetch_dangling_files(&self) -> Result<Vec<File>> {
|
||||
query!(
|
||||
self,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"used_for.type": {
|
||||
"$exists": 0
|
||||
},
|
||||
"deleted": {
|
||||
"$ne": true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Count references to a given hash.
|
||||
async fn count_file_hash_references(&self, hash: &str) -> Result<usize> {
|
||||
query!(
|
||||
self,
|
||||
count_documents,
|
||||
COL,
|
||||
doc! {
|
||||
"hash": hash
|
||||
}
|
||||
)
|
||||
.map(|count| count as usize)
|
||||
}
|
||||
|
||||
/// Find an attachment by its details and mark it as used by a given parent.
|
||||
async fn find_and_use_attachment(
|
||||
&self,
|
||||
@@ -114,7 +159,7 @@ impl AbstractAttachments for MongoDb {
|
||||
/// Mark multiple attachments as having been deleted.
|
||||
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
.update_many(
|
||||
doc! {
|
||||
"_id": {
|
||||
"$in": ids
|
||||
@@ -129,7 +174,12 @@ impl AbstractAttachments for MongoDb {
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
.map_err(|_| create_database_error!("update_many", COL))
|
||||
}
|
||||
|
||||
/// Delete the attachment entry.
|
||||
async fn delete_attachment(&self, id: &str) -> Result<()> {
|
||||
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,41 @@ impl AbstractAttachments for ReferenceDb {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch all deleted attachments.
|
||||
async fn fetch_deleted_attachments(&self) -> Result<Vec<File>> {
|
||||
let files = self.files.lock().await;
|
||||
Ok(files
|
||||
.values()
|
||||
.filter(|file| {
|
||||
// file has been marked as deleted
|
||||
file.deleted.is_some_and(|v| v)
|
||||
// and it has not been reported
|
||||
&& !file.reported.is_some_and(|v| v)
|
||||
})
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Fetch all dangling attachments.
|
||||
async fn fetch_dangling_files(&self) -> Result<Vec<File>> {
|
||||
let files = self.files.lock().await;
|
||||
Ok(files
|
||||
.values()
|
||||
.filter(|file| file.used_for.is_none() && !file.deleted.is_some_and(|v| v))
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Count references to a given hash.
|
||||
async fn count_file_hash_references(&self, hash: &str) -> Result<usize> {
|
||||
let files = self.files.lock().await;
|
||||
Ok(files
|
||||
.values()
|
||||
.filter(|file| file.hash.as_ref().is_some_and(|h| h == hash))
|
||||
.cloned()
|
||||
.count())
|
||||
}
|
||||
|
||||
/// Find an attachment by its details and mark it as used by a given parent.
|
||||
async fn find_and_use_attachment(
|
||||
&self,
|
||||
@@ -96,4 +131,14 @@ impl AbstractAttachments for ReferenceDb {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete the attachment entry.
|
||||
async fn delete_attachment(&self, id: &str) -> Result<()> {
|
||||
let mut files = self.files.lock().await;
|
||||
if files.remove(id).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ impl AbstractMessages for MongoDb {
|
||||
COL,
|
||||
older_message_filter,
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2)
|
||||
.limit(limit / 2 + 1)
|
||||
.sort(doc! {
|
||||
"_id": -1_i32
|
||||
})
|
||||
|
||||
@@ -152,7 +152,7 @@ pub static DISCRIMINATOR_SEARCH_SPACE: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||
.collect::<HashSet<String>>();
|
||||
|
||||
for discrim in [
|
||||
123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999,
|
||||
123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999, 1488,
|
||||
] {
|
||||
set.remove(&format!("{:0>4}", discrim));
|
||||
}
|
||||
@@ -293,7 +293,14 @@ impl User {
|
||||
}
|
||||
|
||||
// Ensure none of the following substrings show up in the username
|
||||
const BLOCKED_SUBSTRINGS: &[&str] = &["```"];
|
||||
const BLOCKED_SUBSTRINGS: &[&str] = &[
|
||||
"```",
|
||||
"discord.gg",
|
||||
"rvlt.gg",
|
||||
"guilded.gg",
|
||||
"https://",
|
||||
"http://",
|
||||
];
|
||||
|
||||
for substr in BLOCKED_SUBSTRINGS {
|
||||
if username_lowercase.contains(substr) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-files"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -20,10 +20,10 @@ typenum = "1.17.0"
|
||||
aws-config = "1.5.5"
|
||||
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
|
||||
|
||||
revolt-config = { version = "0.8.0", path = "../config", features = [
|
||||
revolt-config = { version = "0.8.2", path = "../config", features = [
|
||||
"report-macros",
|
||||
] }
|
||||
revolt-result = { version = "0.8.0", path = "../result" }
|
||||
revolt-result = { version = "0.8.2", path = "../result" }
|
||||
|
||||
# image processing
|
||||
jxl-oxide = "0.8.1"
|
||||
|
||||
@@ -113,6 +113,23 @@ pub async fn upload_to_s3(bucket_id: &str, path: &str, buf: &[u8]) -> Result<Str
|
||||
Ok(BASE64_STANDARD.encode(nonce))
|
||||
}
|
||||
|
||||
/// Delete a file from S3 by path
|
||||
pub async fn delete_from_s3(bucket_id: &str, path: &str) -> Result<()> {
|
||||
let config = config().await;
|
||||
let client = create_client(config.files.s3);
|
||||
|
||||
report_internal_error!(
|
||||
client
|
||||
.delete_object()
|
||||
.bucket(bucket_id)
|
||||
.key(path)
|
||||
.send()
|
||||
.await
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine size of image at temp file
|
||||
pub fn image_size(f: &NamedTempFile) -> Option<(usize, usize)> {
|
||||
if let Ok(size) = imagesize::size(f.path())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-models"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -20,8 +20,8 @@ default = ["serde", "partials", "rocket"]
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-config = { version = "0.8.0", path = "../config" }
|
||||
revolt-permissions = { version = "0.8.0", path = "../permissions" }
|
||||
revolt-config = { version = "0.8.2", path = "../config" }
|
||||
revolt-permissions = { version = "0.8.2", path = "../permissions" }
|
||||
|
||||
# Utility
|
||||
regex = "1"
|
||||
|
||||
@@ -280,7 +280,7 @@ auto_derived!(
|
||||
pub struct OptionsQueryMessages {
|
||||
/// Maximum number of messages to fetch
|
||||
///
|
||||
/// For fetching nearby messages, this is \`(limit + 1)\`.
|
||||
/// For fetching nearby messages, this is \`(limit + 2)\`.
|
||||
#[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
|
||||
pub limit: Option<i64>,
|
||||
/// Message id before which messages should be fetched
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-permissions"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-result = { version = "0.8.0", path = "../result" }
|
||||
revolt-result = { version = "0.8.2", path = "../result" }
|
||||
|
||||
# Utility
|
||||
auto_ops = "0.3.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-presence"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-result"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
|
||||
@@ -42,6 +42,7 @@ impl IntoResponse for Error {
|
||||
ErrorType::InvalidRole => StatusCode::NOT_FOUND,
|
||||
ErrorType::Banned => StatusCode::FORBIDDEN,
|
||||
ErrorType::AlreadyInServer => StatusCode::CONFLICT,
|
||||
ErrorType::CannotTimeoutYourself => StatusCode::BAD_REQUEST,
|
||||
|
||||
ErrorType::TooManyServers { .. } => StatusCode::BAD_REQUEST,
|
||||
ErrorType::TooManyEmbeds { .. } => StatusCode::BAD_REQUEST,
|
||||
|
||||
@@ -116,6 +116,7 @@ pub enum ErrorType {
|
||||
max: usize,
|
||||
},
|
||||
AlreadyInServer,
|
||||
CannotTimeoutYourself,
|
||||
|
||||
// ? Bot related errors
|
||||
ReachedMaximumBots,
|
||||
|
||||
@@ -47,6 +47,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
ErrorType::InvalidRole => Status::NotFound,
|
||||
ErrorType::Banned => Status::Forbidden,
|
||||
ErrorType::AlreadyInServer => Status::Conflict,
|
||||
ErrorType::CannotTimeoutYourself => Status::BadRequest,
|
||||
|
||||
ErrorType::TooManyServers { .. } => Status::BadRequest,
|
||||
ErrorType::TooManyEmbeds { .. } => Status::BadRequest,
|
||||
|
||||
22
crates/daemons/crond/Cargo.toml
Normal file
22
crates/daemons/crond/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "revolt-crond"
|
||||
version = "0.8.2"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
edition = "2021"
|
||||
description = "Revolt Daemon Service: Timed data clean up tasks"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# Utility
|
||||
log = "0.4"
|
||||
|
||||
# Async
|
||||
tokio = { version = "1" }
|
||||
|
||||
# Core
|
||||
revolt-database = { version = "0.8.2", path = "../../core/database" }
|
||||
revolt-result = { version = "0.8.2", path = "../../core/result" }
|
||||
revolt-config = { version = "0.8.2", path = "../../core/config" }
|
||||
revolt-files = { version = "0.8.2", path = "../../core/files" }
|
||||
10
crates/daemons/crond/Dockerfile
Normal file
10
crates/daemons/crond/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
# Build Stage
|
||||
FROM ghcr.io/revoltchat/base:latest AS builder
|
||||
FROM debian:12 AS debian
|
||||
|
||||
# Bundle Stage
|
||||
FROM gcr.io/distroless/cc-debian12:nonroot
|
||||
COPY --from=builder /home/rust/src/target/release/revolt-crond ./
|
||||
|
||||
USER nonroot
|
||||
CMD ["./revolt-crond"]
|
||||
19
crates/daemons/crond/src/main.rs
Normal file
19
crates/daemons/crond/src/main.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use revolt_config::configure;
|
||||
use revolt_database::DatabaseInfo;
|
||||
use revolt_result::Result;
|
||||
use tasks::{file_deletion, prune_dangling_files};
|
||||
use tokio::try_join;
|
||||
|
||||
pub mod tasks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
configure!(crond);
|
||||
|
||||
let db = DatabaseInfo::Auto.connect().await.expect("database");
|
||||
try_join!(
|
||||
file_deletion::task(db.clone()),
|
||||
prune_dangling_files::task(db)
|
||||
)
|
||||
.map(|_| ())
|
||||
}
|
||||
39
crates/daemons/crond/src/tasks/file_deletion.rs
Normal file
39
crates/daemons/crond/src/tasks/file_deletion.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use log::info;
|
||||
use revolt_database::Database;
|
||||
use revolt_files::delete_from_s3;
|
||||
use revolt_result::Result;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub async fn task(db: Database) -> Result<()> {
|
||||
loop {
|
||||
let files = db.fetch_deleted_attachments().await?;
|
||||
|
||||
for file in files {
|
||||
let count = db
|
||||
.count_file_hash_references(file.hash.as_ref().expect("no `hash` present"))
|
||||
.await?;
|
||||
|
||||
// No other files reference this file on disk anymore
|
||||
if count <= 1 {
|
||||
let file_hash = db
|
||||
.fetch_attachment_hash(file.hash.as_ref().expect("no `hash` present"))
|
||||
.await?;
|
||||
|
||||
// Delete from S3
|
||||
delete_from_s3(&file_hash.bucket_id, &file_hash.path).await?;
|
||||
|
||||
// Delete the hash
|
||||
db.delete_attachment_hash(&file_hash.id).await?;
|
||||
info!("Deleted file hash {}", file_hash.id);
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
db.delete_attachment(&file.id).await?;
|
||||
info!("Deleted file {}", file.id);
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
2
crates/daemons/crond/src/tasks/mod.rs
Normal file
2
crates/daemons/crond/src/tasks/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod file_deletion;
|
||||
pub mod prune_dangling_files;
|
||||
36
crates/daemons/crond/src/tasks/prune_dangling_files.rs
Normal file
36
crates/daemons/crond/src/tasks/prune_dangling_files.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use revolt_database::{iso8601_timestamp::Timestamp, Database};
|
||||
use revolt_result::Result;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use log::info;
|
||||
|
||||
pub async fn task(db: Database) -> Result<()> {
|
||||
loop {
|
||||
// This could just be a single database query
|
||||
// ... but timestamps are inconsistently serialised
|
||||
// ... sometimes they are dates/numbers, hard to query
|
||||
// ... in the future, we could use Postgres instead! :D
|
||||
// ...
|
||||
// ... on the plus side, it's still only 2 queries
|
||||
|
||||
let files = db.fetch_dangling_files().await?;
|
||||
let file_ids: Vec<String> = files
|
||||
.into_iter()
|
||||
.filter(|file| {
|
||||
file.uploaded_at.is_some_and(|uploaded_at| {
|
||||
Timestamp::now_utc().duration_since(uploaded_at) > Duration::from_secs(60 * 60)
|
||||
})
|
||||
})
|
||||
.map(|file| file.id)
|
||||
.collect();
|
||||
|
||||
if !file_ids.is_empty() {
|
||||
db.mark_attachments_as_deleted(&file_ids).await?;
|
||||
info!("Marked {} dangling files for deletion", file_ids.len());
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "revolt-pushd"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
revolt-config = { version = "0.8.0", path = "../../core/config" }
|
||||
revolt-database = { version = "0.8.0", path = "../../core/database" }
|
||||
revolt-models = { version = "0.8.0", path = "../../core/models", features = [
|
||||
revolt-config = { version = "0.8.2", path = "../../core/config" }
|
||||
revolt-database = { version = "0.8.2", path = "../../core/database" }
|
||||
revolt-models = { version = "0.8.2", path = "../../core/models", features = [
|
||||
"validator",
|
||||
] }
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Build Stage
|
||||
FROM ghcr.io/revoltchat/base:latest AS builder
|
||||
# BASE_IMAGE is the compiled workspace base image. Defaults to the upstream
|
||||
# revolt base, but CI overrides it with our own base built from repo.
|
||||
ARG BASE_IMAGE=ghcr.io/revoltchat/base:latest
|
||||
FROM ${BASE_IMAGE} AS builder
|
||||
FROM debian:12 AS debian
|
||||
|
||||
# Bundle Stage
|
||||
|
||||
@@ -111,7 +111,7 @@ impl AsyncConsumer for MessageConsumer {
|
||||
config.pushd.vapid.queue.as_str(),
|
||||
)
|
||||
.finish();
|
||||
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
|
||||
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
|
||||
sendable
|
||||
.extras
|
||||
.insert("endpoint".to_string(), sub.endpoint.clone());
|
||||
|
||||
@@ -55,11 +55,24 @@ impl AsyncConsumer for VapidOutboundConsumer {
|
||||
|
||||
let config = revolt_config::config().await;
|
||||
|
||||
// A VAPID web push payload must carry the subscription endpoint and the
|
||||
// p256dh public key in `extras`. Dropped malformed requests instead
|
||||
// of panicking.
|
||||
let (Some(endpoint), Some(p256dh)) =
|
||||
(payload.extras.get("endpoint"), payload.extras.get("p256dh"))
|
||||
else {
|
||||
log::warn!(
|
||||
"Dropping VAPID push for session {}: payload missing endpoint/p256dh in extras.",
|
||||
payload.session_id
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let subscription = SubscriptionInfo {
|
||||
endpoint: payload.extras.get("endpoint").unwrap().clone(),
|
||||
endpoint: endpoint.clone(),
|
||||
keys: SubscriptionKeys {
|
||||
auth: payload.token,
|
||||
p256dh: payload.extras.get("p256dh").unwrap().clone(),
|
||||
p256dh: p256dh.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-delta"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -75,7 +75,15 @@ pub async fn web() -> Rocket<Build> {
|
||||
// Configure Swagger
|
||||
let swagger = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||
url: "../openapi.json".to_owned(),
|
||||
url: "/openapi.json".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.into();
|
||||
|
||||
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||
url: "/0.8/openapi.json".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -89,8 +97,12 @@ pub async fn web() -> Rocket<Build> {
|
||||
&config.rabbit.password,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel = connection.open_channel(None).await.unwrap();
|
||||
.expect("Failed to connect to RabbitMQ");
|
||||
|
||||
let channel = connection
|
||||
.open_channel(None)
|
||||
.await
|
||||
.expect("Failed to open RabbitMQ channel");
|
||||
|
||||
channel
|
||||
.exchange_declare(
|
||||
@@ -116,6 +128,7 @@ pub async fn web() -> Rocket<Build> {
|
||||
.mount("/", rocket_cors::catch_all_options_routes())
|
||||
.mount("/", util::ratelimiter::routes())
|
||||
.mount("/swagger/", swagger)
|
||||
.mount("/0.8/swagger/", swagger_0_8)
|
||||
.manage(authifier)
|
||||
.manage(db)
|
||||
.manage(amqp)
|
||||
|
||||
@@ -22,7 +22,7 @@ pub async fn fetch_webhooks(
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
|
||||
calculate_channel_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ViewChannel)?;
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageWebhooks)?;
|
||||
|
||||
Ok(Json(
|
||||
db.fetch_webhooks_for_channel(channel.id())
|
||||
|
||||
@@ -61,6 +61,47 @@ pub fn mount(config: Settings, mut rocket: Rocket<Build>) -> Rocket<Build> {
|
||||
};
|
||||
}
|
||||
|
||||
if config.features.webhooks_enabled {
|
||||
mount_endpoints_and_merged_docs! {
|
||||
rocket, "/0.8".to_owned(), settings,
|
||||
"/" => (vec![], custom_openapi_spec()),
|
||||
"" => openapi_get_routes_spec![root::root],
|
||||
"/users" => users::routes(),
|
||||
"/bots" => bots::routes(),
|
||||
"/channels" => channels::routes(),
|
||||
"/servers" => servers::routes(),
|
||||
"/invites" => invites::routes(),
|
||||
"/custom" => customisation::routes(),
|
||||
"/safety" => safety::routes(),
|
||||
"/auth/account" => rocket_authifier::routes::account::routes(),
|
||||
"/auth/session" => rocket_authifier::routes::session::routes(),
|
||||
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
|
||||
"/onboard" => onboard::routes(),
|
||||
"/push" => push::routes(),
|
||||
"/sync" => sync::routes(),
|
||||
"/webhooks" => webhooks::routes()
|
||||
};
|
||||
} else {
|
||||
mount_endpoints_and_merged_docs! {
|
||||
rocket, "/0.8".to_owned(), settings,
|
||||
"/" => (vec![], custom_openapi_spec()),
|
||||
"" => openapi_get_routes_spec![root::root],
|
||||
"/users" => users::routes(),
|
||||
"/bots" => bots::routes(),
|
||||
"/channels" => channels::routes(),
|
||||
"/servers" => servers::routes(),
|
||||
"/invites" => invites::routes(),
|
||||
"/custom" => customisation::routes(),
|
||||
"/safety" => safety::routes(),
|
||||
"/auth/account" => rocket_authifier::routes::account::routes(),
|
||||
"/auth/session" => rocket_authifier::routes::session::routes(),
|
||||
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
|
||||
"/onboard" => onboard::routes(),
|
||||
"/push" => push::routes(),
|
||||
"/sync" => sync::routes()
|
||||
};
|
||||
}
|
||||
|
||||
rocket
|
||||
}
|
||||
|
||||
@@ -190,10 +231,15 @@ fn custom_openapi_spec() -> OpenApi {
|
||||
..Default::default()
|
||||
},
|
||||
Server {
|
||||
url: "http://local.revolt.chat:8000".to_owned(),
|
||||
url: "http://local.revolt.chat:14702".to_owned(),
|
||||
description: Some("Local Revolt Environment".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
Server {
|
||||
url: "http://local.revolt.chat:14702/0.8".to_owned(),
|
||||
description: Some("Local Revolt Environment (v0.8)".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
external_docs: Some(ExternalDocs {
|
||||
url: "https://developers.revolt.chat".to_owned(),
|
||||
|
||||
@@ -15,12 +15,12 @@ use validator::Validate;
|
||||
///
|
||||
/// Edit a member by their id.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[patch("/<server>/members/<target>", data = "<data>")]
|
||||
#[patch("/<server>/members/<member>", data = "<data>")]
|
||||
pub async fn edit(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
server: Reference,
|
||||
target: Reference,
|
||||
member: Reference,
|
||||
data: Json<v0::DataMemberEdit>,
|
||||
) -> Result<Json<v0::Member>> {
|
||||
let data = data.into_inner();
|
||||
@@ -30,9 +30,9 @@ pub async fn edit(
|
||||
})
|
||||
})?;
|
||||
|
||||
// Fetch server and target member
|
||||
// Fetch server and member
|
||||
let mut server = server.as_server(db).await?;
|
||||
let mut member = target.as_member(db, &server.id).await?;
|
||||
let mut member = member.as_member(db, &server.id).await?;
|
||||
|
||||
// Fetch our currrent permissions
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
@@ -84,6 +84,10 @@ pub async fn edit(
|
||||
.map(|x| x.contains(&v0::FieldsMember::Timeout))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if data.timeout.is_some() && member.id.user == user.id {
|
||||
return Err(create_error!(CannotTimeoutYourself));
|
||||
}
|
||||
|
||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,14 +94,28 @@ pub struct Ratelimiter {
|
||||
///
|
||||
/// Optionally, include a resource id to hash against.
|
||||
fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r str>) {
|
||||
if let Some(segment) = request.routed_segment(0) {
|
||||
let resource = request.routed_segment(1);
|
||||
let (segment, resource, extra) = if matches!(request.routed_segment(0), Some("0.8")) {
|
||||
(
|
||||
request.routed_segment(1),
|
||||
request.routed_segment(2),
|
||||
request.routed_segment(3),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
request.routed_segment(0),
|
||||
request.routed_segment(1),
|
||||
request.routed_segment(2),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(segment) = segment {
|
||||
let resource = resource;
|
||||
|
||||
let method = request.method();
|
||||
match (segment, resource, method) {
|
||||
("users", target, Method::Patch) => ("user_edit", target),
|
||||
("users", _, _) => {
|
||||
if let Some("default_avatar") = request.routed_segment(2) {
|
||||
if let Some("default_avatar") = extra {
|
||||
return ("default_avatar", None);
|
||||
}
|
||||
|
||||
@@ -110,7 +124,7 @@ fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r
|
||||
("bots", _, _) => ("bots", None),
|
||||
("channels", Some(id), _) => {
|
||||
if request.method() == Method::Post {
|
||||
if let Some("messages") = request.routed_segment(2) {
|
||||
if let Some("messages") = extra {
|
||||
return ("messaging", Some(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-autumn"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -42,12 +42,12 @@ tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
# Core crates
|
||||
revolt-files = { version = "0.8.0", path = "../../core/files" }
|
||||
revolt-config = { version = "0.8.0", path = "../../core/config" }
|
||||
revolt-database = { version = "0.8.0", path = "../../core/database", features = [
|
||||
revolt-files = { version = "0.8.2", path = "../../core/files" }
|
||||
revolt-config = { version = "0.8.2", path = "../../core/config" }
|
||||
revolt-database = { version = "0.8.2", path = "../../core/database", features = [
|
||||
"axum-impl",
|
||||
] }
|
||||
revolt-result = { version = "0.8.0", path = "../../core/result", features = [
|
||||
revolt-result = { version = "0.8.2", path = "../../core/result", features = [
|
||||
"utoipa",
|
||||
"axum",
|
||||
] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-january"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -31,13 +31,13 @@ tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
# Core crates
|
||||
revolt-config = { version = "0.8.0", path = "../../core/config" }
|
||||
revolt-models = { version = "0.8.0", path = "../../core/models" }
|
||||
revolt-result = { version = "0.8.0", path = "../../core/result", features = [
|
||||
revolt-config = { version = "0.8.2", path = "../../core/config" }
|
||||
revolt-models = { version = "0.8.2", path = "../../core/models" }
|
||||
revolt-result = { version = "0.8.2", path = "../../core/result", features = [
|
||||
"utoipa",
|
||||
"axum",
|
||||
] }
|
||||
revolt-files = { version = "0.8.0", path = "../../core/files" }
|
||||
revolt-files = { version = "0.8.2", path = "../../core/files" }
|
||||
|
||||
# Axum / web server
|
||||
axum = { version = "0.7.5" }
|
||||
|
||||
20
default.nix
20
default.nix
@@ -1,26 +1,10 @@
|
||||
let
|
||||
# Pinned nixpkgs, deterministic. Last updated: 28-07-2024.
|
||||
pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/9b34ca580417e1ebc56c4df57d8b387dad686665.tar.gz")) {};
|
||||
|
||||
# Rolling updates, not deterministic.
|
||||
# pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
|
||||
in pkgs.mkShell {
|
||||
name = "revoltEnv";
|
||||
|
||||
# LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||
# pkgs.gcc-unwrapped
|
||||
# pkgs.zlib
|
||||
# pkgs.glib
|
||||
# pkgs.libGL
|
||||
# ];
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell rec {
|
||||
buildInputs = [
|
||||
# Tools
|
||||
pkgs.git
|
||||
|
||||
# Database
|
||||
# pkgs.mongodb
|
||||
|
||||
# Cargo
|
||||
pkgs.cargo
|
||||
pkgs.cargo-nextest
|
||||
|
||||
@@ -34,12 +34,14 @@ deps() {
|
||||
crates/core/result/src \
|
||||
crates/services/autumn/src \
|
||||
crates/services/january/src \
|
||||
crates/daemons/crond/src \
|
||||
crates/daemons/pushd/src
|
||||
echo 'fn main() { panic!("stub"); }' |
|
||||
tee crates/bonfire/src/main.rs |
|
||||
tee crates/delta/src/main.rs |
|
||||
tee crates/services/autumn/src/main.rs |
|
||||
tee crates/services/january/src/main.rs |
|
||||
tee crates/daemons/crond/src/main.rs |
|
||||
tee crates/daemons/pushd/src/main.rs
|
||||
echo '' |
|
||||
tee crates/bindings/node/src/lib.rs |
|
||||
@@ -62,6 +64,7 @@ apps() {
|
||||
touch -am \
|
||||
crates/bonfire/src/main.rs \
|
||||
crates/delta/src/main.rs \
|
||||
crates/daemons/crond/src/main.rs \
|
||||
crates/daemons/pushd/src/main.rs \
|
||||
crates/core/config/src/lib.rs \
|
||||
crates/core/database/src/lib.rs \
|
||||
|
||||
@@ -25,6 +25,8 @@ docker build -t ghcr.io/revoltchat/server:$TAG - < crates/delta/Dockerfile
|
||||
docker build -t ghcr.io/revoltchat/bonfire:$TAG - < crates/bonfire/Dockerfile
|
||||
docker build -t ghcr.io/revoltchat/autumn:$TAG - < crates/services/autumn/Dockerfile
|
||||
docker build -t ghcr.io/revoltchat/january:$TAG - < crates/services/january/Dockerfile
|
||||
docker build -t ghcr.io/revoltchat/crond:$TAG - < crates/daemons/crond/Dockerfile
|
||||
docker build -t ghcr.io/revoltchat/pushd:$TAG - < crates/daemons/pushd/Dockerfile
|
||||
|
||||
if [ "$DEBUG" = "true" ]; then
|
||||
git restore Cargo.toml
|
||||
@@ -34,3 +36,5 @@ docker push ghcr.io/revoltchat/server:$TAG
|
||||
docker push ghcr.io/revoltchat/bonfire:$TAG
|
||||
docker push ghcr.io/revoltchat/autumn:$TAG
|
||||
docker push ghcr.io/revoltchat/january:$TAG
|
||||
docker push ghcr.io/revoltchat/crond:$TAG
|
||||
docker push ghcr.io/revoltchat/pushd:$TAG
|
||||
|
||||
Reference in New Issue
Block a user