Start basic work on tunneling protocol and move some code into a common crate
This commit is contained in:
1
.idea/code.iml
generated
1
.idea/code.iml
generated
@@ -10,6 +10,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/app-lib/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/rust-common/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
80
Cargo.lock
generated
80
Cargo.lock
generated
@@ -1204,7 +1204,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fnv",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1291,7 +1291,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio 1.42.0",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1995,7 +1995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2511,7 +2511,7 @@ checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"flume",
|
||||
"half",
|
||||
"half 2.4.1",
|
||||
"lebe",
|
||||
"miniz_oxide 0.7.4",
|
||||
"rayon-core",
|
||||
@@ -3242,6 +3242,12 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
@@ -4245,6 +4251,7 @@ dependencies = [
|
||||
"deadpool-redis",
|
||||
"derive-new",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"env_logger",
|
||||
"flate2",
|
||||
"futures 0.3.30",
|
||||
@@ -4270,6 +4277,7 @@ dependencies = [
|
||||
"redis",
|
||||
"regex",
|
||||
"reqwest 0.11.27",
|
||||
"rust-common",
|
||||
"rust-s3",
|
||||
"rust_decimal",
|
||||
"rust_iso3166",
|
||||
@@ -4277,6 +4285,7 @@ dependencies = [
|
||||
"sentry",
|
||||
"sentry-actix",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha1 0.6.1",
|
||||
@@ -4290,7 +4299,7 @@ dependencies = [
|
||||
"totp-rs",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"validator",
|
||||
"webp",
|
||||
"woothee",
|
||||
@@ -4657,7 +4666,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror 1.0.64",
|
||||
"time",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"yaup",
|
||||
@@ -6724,7 +6733,7 @@ dependencies = [
|
||||
"rkyv_derive",
|
||||
"seahash",
|
||||
"tinyvec",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6758,6 +6767,22 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-ws",
|
||||
"chrono",
|
||||
"either",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_cbor",
|
||||
"serde_json",
|
||||
"thiserror 2.0.7",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.18.0"
|
||||
@@ -7108,7 +7133,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7361,7 +7386,7 @@ dependencies = [
|
||||
"thiserror 1.0.64",
|
||||
"time",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7396,6 +7421,25 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
||||
dependencies = [
|
||||
"half 1.8.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
@@ -8536,7 +8580,7 @@ dependencies = [
|
||||
"thiserror 2.0.7",
|
||||
"time",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -8629,7 +8673,7 @@ dependencies = [
|
||||
"thiserror 2.0.7",
|
||||
"toml 0.8.19",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8810,7 +8854,7 @@ dependencies = [
|
||||
"toml 0.8.19",
|
||||
"url",
|
||||
"urlpattern",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -8884,6 +8928,7 @@ dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"discord-rich-presence",
|
||||
"dunce",
|
||||
"either",
|
||||
"flate2",
|
||||
"futures 0.3.30",
|
||||
"indicatif",
|
||||
@@ -8895,6 +8940,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest 0.12.7",
|
||||
"rust-common",
|
||||
"serde",
|
||||
"serde_ini",
|
||||
"serde_json",
|
||||
@@ -8913,7 +8959,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"whoami",
|
||||
"winreg 0.52.0",
|
||||
"zip 0.6.6",
|
||||
@@ -8955,7 +9001,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"window-shadows",
|
||||
]
|
||||
|
||||
@@ -8974,7 +9020,7 @@ dependencies = [
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"uuid 1.12.0",
|
||||
"webbrowser",
|
||||
]
|
||||
|
||||
@@ -9819,9 +9865,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.10.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"rand 0.8.5",
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
resolver = '2'
|
||||
members = [
|
||||
'./packages/app-lib',
|
||||
'./packages/rust-common',
|
||||
'./apps/app-playground',
|
||||
'./apps/app',
|
||||
'./apps/labrinth',
|
||||
'./apps/daedalus_client',
|
||||
'./packages/daedalus',
|
||||
'./apps/daedalus_client',
|
||||
'./packages/daedalus',
|
||||
]
|
||||
|
||||
# Optimize for speed and reduce size on release builds
|
||||
@@ -21,4 +22,4 @@ strip = true # Remove debug symbols
|
||||
opt-level = 3
|
||||
|
||||
[patch.crates-io]
|
||||
wry = { git = "https://github.com/modrinth/wry", rev ="cdbf938" }
|
||||
wry = { git = "https://github.com/modrinth/wry", rev ="cdbf938" }
|
||||
|
||||
@@ -37,6 +37,7 @@ hyper = { version = "0.14", features = ["full"] }
|
||||
hyper-tls = "0.5.0"
|
||||
|
||||
serde_json = "1.0"
|
||||
serde_cbor = "0.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_with = "3.0.0"
|
||||
chrono = { version = "0.4.26", features = ["serde"] }
|
||||
@@ -74,6 +75,7 @@ dotenvy = "0.15.7"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.10.1"
|
||||
thiserror = "1.0.56"
|
||||
either = "1.13"
|
||||
|
||||
sqlx = { version = "0.8.2", features = [
|
||||
"runtime-tokio-rustls",
|
||||
@@ -124,6 +126,8 @@ async-stripe = { version = "0.39.1", features = ["runtime-tokio-hyper-rustls"] }
|
||||
rusty-money = "0.4.1"
|
||||
json-patch = "*"
|
||||
|
||||
rust-common = { path = "../../packages/rust-common" }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = "3.4.0"
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ pub enum AuthenticationError {
|
||||
#[error("Error uploading user profile picture")]
|
||||
FileHosting(#[from] FileHostingError),
|
||||
#[error("Error while decoding PAT: {0}")]
|
||||
Decoding(#[from] crate::models::ids::DecodingError),
|
||||
Decoding(#[from] rust_common::ids::DecodingError),
|
||||
#[error("{0}")]
|
||||
Mail(#[from] email::MailError),
|
||||
#[error("Invalid Authentication Credentials")]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::ValidatedRedirectUri;
|
||||
use crate::auth::AuthenticationError;
|
||||
use crate::models::error::ApiError;
|
||||
use crate::models::ids::DecodingError;
|
||||
use actix_web::http::{header::LOCATION, StatusCode};
|
||||
use actix_web::HttpResponse;
|
||||
use rust_common::ids::DecodingError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("{}", .error_type)]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::DatabaseError;
|
||||
use crate::models::ids::base62_impl::to_base62;
|
||||
use crate::models::ids::{random_base62_rng, random_base62_rng_range};
|
||||
use censor::Censor;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use rust_common::ids::base62_impl::to_base62;
|
||||
use rust_common::ids::{random_base62_rng, random_base62_rng_range};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::sqlx_macros::Type;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
database::redis::RedisPool, models::ids::base62_impl::parse_base62,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use dashmap::DashMap;
|
||||
use futures::TryStreamExt;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::pats::Scopes;
|
||||
use chrono::{DateTime, Utc};
|
||||
use dashmap::DashMap;
|
||||
use futures::TryStreamExt;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
|
||||
@@ -6,12 +6,12 @@ use super::{ids::*, User};
|
||||
use crate::database::models;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::projects::{MonetizationStatus, ProjectStatus};
|
||||
use chrono::{DateTime, Utc};
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use futures::TryStreamExt;
|
||||
use itertools::Itertools;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
@@ -300,7 +300,7 @@ impl Project {
|
||||
slug, color, monetization_status, organization_id
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
$12, $13,
|
||||
LOWER($14), $15, $16, $17
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use chrono::{DateTime, Utc};
|
||||
use dashmap::DashMap;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
|
||||
@@ -3,10 +3,10 @@ use super::{CollectionId, ReportId, ThreadId};
|
||||
use crate::database::models;
|
||||
use crate::database::models::{DatabaseError, OrganizationId};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::users::Badges;
|
||||
use chrono::{DateTime, Utc};
|
||||
use dashmap::DashMap;
|
||||
use rust_common::ids::base62_impl::{parse_base62, to_base62};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::models::DatabaseError;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use chrono::{TimeZone, Utc};
|
||||
use dashmap::DashMap;
|
||||
use deadpool_redis::{Config, Runtime};
|
||||
use redis::{cmd, Cmd, ExistenceCheck, SetExpiry, SetOptions};
|
||||
use rust_common::ids::base62_impl::{parse_base62, to_base62};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -297,7 +297,7 @@ pub fn app_setup(
|
||||
}
|
||||
|
||||
let ip_salt = Pepper {
|
||||
pepper: models::ids::Base62Id(models::ids::random_base62(11))
|
||||
pepper: rust_common::ids::Base62Id(rust_common::ids::random_base62(11))
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
|
||||
@@ -16,114 +16,10 @@ pub use super::users::UserId;
|
||||
pub use crate::models::billing::{
|
||||
ChargeId, ProductId, ProductPriceId, UserSubscriptionId,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use rust_common::ids::base62_id_impl;
|
||||
pub use rust_common::ids::Base62Id;
|
||||
|
||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||
/// long when encoded as base62.
|
||||
///
|
||||
/// Uses `rand`'s thread rng on every call.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if `n` is 0 or greater than 11, since a `u64`
|
||||
/// can only represent up to 11 character base62 strings
|
||||
#[inline]
|
||||
pub fn random_base62(n: usize) -> u64 {
|
||||
random_base62_rng(&mut rand::thread_rng(), n)
|
||||
}
|
||||
|
||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||
/// long when encoded as base62, using the given rng.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if `n` is 0 or greater than 11, since a `u64`
|
||||
/// can only represent up to 11 character base62 strings
|
||||
pub fn random_base62_rng<R: rand::RngCore>(rng: &mut R, n: usize) -> u64 {
|
||||
random_base62_rng_range(rng, n, n)
|
||||
}
|
||||
|
||||
pub fn random_base62_rng_range<R: rand::RngCore>(
|
||||
rng: &mut R,
|
||||
n_min: usize,
|
||||
n_max: usize,
|
||||
) -> u64 {
|
||||
use rand::Rng;
|
||||
assert!(n_min > 0 && n_max <= 11 && n_min <= n_max);
|
||||
// gen_range is [low, high): max value is `MULTIPLES[n] - 1`,
|
||||
// which is n characters long when encoded
|
||||
rng.gen_range(MULTIPLES[n_min - 1]..MULTIPLES[n_max])
|
||||
}
|
||||
|
||||
const MULTIPLES: [u64; 12] = [
|
||||
1,
|
||||
62,
|
||||
62 * 62,
|
||||
62 * 62 * 62,
|
||||
62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
u64::MAX,
|
||||
];
|
||||
|
||||
/// An ID encoded as base62 for use in the API.
|
||||
///
|
||||
/// All ids should be random and encode to 8-10 character base62 strings,
|
||||
/// to avoid enumeration and other attacks.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Base62Id(pub u64);
|
||||
|
||||
/// An error decoding a number from base62.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DecodingError {
|
||||
/// Encountered a non-base62 character in a base62 string
|
||||
#[error("Invalid character {0:?} in base62 encoding")]
|
||||
InvalidBase62(char),
|
||||
/// Encountered integer overflow when decoding a base62 id.
|
||||
#[error("Base62 decoding overflowed")]
|
||||
Overflow,
|
||||
}
|
||||
|
||||
macro_rules! from_base62id {
|
||||
($($struct:ty, $con:expr;)+) => {
|
||||
$(
|
||||
impl From<Base62Id> for $struct {
|
||||
fn from(id: Base62Id) -> $struct {
|
||||
$con(id.0)
|
||||
}
|
||||
}
|
||||
impl From<$struct> for Base62Id {
|
||||
fn from(id: $struct) -> Base62Id {
|
||||
Base62Id(id.0)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_base62_display {
|
||||
($struct:ty) => {
|
||||
impl std::fmt::Display for $struct {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&base62_impl::to_base62(self.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_base62_display!(Base62Id);
|
||||
|
||||
macro_rules! base62_id_impl {
|
||||
($struct:ty, $cons:expr) => {
|
||||
from_base62id!($struct, $cons;);
|
||||
impl_base62_display!($struct);
|
||||
}
|
||||
}
|
||||
base62_id_impl!(ProjectId, ProjectId);
|
||||
base62_id_impl!(UserId, UserId);
|
||||
base62_id_impl!(VersionId, VersionId);
|
||||
base62_id_impl!(CollectionId, CollectionId);
|
||||
base62_id_impl!(TeamId, TeamId);
|
||||
@@ -143,91 +39,3 @@ base62_id_impl!(ProductId, ProductId);
|
||||
base62_id_impl!(ProductPriceId, ProductPriceId);
|
||||
base62_id_impl!(UserSubscriptionId, UserSubscriptionId);
|
||||
base62_id_impl!(ChargeId, ChargeId);
|
||||
|
||||
pub mod base62_impl {
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
use serde::ser::Serializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Base62Id, DecodingError};
|
||||
|
||||
impl<'de> Deserialize<'de> for Base62Id {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct Base62Visitor;
|
||||
|
||||
impl Visitor<'_> for Base62Visitor {
|
||||
type Value = Base62Id;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
formatter.write_str("a base62 string id")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, string: &str) -> Result<Base62Id, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
parse_base62(string).map(Base62Id).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(Base62Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Base62Id {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&to_base62(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
const BASE62_CHARS: [u8; 62] =
|
||||
*b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
pub fn to_base62(mut num: u64) -> String {
|
||||
let length = (num as f64).log(62.0).ceil() as usize;
|
||||
let mut output = String::with_capacity(length);
|
||||
|
||||
while num > 0 {
|
||||
// Could be done more efficiently, but requires byte
|
||||
// manipulation of strings & Vec<u8> -> String conversion
|
||||
output.insert(0, BASE62_CHARS[(num % 62) as usize] as char);
|
||||
num /= 62;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_base62(string: &str) -> Result<u64, DecodingError> {
|
||||
let mut num: u64 = 0;
|
||||
for c in string.chars() {
|
||||
let next_digit;
|
||||
if c.is_ascii_digit() {
|
||||
next_digit = (c as u8 - b'0') as u64;
|
||||
} else if c.is_ascii_uppercase() {
|
||||
next_digit = 10 + (c as u8 - b'A') as u64;
|
||||
} else if c.is_ascii_lowercase() {
|
||||
next_digit = 36 + (c as u8 - b'a') as u64;
|
||||
} else {
|
||||
return Err(DecodingError::InvalidBase62(c));
|
||||
}
|
||||
|
||||
// We don't want this panicking or wrapping on integer overflow
|
||||
if let Some(n) =
|
||||
num.checked_mul(62).and_then(|n| n.checked_add(next_digit))
|
||||
{
|
||||
num = n;
|
||||
} else {
|
||||
return Err(DecodingError::Overflow);
|
||||
}
|
||||
}
|
||||
Ok(num)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use super::ids::Base62Id;
|
||||
use crate::{auth::AuthProvider, bitflags_serde_impl};
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use rust_common::users::{UserId, UserStatus};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
|
||||
#[serde(from = "Base62Id")]
|
||||
#[serde(into = "Base62Id")]
|
||||
pub struct UserId(pub u64);
|
||||
|
||||
pub const DELETED_USER: UserId = UserId(127155982985829);
|
||||
|
||||
bitflags::bitflags! {
|
||||
@@ -211,10 +206,3 @@ impl UserFriend {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct UserStatus {
|
||||
pub user_id: UserId,
|
||||
pub profile_name: Option<String>,
|
||||
pub last_update: DateTime<Utc>,
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ use crate::search::SearchConfig;
|
||||
use crate::util::date::get_current_tenths_of_ms;
|
||||
use crate::util::guards::admin_key_guard;
|
||||
use actix_web::{get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use log::info;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use log::info;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
@@ -74,7 +74,7 @@ pub async fn count_download(
|
||||
let project_id: crate::database::models::ids::ProjectId =
|
||||
download_body.project_id.into();
|
||||
|
||||
let id_option = crate::models::ids::base62_impl::parse_base62(
|
||||
let id_option = rust_common::ids::base62_impl::parse_base62(
|
||||
&download_body.version_name,
|
||||
)
|
||||
.ok()
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::models::billing::{
|
||||
Product, ProductMetadata, ProductPrice, SubscriptionMetadata,
|
||||
SubscriptionStatus, UserSubscription,
|
||||
};
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::Badges;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -18,6 +17,7 @@ use crate::routes::ApiError;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use log::{info, warn};
|
||||
use rust_common::ids::base62_impl::{parse_base62, to_base62};
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -4,8 +4,6 @@ use crate::auth::{get_user_from_headers, AuthProvider, AuthenticationError};
|
||||
use crate::database::models::flow_item::Flow;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::ids::random_base62_rng;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::{Badges, Role};
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -25,6 +23,8 @@ use chrono::{Duration, Utc};
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
use rust_common::ids::base62_impl::{parse_base62, to_base62};
|
||||
use rust_common::ids::random_base62_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::ApiError;
|
||||
use crate::database;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::random_base62;
|
||||
use crate::models::projects::ProjectStatus;
|
||||
use crate::queue::moderation::{ApprovalType, IdentifiedFile, MissingMetadata};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::{auth::check_is_moderator_from_headers, models::pats::Scopes};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use rust_common::ids::random_base62;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -2,9 +2,8 @@ use crate::auth::validate::get_user_record_from_bearer_token;
|
||||
use crate::auth::AuthenticationError;
|
||||
use crate::database::models::friend_item::FriendItem;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::UserId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::{User, UserStatus};
|
||||
use crate::models::users::User;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::queue::socket::ActiveSockets;
|
||||
use crate::routes::ApiError;
|
||||
@@ -12,30 +11,20 @@ use actix_web::web::{Data, Payload};
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use actix_ws::Message;
|
||||
use chrono::Utc;
|
||||
use either::Either;
|
||||
use futures_util::{StreamExt, TryStreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use rust_common::ids::UserId;
|
||||
use rust_common::networking::message::{
|
||||
ClientToServerMessage, ServerToClientMessage,
|
||||
};
|
||||
use rust_common::users::UserStatus;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(ws_init);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ClientToServerMessage {
|
||||
StatusUpdate { profile_name: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ServerToClientMessage {
|
||||
StatusUpdate { status: UserStatus },
|
||||
UserOffline { id: UserId },
|
||||
FriendStatuses { statuses: Vec<UserStatus> },
|
||||
FriendRequest { from: UserId },
|
||||
FriendRequestRejected { from: UserId },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LauncherHeartbeatInit {
|
||||
code: String,
|
||||
@@ -133,57 +122,59 @@ pub async fn ws_init(
|
||||
actix_web::rt::spawn(async move {
|
||||
// receive messages from websocket
|
||||
while let Some(msg) = stream.next().await {
|
||||
match msg {
|
||||
Ok(Message::Text(text)) => {
|
||||
if let Ok(message) =
|
||||
serde_json::from_str::<ClientToServerMessage>(&text)
|
||||
{
|
||||
match message {
|
||||
ClientToServerMessage::StatusUpdate {
|
||||
profile_name,
|
||||
} => {
|
||||
if let Some(mut pair) =
|
||||
db.auth_sockets.get_mut(&user.id)
|
||||
if msg.is_err() {
|
||||
continue;
|
||||
}
|
||||
match ClientToServerMessage::deserialize(msg.unwrap()) {
|
||||
Ok(Either::Left(message)) => {
|
||||
match message {
|
||||
ClientToServerMessage::StatusUpdate {
|
||||
profile_name,
|
||||
} => {
|
||||
if let Some(mut pair) =
|
||||
db.auth_sockets.get_mut(&user.id)
|
||||
{
|
||||
let (status, _) = pair.value_mut();
|
||||
|
||||
if status
|
||||
.profile_name
|
||||
.as_ref()
|
||||
.map(|x| x.len() > 64)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let (status, _) = pair.value_mut();
|
||||
|
||||
if status
|
||||
.profile_name
|
||||
.as_ref()
|
||||
.map(|x| x.len() > 64)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
status.profile_name = profile_name;
|
||||
status.last_update = Utc::now();
|
||||
|
||||
let user_status = status.clone();
|
||||
// We drop the pair to avoid holding the lock for too long
|
||||
drop(pair);
|
||||
|
||||
let _ = broadcast_friends(
|
||||
user.id,
|
||||
ServerToClientMessage::StatusUpdate {
|
||||
status: user_status,
|
||||
},
|
||||
&pool,
|
||||
&db,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
status.profile_name = profile_name;
|
||||
status.last_update = Utc::now();
|
||||
|
||||
let user_status = status.clone();
|
||||
// We drop the pair to avoid holding the lock for too long
|
||||
drop(pair);
|
||||
|
||||
let _ = broadcast_friends(
|
||||
user.id,
|
||||
ServerToClientMessage::StatusUpdate {
|
||||
status: user_status,
|
||||
},
|
||||
&pool,
|
||||
&db,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
ClientToServerMessage::SocketOpen
|
||||
| ClientToServerMessage::SocketClose { .. }
|
||||
| ClientToServerMessage::SocketSend { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Message::Close(_)) => {
|
||||
Ok(Either::Right(Message::Close(_))) => {
|
||||
let _ = close_socket(user.id, &pool, &db).await;
|
||||
}
|
||||
|
||||
Ok(Message::Ping(msg)) => {
|
||||
Ok(Either::Right(Message::Ping(msg))) => {
|
||||
if let Some(socket) = db.auth_sockets.get(&user.id) {
|
||||
let (_, socket) = socket.value();
|
||||
let _ = socket.clone().pong(&msg).await;
|
||||
@@ -207,6 +198,7 @@ pub async fn broadcast_friends(
|
||||
sockets: &ActiveSockets,
|
||||
friends: Option<Vec<FriendItem>>,
|
||||
) -> Result<(), crate::database::models::DatabaseError> {
|
||||
// FIXME Probably shouldn't be using database errors for this
|
||||
let friends = if let Some(friends) = friends {
|
||||
friends
|
||||
} else {
|
||||
@@ -224,8 +216,7 @@ pub async fn broadcast_friends(
|
||||
if let Some(socket) = sockets.auth_sockets.get(&friend_id.into()) {
|
||||
let (_, socket) = socket.value();
|
||||
|
||||
let _ =
|
||||
socket.clone().text(serde_json::to_string(&message)?).await;
|
||||
let _ = message.send(&mut socket.clone()).await; // FIXME Probably shouldn't swallow this error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ async fn find_version(
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<QueryVersion>, ApiError> {
|
||||
let id_option = crate::models::ids::base62_impl::parse_base62(vcoords)
|
||||
let id_option = rust_common::ids::base62_impl::parse_base62(vcoords)
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ pub enum ApiError {
|
||||
#[error("Captcha Error. Try resubmitting the form.")]
|
||||
Turnstile,
|
||||
#[error("Error while decoding Base62: {0}")]
|
||||
Decoding(#[from] crate::models::ids::DecodingError),
|
||||
Decoding(#[from] rust_common::ids::DecodingError),
|
||||
#[error("Image Parsing Error: {0}")]
|
||||
ImageParse(#[from] image::ImageError),
|
||||
#[error("Password Hashing Error: {0}")]
|
||||
|
||||
@@ -6,13 +6,14 @@ use crate::{
|
||||
auth::get_user_from_headers,
|
||||
database::models::user_item,
|
||||
models::{
|
||||
ids::{base62_impl::to_base62, ProjectId, VersionId},
|
||||
ids::{ProjectId, VersionId},
|
||||
pats::Scopes,
|
||||
},
|
||||
queue::session::AuthQueue,
|
||||
};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use rust_common::ids::base62_impl::to_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::types::PgInterval;
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::database::models::{
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::collections::{Collection, CollectionStatus};
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::ids::{CollectionId, ProjectId};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -20,6 +19,7 @@ use actix_web::web::Data;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use itertools::Itertools;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -5,10 +5,11 @@ use crate::models::pats::Scopes;
|
||||
use crate::models::users::UserFriend;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::queue::socket::ActiveSockets;
|
||||
use crate::routes::internal::statuses::{close_socket, ServerToClientMessage};
|
||||
use crate::routes::internal::statuses::close_socket;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use rust_common::networking::message::ServerToClientMessage;
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
use std::{collections::HashSet, fmt::Display, sync::Arc};
|
||||
|
||||
use actix_web::{
|
||||
delete, get, patch, post,
|
||||
web::{self, scope},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use itertools::Itertools;
|
||||
use rand::{distributions::Alphanumeric, Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
|
||||
use super::ApiError;
|
||||
use crate::{
|
||||
auth::{checks::ValidateAuthorized, get_user_from_headers},
|
||||
@@ -35,13 +22,22 @@ use crate::{
|
||||
util::validate::validation_errors_to_string,
|
||||
};
|
||||
use crate::{
|
||||
file_hosting::FileHost,
|
||||
models::{
|
||||
ids::base62_impl::parse_base62,
|
||||
oauth_clients::DeleteOAuthClientQueryParam,
|
||||
},
|
||||
file_hosting::FileHost, models::oauth_clients::DeleteOAuthClientQueryParam,
|
||||
util::routes::read_from_payload,
|
||||
};
|
||||
use actix_web::{
|
||||
delete, get, patch, post,
|
||||
web::{self, scope},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use itertools::Itertools;
|
||||
use rand::{distributions::Alphanumeric, Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::database::models::oauth_client_item::OAuthClient as DBOAuthClient;
|
||||
use crate::models::ids::OAuthClientId as ApiOAuthClientId;
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::database::models::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::ids::UserId;
|
||||
use crate::models::organizations::OrganizationId;
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -22,6 +21,7 @@ use crate::util::validate::validation_errors_to_string;
|
||||
use crate::{database, models};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use futures::TryStreamExt;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
@@ -786,7 +786,7 @@ pub async fn organization_projects_add(
|
||||
|
||||
let organization_owner_user_id = sqlx::query!(
|
||||
"
|
||||
SELECT u.id
|
||||
SELECT u.id
|
||||
FROM team_members
|
||||
INNER JOIN users u ON u.id = team_members.user_id
|
||||
WHERE team_id = $1 AND is_owner = TRUE
|
||||
@@ -969,7 +969,7 @@ pub async fn organization_projects_remove(
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE team_members
|
||||
SET
|
||||
SET
|
||||
is_owner = TRUE,
|
||||
accepted = TRUE,
|
||||
permissions = $2,
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::database::models::{self, image_item, User};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::{FileHost, FileHostingError};
|
||||
use crate::models::error::ApiError;
|
||||
use crate::models::ids::base62_impl::to_base62;
|
||||
use crate::models::ids::{ImageId, OrganizationId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -32,6 +31,7 @@ use chrono::Utc;
|
||||
use futures::stream::StreamExt;
|
||||
use image::ImageError;
|
||||
use itertools::Itertools;
|
||||
use rust_common::ids::base62_impl::to_base62;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::database::redis::RedisPool;
|
||||
use crate::database::{self, models as db_models};
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::images::ImageContext;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -33,6 +32,7 @@ use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::TryStreamExt;
|
||||
use itertools::Itertools;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -6,9 +6,7 @@ use crate::database::models::thread_item::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::ids::{
|
||||
base62_impl::parse_base62, ProjectId, UserId, VersionId,
|
||||
};
|
||||
use crate::models::ids::{ProjectId, UserId, VersionId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::reports::{ItemType, Report};
|
||||
@@ -19,6 +17,7 @@ use crate::util::img;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::StreamExt;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::database::models::version_item::{DependencyBuilder, LoaderVersion};
|
||||
use crate::database::models::{image_item, Organization};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models;
|
||||
use crate::models::ids::base62_impl::parse_base62;
|
||||
use crate::models::ids::VersionId;
|
||||
use crate::models::images::ImageContext;
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -29,6 +28,7 @@ use crate::util::img;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use itertools::Itertools;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use validator::Validate;
|
||||
@@ -444,7 +444,7 @@ pub async fn version_edit_helper(
|
||||
.collect::<Vec<i32>>();
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM version_fields
|
||||
DELETE FROM version_fields
|
||||
WHERE version_id = $1
|
||||
AND field_id = ANY($2)
|
||||
",
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
pub mod local_import;
|
||||
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::to_base62;
|
||||
use crate::search::{SearchConfig, UploadSearchProject};
|
||||
use local_import::index_local;
|
||||
use log::info;
|
||||
use meilisearch_sdk::client::{Client, SwapIndexes};
|
||||
use meilisearch_sdk::indexes::Index;
|
||||
use meilisearch_sdk::settings::{PaginationSetting, Settings};
|
||||
use rust_common::ids::base62_impl::to_base62;
|
||||
use sqlx::postgres::PgPool;
|
||||
use thiserror::Error;
|
||||
#[derive(Error, Debug)]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::base62_impl::to_base62;
|
||||
use crate::models::projects::ProjectId;
|
||||
use crate::routes::ApiError;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_common::ids::base62_impl::to_base62;
|
||||
use serde::Serialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ use common::{
|
||||
environment::{with_test_environment, TestEnvironment},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::teams::ProjectPermissions;
|
||||
use labrinth::queue::payouts;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use rust_decimal::{prelude::ToPrimitive, Decimal};
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -12,10 +12,10 @@ use futures::StreamExt;
|
||||
use labrinth::database::models::project_item::{
|
||||
PROJECTS_NAMESPACE, PROJECTS_SLUGS_NAMESPACE,
|
||||
};
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use labrinth::models::teams::ProjectPermissions;
|
||||
use labrinth::util::actix::{MultipartSegment, MultipartSegmentData};
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::api_common::models::CommonProject;
|
||||
|
||||
@@ -18,10 +18,10 @@ use common::environment::{
|
||||
with_test_environment, with_test_environment_all, TestEnvironment,
|
||||
};
|
||||
use common::{database::*, scopes::ScopeTest};
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::pats::Scopes;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use labrinth::models::users::UserId;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde_json::json;
|
||||
|
||||
// For each scope, we (using test_scope):
|
||||
|
||||
@@ -8,7 +8,7 @@ use common::environment::with_test_environment;
|
||||
use common::environment::TestEnvironment;
|
||||
use common::search::setup_search_projects;
|
||||
use futures::stream::StreamExt;
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::api_common::Api;
|
||||
|
||||
@@ -20,12 +20,10 @@ use futures::StreamExt;
|
||||
use itertools::Itertools;
|
||||
use labrinth::{
|
||||
database::models::project_item::PROJECTS_SLUGS_NAMESPACE,
|
||||
models::{
|
||||
ids::base62_impl::parse_base62, projects::ProjectId,
|
||||
teams::ProjectPermissions,
|
||||
},
|
||||
models::{projects::ProjectId, teams::ProjectPermissions},
|
||||
util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData},
|
||||
};
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde_json::json;
|
||||
|
||||
#[actix_rt::test]
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::common::dummy_data::TestFile;
|
||||
use crate::common::environment::with_test_environment;
|
||||
use crate::common::environment::TestEnvironment;
|
||||
use crate::common::scopes::ScopeTest;
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::pats::Scopes;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
|
||||
// Project version creation scopes
|
||||
#[actix_rt::test]
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::common::environment::with_test_environment;
|
||||
use crate::common::environment::TestEnvironment;
|
||||
use actix_http::StatusCode;
|
||||
use futures::stream::StreamExt;
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -14,11 +14,11 @@ use common::database::USER_USER_PAT;
|
||||
use common::environment::{with_test_environment, with_test_environment_all};
|
||||
use futures::StreamExt;
|
||||
use labrinth::database::models::version_item::VERSIONS_NAMESPACE;
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::projects::{
|
||||
Dependency, DependencyType, VersionId, VersionStatus, VersionType,
|
||||
};
|
||||
use labrinth::routes::v3::version_file::FileUpdateData;
|
||||
use rust_common::ids::base62_impl::parse_base62;
|
||||
use serde_json::json;
|
||||
|
||||
// importing common module.
|
||||
|
||||
@@ -29,6 +29,7 @@ regex = "1.5"
|
||||
sys-info = "0.9.0"
|
||||
sysinfo = "0.30.8"
|
||||
thiserror = "1.0"
|
||||
either = "1.13"
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["chrono", "env-filter"] }
|
||||
@@ -62,6 +63,8 @@ base64 = "0.22.0"
|
||||
|
||||
sqlx = { version = "0.8.2", features = [ "runtime-tokio", "sqlite", "macros" ] }
|
||||
|
||||
rust-common = { path = "../rust-common" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.52.0"
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::state::{FriendsSocket, UserFriend, UserStatus};
|
||||
use crate::state::{FriendsSocket, UserFriend};
|
||||
use rust_common::users::UserStatus;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn friends() -> crate::Result<Vec<UserFriend>> {
|
||||
|
||||
@@ -19,8 +19,9 @@ pub mod data {
|
||||
Hooks, JavaVersion, LinkedData, MemorySettings, ModLoader,
|
||||
ModrinthCredentials, Organization, ProcessMetadata, ProfileFile,
|
||||
Project, ProjectType, SearchResult, SearchResults, Settings,
|
||||
TeamMember, Theme, User, UserFriend, UserStatus, Version, WindowSize,
|
||||
TeamMember, Theme, User, UserFriend, Version, WindowSize,
|
||||
};
|
||||
pub use rust_common::users::UserStatus;
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Theseus state management system
|
||||
use crate::state::UserStatus;
|
||||
use dashmap::DashMap;
|
||||
use rust_common::users::{UserId, UserStatus};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
#[cfg(feature = "tauri")]
|
||||
@@ -262,8 +262,8 @@ pub enum EventError {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "event")]
|
||||
pub enum FriendPayload {
|
||||
FriendRequest { from: String },
|
||||
UserOffline { id: String },
|
||||
FriendRequest { from: UserId },
|
||||
UserOffline { id: UserId },
|
||||
StatusUpdate { user_status: UserStatus },
|
||||
StatusSync,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ use futures::stream::SplitSink;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::Method;
|
||||
use rust_common::networking::message::{
|
||||
ClientToServerMessage, ServerToClientMessage,
|
||||
};
|
||||
use rust_common::users::{UserId, UserStatus};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -23,7 +27,7 @@ type WriteSocket =
|
||||
|
||||
pub struct FriendsSocket {
|
||||
write: WriteSocket,
|
||||
user_statuses: Arc<DashMap<String, UserStatus>>,
|
||||
user_statuses: Arc<DashMap<UserId, UserStatus>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
@@ -34,28 +38,6 @@ pub struct UserFriend {
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ClientToServerMessage {
|
||||
StatusUpdate { profile_name: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ServerToClientMessage {
|
||||
StatusUpdate { status: UserStatus },
|
||||
UserOffline { id: String },
|
||||
FriendStatuses { statuses: Vec<UserStatus> },
|
||||
FriendRequest { from: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct UserStatus {
|
||||
pub user_id: String,
|
||||
pub profile_name: Option<String>,
|
||||
pub last_update: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Default for FriendsSocket {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -126,6 +108,7 @@ impl FriendsSocket {
|
||||
while let Some(msg_result) = read_stream.next().await {
|
||||
match msg_result {
|
||||
Ok(msg) => {
|
||||
// TODO: Make wire package work with this other library
|
||||
let server_message = match msg {
|
||||
Message::Text(text) => {
|
||||
serde_json::from_str::<
|
||||
@@ -165,7 +148,7 @@ impl FriendsSocket {
|
||||
{
|
||||
match server_message {
|
||||
ServerToClientMessage::StatusUpdate { status } => {
|
||||
statuses.insert(status.user_id.clone(), status.clone());
|
||||
statuses.insert(status.user_id, status.clone());
|
||||
let _ = emit_friend(FriendPayload::StatusUpdate { user_status: status }).await;
|
||||
},
|
||||
ServerToClientMessage::UserOffline { id } => {
|
||||
@@ -175,13 +158,18 @@ impl FriendsSocket {
|
||||
ServerToClientMessage::FriendStatuses { statuses: new_statuses } => {
|
||||
statuses.clear();
|
||||
new_statuses.into_iter().for_each(|status| {
|
||||
statuses.insert(status.user_id.clone(), status);
|
||||
statuses.insert(status.user_id, status);
|
||||
});
|
||||
let _ = emit_friend(FriendPayload::StatusSync).await;
|
||||
}
|
||||
ServerToClientMessage::FriendRequest { from } => {
|
||||
let _ = emit_friend(FriendPayload::FriendRequest { from }).await;
|
||||
}
|
||||
ServerToClientMessage::FriendRequestRejected { .. } => todo!(),
|
||||
ServerToClientMessage::SocketOpened { .. } => todo!(),
|
||||
ServerToClientMessage::SocketClosed { .. } => todo!(),
|
||||
ServerToClientMessage::FriendSocketOpened { .. } => todo!(),
|
||||
ServerToClientMessage::SocketData { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
packages/rust-common/Cargo.toml
Normal file
19
packages/rust-common/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "rust-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_bytes = "0.11"
|
||||
serde_json = "1.0"
|
||||
serde_cbor = "0.11"
|
||||
|
||||
uuid = { version = "1.12", features = ["serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
actix-ws = "0.3"
|
||||
|
||||
thiserror = "2.0"
|
||||
rand = "0.8"
|
||||
either = "1.13"
|
||||
9
packages/rust-common/package.json
Normal file
9
packages/rust-common/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@modrinth/networking",
|
||||
"scripts": {
|
||||
"build": "cargo build --release",
|
||||
"lint": "cargo fmt --check && cargo clippy --all-targets -- -D warnings",
|
||||
"fix": "cargo fmt && cargo clippy --fix",
|
||||
"test": "cargo test"
|
||||
}
|
||||
}
|
||||
201
packages/rust-common/src/ids.rs
Normal file
201
packages/rust-common/src/ids.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
pub use super::users::UserId;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||
/// long when encoded as base62.
|
||||
///
|
||||
/// Uses `rand`'s thread rng on every call.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if `n` is 0 or greater than 11, since a `u64`
|
||||
/// can only represent up to 11 character base62 strings
|
||||
#[inline]
|
||||
pub fn random_base62(n: usize) -> u64 {
|
||||
random_base62_rng(&mut rand::thread_rng(), n)
|
||||
}
|
||||
|
||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||
/// long when encoded as base62, using the given rng.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if `n` is 0 or greater than 11, since a `u64`
|
||||
/// can only represent up to 11 character base62 strings
|
||||
pub fn random_base62_rng<R: rand::RngCore>(rng: &mut R, n: usize) -> u64 {
|
||||
random_base62_rng_range(rng, n, n)
|
||||
}
|
||||
|
||||
pub fn random_base62_rng_range<R: rand::RngCore>(
|
||||
rng: &mut R,
|
||||
n_min: usize,
|
||||
n_max: usize,
|
||||
) -> u64 {
|
||||
use rand::Rng;
|
||||
assert!(n_min > 0 && n_max <= 11 && n_min <= n_max);
|
||||
// gen_range is [low, high): max value is `MULTIPLES[n] - 1`,
|
||||
// which is n characters long when encoded
|
||||
rng.gen_range(MULTIPLES[n_min - 1]..MULTIPLES[n_max])
|
||||
}
|
||||
|
||||
const MULTIPLES: [u64; 12] = [
|
||||
1,
|
||||
62,
|
||||
62 * 62,
|
||||
62 * 62 * 62,
|
||||
62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
u64::MAX,
|
||||
];
|
||||
|
||||
/// An ID encoded as base62 for use in the API.
|
||||
///
|
||||
/// All ids should be random and encode to 8-10 character base62 strings,
|
||||
/// to avoid enumeration and other attacks.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Base62Id(pub u64);
|
||||
|
||||
/// An error decoding a number from base62.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DecodingError {
|
||||
/// Encountered a non-base62 character in a base62 string
|
||||
#[error("Invalid character {0:?} in base62 encoding")]
|
||||
InvalidBase62(char),
|
||||
/// Encountered integer overflow when decoding a base62 id.
|
||||
#[error("Base62 decoding overflowed")]
|
||||
Overflow,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! from_base62id {
|
||||
($($struct:ty, $con:expr;)+) => {
|
||||
$(
|
||||
impl From<Base62Id> for $struct {
|
||||
fn from(id: Base62Id) -> $struct {
|
||||
$con(id.0)
|
||||
}
|
||||
}
|
||||
impl From<$struct> for Base62Id {
|
||||
fn from(id: $struct) -> Base62Id {
|
||||
Base62Id(id.0)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_base62_display {
|
||||
($struct:ty) => {
|
||||
impl std::fmt::Display for $struct {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&$crate::ids::base62_impl::to_base62(self.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_base62_display!(Base62Id);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! base62_id_impl {
|
||||
($struct:ty, $cons:expr) => {
|
||||
$crate::ids::from_base62id!($struct, $cons;);
|
||||
$crate::ids::impl_base62_display!($struct);
|
||||
}
|
||||
}
|
||||
base62_id_impl!(UserId, UserId);
|
||||
|
||||
pub use {base62_id_impl, from_base62id, impl_base62_display};
|
||||
|
||||
pub mod base62_impl {
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
use serde::ser::Serializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Base62Id, DecodingError};
|
||||
|
||||
impl<'de> Deserialize<'de> for Base62Id {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct Base62Visitor;
|
||||
|
||||
impl Visitor<'_> for Base62Visitor {
|
||||
type Value = Base62Id;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
formatter.write_str("a base62 string id")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, string: &str) -> Result<Base62Id, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
parse_base62(string).map(Base62Id).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(Base62Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Base62Id {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&to_base62(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
const BASE62_CHARS: [u8; 62] =
|
||||
*b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
pub fn to_base62(mut num: u64) -> String {
|
||||
let length = (num as f64).log(62.0).ceil() as usize;
|
||||
let mut output = String::with_capacity(length);
|
||||
|
||||
while num > 0 {
|
||||
// Could be done more efficiently, but requires byte
|
||||
// manipulation of strings & Vec<u8> -> String conversion
|
||||
output.insert(0, BASE62_CHARS[(num % 62) as usize] as char);
|
||||
num /= 62;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_base62(string: &str) -> Result<u64, DecodingError> {
|
||||
let mut num: u64 = 0;
|
||||
for c in string.chars() {
|
||||
let next_digit;
|
||||
if c.is_ascii_digit() {
|
||||
next_digit = (c as u8 - b'0') as u64;
|
||||
} else if c.is_ascii_uppercase() {
|
||||
next_digit = 10 + (c as u8 - b'A') as u64;
|
||||
} else if c.is_ascii_lowercase() {
|
||||
next_digit = 36 + (c as u8 - b'a') as u64;
|
||||
} else {
|
||||
return Err(DecodingError::InvalidBase62(c));
|
||||
}
|
||||
|
||||
// We don't want this panicking or wrapping on integer overflow
|
||||
if let Some(n) =
|
||||
num.checked_mul(62).and_then(|n| n.checked_add(next_digit))
|
||||
{
|
||||
num = n;
|
||||
} else {
|
||||
return Err(DecodingError::Overflow);
|
||||
}
|
||||
}
|
||||
Ok(num)
|
||||
}
|
||||
}
|
||||
3
packages/rust-common/src/lib.rs
Normal file
3
packages/rust-common/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod ids;
|
||||
pub mod networking;
|
||||
pub mod users;
|
||||
58
packages/rust-common/src/networking/message.rs
Normal file
58
packages/rust-common/src/networking/message.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use crate::ids::UserId;
|
||||
use crate::users::UserStatus;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ClientToServerMessage {
|
||||
StatusUpdate {
|
||||
profile_name: Option<String>,
|
||||
},
|
||||
|
||||
SocketOpen,
|
||||
SocketClose {
|
||||
socket: Uuid,
|
||||
},
|
||||
SocketSend {
|
||||
socket: Uuid,
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ServerToClientMessage {
|
||||
StatusUpdate {
|
||||
status: UserStatus,
|
||||
},
|
||||
UserOffline {
|
||||
id: UserId,
|
||||
},
|
||||
FriendStatuses {
|
||||
statuses: Vec<UserStatus>,
|
||||
},
|
||||
FriendRequest {
|
||||
from: UserId,
|
||||
},
|
||||
FriendRequestRejected {
|
||||
from: UserId,
|
||||
},
|
||||
|
||||
SocketOpened {
|
||||
socket: Uuid,
|
||||
},
|
||||
SocketClosed {
|
||||
socket: Uuid,
|
||||
},
|
||||
FriendSocketOpened {
|
||||
user: UserId,
|
||||
socket: Uuid,
|
||||
},
|
||||
SocketData {
|
||||
socket: Uuid,
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
2
packages/rust-common/src/networking/mod.rs
Normal file
2
packages/rust-common/src/networking/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod message;
|
||||
pub mod wire;
|
||||
72
packages/rust-common/src/networking/wire.rs
Normal file
72
packages/rust-common/src/networking/wire.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use super::message::{ClientToServerMessage, ServerToClientMessage};
|
||||
use actix_ws::{Closed, Message, Session};
|
||||
use either::Either;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DeserializationError {
|
||||
#[error("Failed to deserialize message: {0}")]
|
||||
DeserializationFailed(#[from] serde_json::Error),
|
||||
|
||||
#[error("Failed to deserialize binary message: {0}")]
|
||||
BinaryDeserializationFailed(#[from] serde_cbor::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SendError {
|
||||
#[error("Failed to serialize message: {0}")]
|
||||
SerializationFailed(#[from] serde_json::Error),
|
||||
|
||||
#[error("Failed to serialize binary message: {0}")]
|
||||
BinarySerializationFailed(#[from] serde_cbor::Error),
|
||||
|
||||
#[error("Websocket closed")]
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl From<Closed> for SendError {
|
||||
fn from(_: Closed) -> Self {
|
||||
SendError::Closed
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! message_wire {
|
||||
($message_enum:ty, $binary_pattern:pat) => {
|
||||
impl $message_enum {
|
||||
pub fn deserialize(
|
||||
msg: Message,
|
||||
) -> Result<Either<Self, Message>, DeserializationError> {
|
||||
Ok(match msg {
|
||||
Message::Text(text) => {
|
||||
Either::Left(serde_json::from_str(&text)?)
|
||||
}
|
||||
Message::Binary(bytes) => {
|
||||
Either::Left(serde_cbor::from_slice(&bytes)?)
|
||||
}
|
||||
other => Either::Right(other),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send(
|
||||
&self,
|
||||
session: &mut Session,
|
||||
) -> Result<(), SendError> {
|
||||
Ok(match self {
|
||||
$binary_pattern => {
|
||||
session.binary(serde_cbor::to_vec(self)?).await?
|
||||
}
|
||||
_ => session.text(serde_json::to_string(self)?).await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
message_wire!(
|
||||
ClientToServerMessage,
|
||||
ClientToServerMessage::SocketSend { .. }
|
||||
);
|
||||
message_wire!(
|
||||
ServerToClientMessage,
|
||||
ServerToClientMessage::SocketData { .. }
|
||||
);
|
||||
15
packages/rust-common/src/users.rs
Normal file
15
packages/rust-common/src/users.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use super::ids::Base62Id;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
|
||||
#[serde(from = "Base62Id")]
|
||||
#[serde(into = "Base62Id")]
|
||||
pub struct UserId(pub u64);
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct UserStatus {
|
||||
pub user_id: UserId,
|
||||
pub profile_name: Option<String>,
|
||||
pub last_update: DateTime<Utc>,
|
||||
}
|
||||
Reference in New Issue
Block a user