Compare commits

..

8 Commits

Author SHA1 Message Date
izzy
050ba16d4a fix: correct shebang for try-tag-and-release 2025-08-07 12:58:52 +02:00
izzy
65bc6c8fc6 chore: add just tool to nix shell 2025-08-07 12:58:15 +02:00
izzy
6ad3da5f35 fix: permit empty remove array in edit requests 2025-08-07 12:57:33 +02:00
izzy
947eb15771 fix: newly created roles should be ranked the lowest 2025-08-07 12:29:42 +02:00
IAmTomahawkx
f4ee35fb09 fix: don't update the same field while trying to remove it
fixes #392
2025-08-06 14:18:40 -07:00
IAmTomahawkx
6048587d34 fix: rename openapi operation ids
fixes #406
2025-08-06 12:28:58 -07:00
Builderb
80cf8fc4e8 Increase limit of embed icon url due to new cdn changes causing longer url (#426)
Signed-off-by: Builderb <builderbgamer@gmail.com>
2025-08-03 11:54:22 -07:00
higgs01
4f54227495 chore: use mc alias set instead of removed mc config (#423) 2025-07-18 09:28:31 +01:00
21 changed files with 106 additions and 146 deletions

View File

@@ -34,7 +34,7 @@ services:
- minio
entrypoint: >
/bin/sh -c "while ! /usr/bin/mc ready minio; do
/usr/bin/mc config host add minio http://minio:9000 minioautumn minioautumn;
/usr/bin/mc alias set minio http://minio:9000 minioautumn minioautumn;
echo 'Waiting minio...' && sleep 1;
done; /usr/bin/mc mb minio/revolt-uploads; exit 0;"

View File

@@ -212,16 +212,34 @@ impl AbstractUsers for MongoDb {
partial: &PartialUser,
remove: Vec<FieldsUser>,
) -> Result<()> {
query!(
self,
update_one_by_id,
COL,
id,
partial,
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
None
)
.map(|_| ())
if remove.contains(&FieldsUser::StatusText) && partial.status.is_some() {
// stupid-ass workaround to fix mongo conflicting the same item
let _: Result<()> = query!(
self,
update_one_by_id,
COL,
id,
PartialUser {
..Default::default()
},
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
None
)
.map(|_| ());
query!(self, update_one_by_id, COL, id, partial, vec![], None).map(|_| ())
} else {
query!(
self,
update_one_by_id,
COL,
id,
partial,
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
None
)
.map(|_| ())
}
}
/// Set relationship with another user

View File

@@ -132,8 +132,8 @@ auto_derived!(
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 2048)))]
pub interactions_url: Option<String>,
/// Fields to remove from bot object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsBot>>,
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsBot>,
}
/// Where we are inviting a bot to

View File

@@ -207,7 +207,7 @@ auto_derived!(
/// Fields to remove from channel
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Option<Vec<FieldsChannel>>,
pub remove: Vec<FieldsChannel>,
}
/// Create new group

View File

@@ -215,7 +215,7 @@ auto_derived!(
#[derive(Default)]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct SendableEmbed {
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
pub icon_url: Option<String>,
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
pub url: Option<String>,

View File

@@ -124,7 +124,7 @@ auto_derived!(
/// Timestamp this member is timed out until
pub timeout: Option<Timestamp>,
/// Fields to remove from channel object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsMember>>,
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsMember>,
}
);

View File

@@ -175,6 +175,8 @@ auto_derived!(
/// Ranking position
///
/// Smaller values take priority.
///
/// **Removed** - no effect, use the edit server role positions route
pub rank: Option<i64>,
}
@@ -247,8 +249,8 @@ auto_derived!(
pub analytics: Option<bool>,
/// Fields to remove from server object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsServer>>,
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsServer>,
}
/// New role information
@@ -270,8 +272,8 @@ auto_derived!(
/// **Removed** - no effect, use the edit server role positions route
pub rank: Option<i64>,
/// Fields to remove from role object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsRole>>,
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsRole>,
}
/// New role permissions

View File

@@ -245,8 +245,8 @@ auto_derived!(
pub flags: Option<i32>,
/// Fields to remove from user object
#[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub remove: Option<Vec<FieldsUser>>,
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsUser>,
}
/// User flag reponse

View File

