Compare commits

...
9 Commits
Author SHA1 Message Date
Paul Makles 03a28dbb3e fix: use debian:bullseye image for glibc 2.29 2023-04-21 18:11:06 +01:00
Paul Makles 32542a822e feat: rewrite presence system to use sets 2023-04-21 17:28:13 +01:00
Paul Makles 1df90ff53b chore: update DSN for Sentry 2023-04-21 17:27:45 +01:00
ToastXC 487b979f0d fix: remove check for bot account fetching invites from a server (#237) 2023-04-20 11:05:13 +01:00
ToastXC b9d813d8f0 fix: deny access for bot creating a server (#238) 2023-04-20 11:04:51 +01:00
Paul Makles 36dd128459 merge: pull request #234 from thebearingedge/arm64-support 2023-04-20 11:04:35 +01:00
Zomatree 144f0d39c6 fix: Add limits to channels and roles 2023-04-10 17:39:21 +01:00
Tim Davis 32a294a64a feat: add support for arm64 (#142)
- set explicit action permissions
- make package owner dynamic so forks can test workflow
- build multi platform images using cross compilation amd64 -> arm64
- add a scope to buildx gha cache
2023-03-21 12:23:57 -07:00
Paul Makles b7b70346b4 fix: add additional checks to group members 2023-03-18 22:32:15 +00:00
23 changed files with 261 additions and 217 deletions

No files matched your search

+30 -22
View File
@@ -20,47 +20,52 @@ on:
- "Dockerfile" - "Dockerfile"
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
packages: write
jobs: jobs:
base: base:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Build base image (amd64) name: Build base image
steps: steps:
# Configure build environment # Configure build environment
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Authenticate with GHCR # Authenticate with GHCR
- name: Login to Github Container Registry - name: Login to Github Container Registry
uses: docker/login-action@v1 if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
# Build all projects and cache - name: Build base image
- name: Build Base Image uses: docker/build-push-action@v4
uses: docker/build-push-action@v3
with: with:
context: . context: .
push: true push: ${{ github.event_name != 'pull_request' }}
tags: ghcr.io/revoltchat/base:latest platforms: linux/amd64,linux/arm64
cache-from: type=gha tags: ghcr.io/${{ github.repository_owner }}/base:latest
cache-to: type=gha,mode=max cache-from: type=gha,scope=buildx-base-multi-arch
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
publish_amd64: publish:
needs: [base] needs: [base]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
strategy: strategy:
matrix: matrix:
project: [delta, bonfire] project: [delta, bonfire]
name: Build ${{ matrix.project }} image (amd64) name: Build ${{ matrix.project }} image
steps: steps:
# Configure build environment # Configure build environment
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
@@ -68,10 +73,11 @@ jobs:
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Container Registry - name: Login to Github Container Registry
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -86,11 +92,11 @@ jobs:
{ {
"delta": { "delta": {
"path": "crates/delta", "path": "crates/delta",
"tag": "revoltchat/server" "tag": "${{ github.repository_owner }}/server"
}, },
"bonfire": { "bonfire": {
"path": "crates/bonfire", "path": "crates/bonfire",
"tag": "revoltchat/bonfire" "tag": "${{ github.repository_owner }}/bonfire"
} }
} }
export_to: output export_to: output
@@ -98,19 +104,21 @@ jobs:
# Configure metadata # Configure metadata
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v4
with: with:
images: ${{ steps.export.outputs.tag }}, ghcr.io/${{ steps.export.outputs.tag }} images: |
docker.io/${{ steps.export.outputs.tag }}
ghcr.io/${{ steps.export.outputs.tag }}
# Build crate image # Build crate image
- name: Publish - name: Publish
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64 platforms: linux/amd64,linux/arm64
file: ${{ steps.export.outputs.path }}/Dockerfile file: ${{ steps.export.outputs.path }}/Dockerfile
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Generated
+4 -3
View File
@@ -2792,7 +2792,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.5.16" version = "0.5.18"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-tungstenite", "async-tungstenite",
@@ -2808,7 +2808,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.5.16" version = "0.5.18"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-std", "async-std",
@@ -2847,7 +2847,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-quark" name = "revolt-quark"
version = "0.5.16" version = "0.5.18"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"async-recursion", "async-recursion",
@@ -2874,6 +2874,7 @@ dependencies = [
"once_cell", "once_cell",
"optional_struct", "optional_struct",
"pretty_env_logger", "pretty_env_logger",
"rand 0.8.5",
"redis-kiss", "redis-kiss",
"regex", "regex",
"reqwest", "reqwest",
+20 -5
View File
@@ -1,12 +1,27 @@
# Build Stage # Build Stage
FROM rustlang/rust:nightly-slim AS builder FROM --platform="${BUILDPLATFORM}" rustlang/rust:nightly-slim
USER 0:0 USER 0:0
WORKDIR /home/rust/src WORKDIR /home/rust/src
# Install build requirements ARG TARGETARCH
RUN apt-get update && apt-get install -y libssl-dev pkg-config
# Build all crates # Install build requirements
RUN dpkg --add-architecture "${TARGETARCH}"
RUN apt-get update && \
apt-get install -y \
make \
pkg-config \
libssl-dev:"${TARGETARCH}"
COPY scripts/build-image-layer.sh /tmp/
RUN sh /tmp/build-image-layer.sh tools
# Build all dependencies
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
COPY crates/bonfire/Cargo.toml ./crates/bonfire/
COPY crates/delta/Cargo.toml ./crates/delta/
COPY crates/quark/Cargo.toml ./crates/quark/
RUN sh /tmp/build-image-layer.sh deps
# Build all apps
COPY crates ./crates COPY crates ./crates
RUN cargo build --locked --release RUN sh /tmp/build-image-layer.sh apps
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.5.16" version = "0.5.18"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
+5 -4
View File
@@ -1,10 +1,11 @@
# Build Stage # Build Stage
FROM ghcr.io/revoltchat/base:latest AS builder FROM ghcr.io/revoltchat/base:latest AS builder
RUN cargo install --locked --path crates/bonfire
# Bundle Stage # Bundle Stage
FROM debian:buster-slim FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y ca-certificates RUN apt-get update && \
COPY --from=builder /usr/local/cargo/bin/revolt-bonfire ./ apt-get install -y ca-certificates && \
apt-get clean
COPY --from=builder /home/rust/src/target/release/revolt-bonfire ./
EXPOSE 9000 EXPOSE 9000
CMD ["./revolt-bonfire"] CMD ["./revolt-bonfire"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.5.16" version = "0.5.18"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
+5 -4
View File
@@ -1,11 +1,12 @@
# Build Stage # Build Stage
FROM ghcr.io/revoltchat/base:latest AS builder FROM ghcr.io/revoltchat/base:latest AS builder
RUN cargo install --locked --path crates/delta
# Bundle Stage # Bundle Stage
FROM debian:buster-slim FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y ca-certificates RUN apt-get update && \
COPY --from=builder /usr/local/cargo/bin/revolt-delta ./ apt-get install -y ca-certificates && \
apt-get clean
COPY --from=builder /home/rust/src/target/release/revolt-delta ./
EXPOSE 8000 EXPOSE 8000
ENV ROCKET_ADDRESS 0.0.0.0 ENV ROCKET_ADDRESS 0.0.0.0
@@ -1,5 +1,6 @@
use revolt_quark::{ use revolt_quark::{
models::{Channel, User}, get_relationship,
models::{user::RelationshipStatus, Channel, User},
perms, Db, EmptyResponse, Error, Permission, Ref, Result, perms, Db, EmptyResponse, Error, Permission, Ref, Result,
}; };
@@ -22,6 +23,13 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
match &channel { match &channel {
Channel::Group { .. } => { Channel::Group { .. } => {
let member = member.as_user(db).await?; let member = member.as_user(db).await?;
if !matches!(
get_relationship(&user, &member.id),
RelationshipStatus::Friend
) {
return Err(Error::NotFriends);
}
channel channel
.add_user_to_group(db, &member.id, &user.id) .add_user_to_group(db, &member.id, &user.id)
.await .await
@@ -7,7 +7,7 @@ use revolt_quark::{
}, },
perms, perms,
web::idempotency::IdempotencyKey, web::idempotency::IdempotencyKey,
Db, Error, Permission, Ref, Result, Db, Error, Permission, Ref, Result, variables::delta::{MAX_ATTACHMENT_COUNT, MAX_REPLY_COUNT},
}; };
use regex::Regex; use regex::Regex;
@@ -139,8 +139,8 @@ pub async fn message_send(
// 4. Verify replies are valid. // 4. Verify replies are valid.
let mut replies = HashSet::new(); let mut replies = HashSet::new();
if let Some(entries) = data.replies { if let Some(entries) = data.replies {
if entries.len() > 5 { if entries.len() > *MAX_REPLY_COUNT {
return Err(Error::TooManyReplies); return Err(Error::TooManyReplies { max: *MAX_REPLY_COUNT });
} }
for Reply { id, mention } in entries { for Reply { id, mention } in entries {
@@ -191,8 +191,8 @@ pub async fn message_send(
} }
// ! FIXME: move this to app config // ! FIXME: move this to app config
if ids.len() > 5 { if ids.len() > *MAX_ATTACHMENT_COUNT {
return Err(Error::TooManyAttachments); return Err(Error::TooManyAttachments { max: *MAX_ATTACHMENT_COUNT} );
} }
for attachment_id in ids { for attachment_id in ids {
@@ -1,5 +1,6 @@
use revolt_quark::models::emoji::EmojiParent; use revolt_quark::models::emoji::EmojiParent;
use revolt_quark::models::{Emoji, File, User}; use revolt_quark::models::{Emoji, File, User};
use revolt_quark::variables::delta::MAX_EMOJI_COUNT;
use revolt_quark::{perms, Db, Error, Permission, Result}; use revolt_quark::{perms, Db, Error, Permission, Result};
use serde::Deserialize; use serde::Deserialize;
use validator::Validate; use validator::Validate;
@@ -55,8 +56,8 @@ pub async fn create_emoji(
// Check that there are no more than 100 emoji // Check that there are no more than 100 emoji
// ! FIXME: hardcoded upper limit // ! FIXME: hardcoded upper limit
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?; let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
if emojis.len() > 99 { if emojis.len() > *MAX_EMOJI_COUNT {
return Err(Error::TooManyEmoji); return Err(Error::TooManyEmoji { max: *MAX_EMOJI_COUNT });
} }
} }
EmojiParent::Detached => return Err(Error::InvalidOperation), EmojiParent::Detached => return Err(Error::InvalidOperation),
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use revolt_quark::{ use revolt_quark::{
models::{server::PartialServer, Channel, User}, models::{server::PartialServer, Channel, User},
perms, Db, Error, Permission, Ref, Result, perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_CHANNEL_COUNT,
}; };
use rocket::serde::json::Json; use rocket::serde::json::Json;
@@ -58,6 +58,10 @@ pub async fn req(
.throw_permission(db, Permission::ManageChannel) .throw_permission(db, Permission::ManageChannel)
.await?; .await?;
if server.channels.len() > *MAX_CHANNEL_COUNT {
return Err(Error::TooManyChannels { max: *MAX_CHANNEL_COUNT })
};
let id = Ulid::new().to_string(); let id = Ulid::new().to_string();
let mut channels = server.channels.clone(); let mut channels = server.channels.clone();
channels.push(id.clone()); channels.push(id.clone());
@@ -11,10 +11,6 @@ use rocket::serde::json::Json;
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[get("/<target>/invites")] #[get("/<target>/invites")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Invite>>> { pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Invite>>> {
if user.bot.is_some() {
return Err(Error::IsBot);
}
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
perms(&user) perms(&user)
.server(&server) .server(&server)
@@ -1,6 +1,6 @@
use revolt_quark::{ use revolt_quark::{
models::{server::Role, User}, models::{server::Role, User},
perms, Db, Error, Permission, Ref, Result, perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_ROLE_COUNT,
}; };
use rocket::serde::json::Json; use rocket::serde::json::Json;
@@ -50,6 +50,10 @@ pub async fn req(
.throw_permission(db, Permission::ManageRole) .throw_permission(db, Permission::ManageRole)
.await?; .await?;
if server.roles.len() > *MAX_ROLE_COUNT {
return Err(Error::TooManyRoles { max: *MAX_ROLE_COUNT })
};
let member_rank = permissions.get_member_rank(); let member_rank = permissions.get_member_rank();
let rank = if let Some(given_rank) = data.rank { let rank = if let Some(given_rank) = data.rank {
if given_rank <= member_rank.unwrap_or(i64::MIN) { if given_rank <= member_rank.unwrap_or(i64::MIN) {
@@ -44,6 +44,10 @@ pub async fn req(
user: User, user: User,
info: Json<DataCreateServer>, info: Json<DataCreateServer>,
) -> Result<Json<CreateServerResponse>> { ) -> Result<Json<CreateServerResponse>> {
if user.bot.is_some() {
return Err(Error::IsBot);
}
let info = info.into_inner(); let info = info.into_inner();
info.validate() info.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-quark" name = "revolt-quark"
version = "0.5.16" version = "0.5.18"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -57,6 +57,7 @@ log = "0.4.14"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
# Util # Util
rand = "0.8.5"
ulid = "0.5.0" ulid = "0.5.0"
regex = "1.5.5" regex = "1.5.5"
nanoid = "0.4.0" nanoid = "0.4.0"
+6 -58
View File
@@ -1,64 +1,12 @@
use std::env; use std::env;
use serde::{Deserialize, Serialize};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
pub static REGION_ID: Lazy<u16> = Lazy::new(|| env::var("REGION_ID") pub static REGION_ID: Lazy<u16> = Lazy::new(|| {
.unwrap_or_else(|_| "0".to_string()) env::var("REGION_ID")
.parse() .unwrap_or_else(|_| "0".to_string())
.unwrap()); .parse()
.unwrap()
});
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID)); pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
/// Compact presence information for a user
#[derive(Serialize, Deserialize, Debug)]
pub struct PresenceEntry {
/// Region this session exists in
///
/// We can have up to 65535 regions
pub region_id: u16,
/// Unique session ID
pub session_id: u8,
/// Known flags about session
pub flags: u8,
}
impl PresenceEntry {
/// Create a new presence entry from a given session ID and known flags
pub fn from(session_id: u8, flags: u8) -> Self {
Self {
region_id: *REGION_ID,
session_id,
flags,
}
}
}
pub trait PresenceOp {
/// Find next available session ID
fn find_next_id(&self) -> u8;
}
impl PresenceOp for Vec<PresenceEntry> {
fn find_next_id(&self) -> u8 {
// O(n^2) scan algorithm
// should be relatively fast at low numbers anyways
for i in 0..255 {
let mut found = false;
for entry in self {
if entry.session_id == i {
found = true;
break;
}
}
if !found {
return i;
}
}
255
}
}
+45 -51
View File
@@ -2,87 +2,77 @@ use std::collections::HashSet;
use redis_kiss::{get_connection, AsyncCommands}; use redis_kiss::{get_connection, AsyncCommands};
use rand::Rng;
mod entry; mod entry;
mod operations; mod operations;
use entry::{PresenceEntry, PresenceOp};
use operations::{ use operations::{
__add_to_set_sessions, __delete_key_presence_entry, __get_key_presence_entry, __add_to_set_string, __add_to_set_u32, __delete_key, __get_set_members_as_string,
__get_set_sessions, __remove_from_set_sessions, __set_key_presence_entry, __get_set_size, __remove_from_set_string, __remove_from_set_u32,
}; };
use crate::presence::operations::__delete_set_sessions;
use self::entry::REGION_KEY; use self::entry::REGION_KEY;
/// Create a new presence session, returns the ID of this session /// Create a new presence session, returns the ID of this session
pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u8) { pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u32) {
info!("Creating a presence session for {user_id} with flags {flags}"); info!("Creating a presence session for {user_id} with flags {flags}");
// Try to find the presence entry for this user. if let Ok(mut conn) = get_connection().await {
let mut conn = get_connection().await.unwrap(); // Check whether this is the first session
let mut entry: Vec<PresenceEntry> = __get_key_presence_entry(&mut conn, user_id) let was_empty = __get_set_size(&mut conn, user_id).await == 0;
.await
.unwrap_or_default();
// Return whether this was the first session. // A session ID is comprised of random data and any flags ORed to the end
let was_empty = entry.is_empty(); let session_id = {
info!("User ID {} just came online.", &user_id); let mut rng = rand::thread_rng();
(rng.gen::<u32>() ^ 1) | (flags as u32 & 1)
};
// Generate session ID and push new entry. // Add session to user's sessions and to the region
let session_id = entry.find_next_id(); __add_to_set_u32(&mut conn, user_id, session_id).await;
entry.push(PresenceEntry::from(session_id, flags)); __add_to_set_string(&mut conn, &REGION_KEY, &format!("{user_id}:{session_id}")).await;
__set_key_presence_entry(&mut conn, user_id, entry).await; info!("Created session for {user_id}, assigned them a session ID of {session_id}.");
// Add to region set in case of failure. (was_empty, session_id)
__add_to_set_sessions(&mut conn, &REGION_KEY, user_id, session_id).await; } else {
(was_empty, session_id) // Fail through
(false, 0)
}
} }
/// Delete existing presence session /// Delete existing presence session
pub async fn presence_delete_session(user_id: &str, session_id: u8) -> bool { pub async fn presence_delete_session(user_id: &str, session_id: u32) -> bool {
presence_delete_session_internal(user_id, session_id, false).await presence_delete_session_internal(user_id, session_id, false).await
} }
/// Delete existing presence session (but also choose whether to skip region) /// Delete existing presence session (but also choose whether to skip region)
async fn presence_delete_session_internal( async fn presence_delete_session_internal(
user_id: &str, user_id: &str,
session_id: u8, session_id: u32,
skip_region: bool, skip_region: bool,
) -> bool { ) -> bool {
info!("Deleting presence session for {user_id} with id {session_id}"); info!("Deleting presence session for {user_id} with id {session_id}");
// Return whether this was the last session. if let Ok(mut conn) = get_connection().await {
let mut is_empty = false; // Remove the session
__remove_from_set_u32(&mut conn, user_id, session_id).await;
// Only continue if we can actually find one. // Remove from the region
let mut conn = get_connection().await.unwrap();
let entry: Option<Vec<PresenceEntry>> = __get_key_presence_entry(&mut conn, user_id).await;
if let Some(entry) = entry {
let entries = entry
.into_iter()
.filter(|x| x.session_id != session_id)
.collect::<Vec<PresenceEntry>>();
// If entry is empty, then just delete it.
if entries.is_empty() {
__delete_key_presence_entry(&mut conn, user_id).await;
is_empty = true;
} else {
__set_key_presence_entry(&mut conn, user_id, entries).await;
}
// Remove from region set.
if !skip_region { if !skip_region {
__remove_from_set_sessions(&mut conn, &REGION_KEY, user_id, session_id).await; __remove_from_set_string(&mut conn, &REGION_KEY, &format!("{user_id}:{session_id}"))
.await;
} }
}
if is_empty { // Return whether this was the last session
info!("User ID {} just went offline.", &user_id); let is_empty = __get_set_size(&mut conn, user_id).await == 0;
} if is_empty {
info!("User ID {} just went offline.", &user_id);
}
is_empty is_empty
} else {
// Fail through
false
}
} }
/// Check whether a given user ID is online /// Check whether a given user ID is online
@@ -102,6 +92,10 @@ pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
return set; return set;
} }
// NOTE: at the point that we need mobile indicators
// you can interpret the data here and return a new data
// structure like HashMap<String /* id */, u8 /* flags */>
// We need to handle a special case where only one is present // We need to handle a special case where only one is present
// as for some reason or another, Redis does not like us sending // as for some reason or another, Redis does not like us sending
// a list of just one ID to the server. // a list of just one ID to the server.
@@ -136,7 +130,7 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
let region_id = region_id.unwrap_or(&*REGION_KEY); let region_id = region_id.unwrap_or(&*REGION_KEY);
let mut conn = get_connection().await.expect("Redis connection"); let mut conn = get_connection().await.expect("Redis connection");
let sessions = __get_set_sessions(&mut conn, region_id).await; let sessions = __get_set_members_as_string(&mut conn, region_id).await;
if !sessions.is_empty() { if !sessions.is_empty() {
info!( info!(
"Cleaning up {} sessions, this may take a while...", "Cleaning up {} sessions, this may take a while...",
@@ -155,7 +149,7 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
} }
// Then clear the set in Redis. // Then clear the set in Redis.
__delete_set_sessions(&mut conn, region_id).await; __delete_key(&mut conn, region_id).await;
info!("Clean up complete."); info!("Clean up complete.");
} }
+28 -43
View File
@@ -1,57 +1,42 @@
use redis_kiss::{AsyncCommands, Conn}; use redis_kiss::{AsyncCommands, Conn};
use super::entry::PresenceEntry; /// Add to set (string)
pub async fn __add_to_set_string(conn: &mut Conn, key: &str, value: &str) {
/// Set presence entry by given ID let _: Option<()> = conn.sadd(key, value).await.ok();
pub async fn __set_key_presence_entry(conn: &mut Conn, id: &str, data: Vec<PresenceEntry>) {
let _: Option<()> = conn.set(id, bincode::serialize(&data).unwrap()).await.ok();
} }
/// Delete presence entry by given ID /// Add to set (u32)
pub async fn __delete_key_presence_entry(conn: &mut Conn, id: &str) { pub async fn __add_to_set_u32(conn: &mut Conn, key: &str, value: u32) {
let _: Option<()> = conn.del(id).await.ok(); let _: Option<()> = conn.sadd(key, value).await.ok();
} }
/// Get presence entry by given ID /// Remove from set (string)
pub async fn __get_key_presence_entry(conn: &mut Conn, id: &str) -> Option<Vec<PresenceEntry>> { pub async fn __remove_from_set_string(conn: &mut Conn, key: &str, value: &str) {
conn.get::<_, Option<Vec<u8>>>(id) let _: Option<()> = conn.srem(key, value).await.ok();
}
/// Remove from set (u32)
pub async fn __remove_from_set_u32(conn: &mut Conn, key: &str, value: u32) {
let _: Option<()> = conn.srem(key, value).await.ok();
}
/// Get set members as string
pub async fn __get_set_members_as_string(conn: &mut Conn, key: &str) -> Vec<String> {
conn.smembers::<_, Vec<String>>(key)
.await .await
.unwrap() .expect("could not get set members as string")
.map(|entry| bincode::deserialize(&entry[..]).unwrap())
} }
/// Add to region session set /// Get set size
pub async fn __add_to_set_sessions( pub async fn __get_set_size(conn: &mut Conn, id: &str) -> u32 {
conn: &mut Conn, conn.scard::<_, u32>(id)
region_id: &str,
user_id: &str,
session_id: u8,
) {
let _: Option<()> = conn
.sadd(region_id, format!("{user_id}:{session_id}"))
.await .await
.ok(); .expect("could not get set size")
} }
/// Remove from region session set /// Delete key by id
pub async fn __remove_from_set_sessions( pub async fn __delete_key(conn: &mut Conn, id: &str) {
conn: &mut Conn, conn.del::<_, ()>(id)
region_id: &str,
user_id: &str,
session_id: u8,
) {
let _: Option<()> = conn
.srem(region_id, format!("{user_id}:{session_id}"))
.await .await
.ok(); .expect("could not delete key by id");
}
/// Get region session set as list
pub async fn __get_set_sessions(conn: &mut Conn, region_id: &str) -> Vec<String> {
conn.smembers::<_, Vec<String>>(region_id).await.unwrap()
}
/// Delete region session set
pub async fn __delete_set_sessions(conn: &mut Conn, region_id: &str) {
conn.del::<_, ()>(region_id).await.unwrap();
} }
+1 -1
View File
@@ -14,7 +14,7 @@ pub fn setup_logging(release: &'static str) -> sentry::ClientInitGuard {
info!("Starting {release}"); info!("Starting {release}");
sentry::init(( sentry::init((
"https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4", "https://d1d2a6f15c6245a987c532bbbcb30a04@glitchtip.insert.moe/2",
sentry::ClientOptions { sentry::ClientOptions {
release: Some(release.into()), release: Some(release.into()),
..Default::default() ..Default::default()
+23 -9
View File
@@ -3,7 +3,6 @@ use revolt_rocket_okapi::revolt_okapi::openapi3;
use rocket::{ use rocket::{
http::{ContentType, Status}, http::{ContentType, Status},
response::{self, Responder}, response::{self, Responder},
serde::json::serde_json::json,
Request, Response, Request, Response,
}; };
use schemars::schema::Schema; use schemars::schema::Schema;
@@ -39,8 +38,15 @@ pub enum Error {
UnknownMessage, UnknownMessage,
CannotEditMessage, CannotEditMessage,
CannotJoinCall, CannotJoinCall,
TooManyAttachments, TooManyAttachments {
TooManyReplies, max: usize
},
TooManyReplies {
max: usize
},
TooManyChannels {
max: usize
},
EmptyMessage, EmptyMessage,
PayloadTooLarge, PayloadTooLarge,
CannotRemoveYourself, CannotRemoveYourself,
@@ -57,7 +63,12 @@ pub enum Error {
TooManyServers { TooManyServers {
max: usize, max: usize,
}, },
TooManyEmoji, TooManyEmoji {
max: usize
},
TooManyRoles {
max: usize
},
// ? Bot related errors // ? Bot related errors
ReachedMaximumBots, ReachedMaximumBots,
@@ -151,8 +162,8 @@ impl<'r> Responder<'r, 'static> for Error {
Error::UnknownAttachment => Status::BadRequest, Error::UnknownAttachment => Status::BadRequest,
Error::CannotEditMessage => Status::Forbidden, Error::CannotEditMessage => Status::Forbidden,
Error::CannotJoinCall => Status::BadRequest, Error::CannotJoinCall => Status::BadRequest,
Error::TooManyAttachments => Status::BadRequest, Error::TooManyAttachments { .. } => Status::BadRequest,
Error::TooManyReplies => Status::BadRequest, Error::TooManyReplies { .. } => Status::BadRequest,
Error::EmptyMessage => Status::UnprocessableEntity, Error::EmptyMessage => Status::UnprocessableEntity,
Error::PayloadTooLarge => Status::UnprocessableEntity, Error::PayloadTooLarge => Status::UnprocessableEntity,
Error::CannotRemoveYourself => Status::BadRequest, Error::CannotRemoveYourself => Status::BadRequest,
@@ -163,8 +174,11 @@ impl<'r> Responder<'r, 'static> for Error {
Error::UnknownServer => Status::NotFound, Error::UnknownServer => Status::NotFound,
Error::InvalidRole => Status::NotFound, Error::InvalidRole => Status::NotFound,
Error::Banned => Status::Forbidden, Error::Banned => Status::Forbidden,
Error::TooManyServers { .. } => Status::Forbidden,
Error::TooManyEmoji => Status::BadRequest, Error::TooManyServers { .. } => Status::BadRequest,
Error::TooManyEmoji { .. } => Status::BadRequest,
Error::TooManyChannels { .. } => Status::BadRequest,
Error::TooManyRoles { .. } => Status::BadRequest,
Error::ReachedMaximumBots => Status::BadRequest, Error::ReachedMaximumBots => Status::BadRequest,
Error::IsBot => Status::BadRequest, Error::IsBot => Status::BadRequest,
@@ -193,7 +207,7 @@ impl<'r> Responder<'r, 'static> for Error {
}; };
// Serialize the error data structure into JSON. // Serialize the error data structure into JSON.
let string = json!(self).to_string(); let string = serde_json::to_string(&self).unwrap();
// Build and send the request. // Build and send the request.
Response::build() Response::build()
+6
View File
@@ -44,6 +44,12 @@ pub static MAX_GROUP_SIZE: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_GROUP
pub static MAX_BOT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap()); pub static MAX_BOT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap()); pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
pub static MAX_SERVER_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap()); pub static MAX_SERVER_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
pub static MAX_CHANNEL_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_CHANNEL_COUNT").unwrap_or_else(|_| "200".to_string()).parse().unwrap());
pub static MAX_ROLE_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_ROLE_COUNT").unwrap_or_else(|_| "200".to_string()).parse().unwrap());
pub static MAX_EMOJI_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMOJI_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
pub static MAX_ATTACHMENT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_ATTACHMENT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
pub static MAX_REPLY_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_REPLY_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap()); pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap());
pub fn preflight_checks() { pub fn preflight_checks() {
+6
View File
@@ -1,5 +1,11 @@
version: "3.3" version: "3.3"
services: services:
# Redis
redis:
image: eqalpha/keydb
ports:
- "6379:6379"
# MongoDB # MongoDB
database: database:
image: mongo image: mongo
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
set -eu
case "${TARGETARCH}" in
"amd64")
LINKER_NAME="x86_64-linux-gnu-gcc"
LINKER_PACKAGE="gcc-x86-64-linux-gnu"
BUILD_TARGET="x86_64-unknown-linux-gnu" ;;
"arm64")
LINKER_NAME="aarch64-linux-gnu-gcc"
LINKER_PACKAGE="gcc-aarch64-linux-gnu"
BUILD_TARGET="aarch64-unknown-linux-gnu" ;;
esac
tools() {
apt-get install -y "${LINKER_PACKAGE}"
rustup target add "${BUILD_TARGET}"
}
deps() {
mkdir -p \
crates/bonfire/src \
crates/delta/src \
crates/quark/src
echo 'fn main() { panic!("stub"); }' |
tee crates/bonfire/src/main.rs |
tee crates/delta/src/main.rs
echo '' |
tee crates/quark/src/lib.rs
cargo build --locked --release --target "${BUILD_TARGET}"
}
apps() {
touch -am \
crates/bonfire/src/main.rs \
crates/delta/src/main.rs \
crates/quark/src/lib.rs
cargo build --locked --release --target "${BUILD_TARGET}"
mv target _target && mv _target/"${BUILD_TARGET}" target
}
export RUSTFLAGS="-C linker=${LINKER_NAME}"
export PKG_CONFIG_ALLOW_CROSS="1"
export PKG_CONFIG_PATH="/usr/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig"
"$@"