@@ -37,7 +37,7 @@ pub async fn edit_bot(
if data.public.is_none()
&& data.analytics.is_none()
&& data.interactions_url.is_none()
&& data.remove.is_none()
&& data.remove.is_empty()
{
return Ok(Json(v0::BotWithUserResponse {
bot: bot.into(),
@@ -64,7 +64,6 @@ pub async fn edit_bot(
db,
partial,
remove
.unwrap_or_default()
.into_iter()
.map(|v| v.into())
.collect(),
@@ -100,7 +99,7 @@ mod test {
.body(
json!(v0::DataEditBot {
public: Some(true),
remove: Some(vec![FieldsBot::Token]),
remove: vec![FieldsBot::Token],
..Default::default()
})
.to_string(),

View File

@@ -38,7 +38,7 @@ pub async fn edit(
&& data.icon.is_none()
&& data.nsfw.is_none()
&& data.owner.is_none()
&& data.remove.is_none()
&& data.remove.is_empty()
{
return Ok(Json(channel.into()));
}
@@ -112,23 +112,21 @@ pub async fn edit(
nsfw,
..
} => {
if let Some(fields) = &data.remove {
if fields.contains(&v0::FieldsChannel::Icon) {
if let Some(icon) = &icon {
db.mark_attachment_as_deleted(&icon.id).await?;
}
if data.remove.contains(&v0::FieldsChannel::Icon) {
if let Some(icon) = &icon {
db.mark_attachment_as_deleted(&icon.id).await?;
}
}
for field in fields {
match field {
v0::FieldsChannel::Description => {
description.take();
}
v0::FieldsChannel::Icon => {
icon.take();
}
_ => {}
for field in &data.remove {
match field {
v0::FieldsChannel::Description => {
description.take();
}
v0::FieldsChannel::Icon => {
icon.take();
}
_ => {}
}
}
@@ -214,11 +212,7 @@ pub async fn edit(
.update(
db,
partial,
data.remove
.unwrap_or_default()
.into_iter()
.map(|f| f.into())
.collect(),
data.remove.into_iter().map(|f| f.into()).collect(),
)
.await?;
}

View File

@@ -50,7 +50,7 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
group_remove_member::remove_member,
voice_join::call,
permissions_set::set_role_permissions,
permissions_set_default::set_default_permissions,
permissions_set_default::set_default_channel_permissions,
message_react::react_message,
message_unreact::unreact_message,
message_clear_reactions::clear_reactions,

View File

@@ -14,7 +14,7 @@ use rocket::{serde::json::Json, State};
/// Channel must be a `Group`, `TextChannel` or `VoiceChannel`.
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn set_default_permissions(
pub async fn set_default_channel_permissions(
db: &State<Database>,
user: User,
target: Reference,

View File

@@ -39,13 +39,7 @@ pub async fn edit(
let permissions = calculate_server_permissions(&mut query).await;
// Check permissions in server
if data.nickname.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&v0::FieldsMember::Nickname))
.unwrap_or_default()
{
if data.nickname.is_some() || data.remove.contains(&v0::FieldsMember::Nickname) {
if user.id == member.id.user {
permissions.throw_if_lacking_channel_permission(ChannelPermission::ChangeNickname)?;
} else {
@@ -53,13 +47,7 @@ pub async fn edit(
}
}
if data.avatar.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&v0::FieldsMember::Avatar))
.unwrap_or_default()
{
if data.avatar.is_some() || data.remove.contains(&v0::FieldsMember::Avatar) {
if user.id == member.id.user {
permissions.throw_if_lacking_channel_permission(ChannelPermission::ChangeAvatar)?;
} else {
@@ -67,23 +55,11 @@ pub async fn edit(
}
}
if data.roles.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&v0::FieldsMember::Roles))
.unwrap_or_default()
{
if data.roles.is_some() || data.remove.contains(&v0::FieldsMember::Roles) {
permissions.throw_if_lacking_channel_permission(ChannelPermission::AssignRoles)?;
}
if data.timeout.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&v0::FieldsMember::Timeout))
.unwrap_or_default()
{
if data.timeout.is_some() || data.remove.contains(&v0::FieldsMember::Timeout) {
if data.timeout.is_some() && member.id.user == user.id {
return Err(create_error!(CannotTimeoutYourself));
}
@@ -136,11 +112,9 @@ pub async fn edit(
};
// 1. Remove fields from object
if let Some(fields) = &remove {
if fields.contains(&v0::FieldsMember::Avatar) {
if let Some(avatar) = &member.avatar {
db.mark_attachment_as_deleted(&avatar.id).await?;
}
if remove.contains(&v0::FieldsMember::Avatar) {
if let Some(avatar) = &member.avatar {
db.mark_attachment_as_deleted(&avatar.id).await?;
}
}
@@ -150,13 +124,7 @@ pub async fn edit(
}
member
.update(
db,
partial,
remove
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default(),
)
.update(db, partial, remove.into_iter().map(Into::into).collect())
.await?;
Ok(Json(member.into()))

View File

@@ -17,8 +17,8 @@ mod permissions_set_default;
mod roles_create;
mod roles_delete;
mod roles_edit;
mod roles_fetch;
mod roles_edit_positions;
mod roles_fetch;
mod server_ack;
mod server_create;
mod server_delete;
@@ -47,7 +47,7 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
roles_fetch::fetch,
roles_delete::delete,
permissions_set::set_role_permission,
permissions_set_default::set_default_permissions,
permissions_set_default::set_default_server_permissions,
emoji_list::list_emoji,
roles_edit_positions::edit_role_ranks
]

View File

@@ -14,7 +14,7 @@ use rocket::{serde::json::Json, State};
/// Sets permissions for the default role in this server.
#[openapi(tag = "Server Permissions")]
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn set_default_permissions(
pub async fn set_default_server_permissions(
db: &State<Database>,
user: User,
target: Reference,

View File

@@ -40,20 +40,10 @@ pub async fn create(
}));
};
let member_rank = query.get_member_rank();
let rank = if let Some(given_rank) = data.rank {
if given_rank <= member_rank.unwrap_or(i64::MIN) {
return Err(create_error!(NotElevated));
}
given_rank
} else {
member_rank.unwrap_or(0).saturating_add(1)
};
let role = Role {
name: data.name,
rank,
// Rank of the new role should be below the lowest role
rank: server.roles.len() as i64,
colour: None,
hoist: false,
permissions: Default::default(),

View File

@@ -61,9 +61,7 @@ pub async fn edit(
&server.id,
&role_id,
partial,
remove
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default(),
remove.into_iter().map(Into::into).collect(),
)
.await?;

View File

@@ -43,7 +43,7 @@ pub async fn edit(
&& data.flags.is_none()
&& data.analytics.is_none()
&& data.discoverable.is_none()
&& data.remove.is_none()
&& data.remove.is_empty()
{
return Ok(Json(server.into()));
} else if data.name.is_some()
@@ -52,7 +52,7 @@ pub async fn edit(
|| data.banner.is_some()
|| data.system_messages.is_some()
|| data.analytics.is_some()
|| data.remove.is_some()
|| !data.remove.is_empty()
{
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
}
@@ -96,17 +96,15 @@ pub async fn edit(
};
// 1. Remove fields from object
if let Some(fields) = &remove {
if fields.contains(&v0::FieldsServer::Banner) {
if let Some(banner) = &server.banner {
db.mark_attachment_as_deleted(&banner.id).await?;
}
if remove.contains(&v0::FieldsServer::Banner) {
if let Some(banner) = &server.banner {
db.mark_attachment_as_deleted(&banner.id).await?;
}
}
if fields.contains(&v0::FieldsServer::Icon) {
if let Some(icon) = &server.icon {
db.mark_attachment_as_deleted(&icon.id).await?;
}
if remove.contains(&v0::FieldsServer::Icon) {
if let Some(icon) = &server.icon {
db.mark_attachment_as_deleted(&icon.id).await?;
}
}
@@ -149,13 +147,7 @@ pub async fn edit(
}
server
.update(
db,
partial,
remove
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default(),
)
.update(db, partial, remove.into_iter().map(Into::into).collect())
.await?;
Ok(Json(server.into()))

View File

@@ -53,31 +53,29 @@ pub async fn edit(
&& data.avatar.is_none()
&& data.badges.is_none()
&& data.flags.is_none()
&& data.remove.is_none()
&& data.remove.is_empty()
{
return Ok(Json(user.into_self(false).await));
}
// 1. Remove fields from object
if let Some(fields) = &data.remove {
if fields.contains(&v0::FieldsUser::Avatar) {
if let Some(avatar) = &user.avatar {
db.mark_attachment_as_deleted(&avatar.id).await?;
if data.remove.contains(&v0::FieldsUser::Avatar) {
if let Some(avatar) = &user.avatar {
db.mark_attachment_as_deleted(&avatar.id).await?;
}
}
if data.remove.contains(&v0::FieldsUser::ProfileBackground) {
if let Some(profile) = &user.profile {
if let Some(background) = &profile.background {
db.mark_attachment_as_deleted(&background.id).await?;
}
}
}
if fields.contains(&v0::FieldsUser::ProfileBackground) {
if let Some(profile) = &user.profile {
if let Some(background) = &profile.background {
db.mark_attachment_as_deleted(&background.id).await?;
}
}
}
for field in fields {
let field: FieldsUser = field.clone().into();
user.remove_field(&field);
}
for field in &data.remove {
let field: FieldsUser = field.clone().into();
user.remove_field(&field);
}
let mut partial: PartialUser = PartialUser {
@@ -124,9 +122,7 @@ pub async fn edit(
user.update(
db,
partial,
data.remove
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default(),
data.remove.into_iter().map(Into::into).collect(),
)
.await?;

View File

@@ -1,9 +1,12 @@
{ pkgs ? import <nixpkgs> {} }:
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell rec {
buildInputs = [
# Tools
pkgs.git
pkgs.just
# Cargo
pkgs.cargo

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
date=$(date +'%Y%m%d')
incr=1