WIP: Audit Logs #45
8 changed files with 213 additions and 46 deletions
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
use ::uuid::Uuid;
|
use ::uuid::Uuid;
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension, Json,
|
Extension, Json,
|
||||||
extract::{Path, State},
|
extract::{Path, Query, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
|
@ -12,13 +12,14 @@ use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
api::v1::auth::CurrentUser,
|
api::v1::auth::CurrentUser,
|
||||||
error::Error,
|
error::Error,
|
||||||
objects::{Me, Member},
|
objects::{Me, Member, PaginationRequest},
|
||||||
utils::global_checks,
|
utils::global_checks,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(app_state): State<Arc<AppState>>,
|
State(app_state): State<Arc<AppState>>,
|
||||||
Path(guild_uuid): Path<Uuid>,
|
Path(guild_uuid): Path<Uuid>,
|
||||||
|
Query(pagination): Query<PaginationRequest>,
|
||||||
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
|
Extension(CurrentUser(uuid)): Extension<CurrentUser<Uuid>>,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
let mut conn = app_state.pool.get().await?;
|
let mut conn = app_state.pool.get().await?;
|
||||||
|
@ -29,7 +30,14 @@ pub async fn get(
|
||||||
|
|
||||||
let me = Me::get(&mut conn, uuid).await?;
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
let members = Member::fetch_all(&mut conn, &app_state.cache_pool, &me, guild_uuid).await?;
|
let members = Member::fetch_page(
|
||||||
|
&mut conn,
|
||||||
|
&app_state.cache_pool,
|
||||||
|
&me,
|
||||||
|
guild_uuid,
|
||||||
|
pagination,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok((StatusCode::OK, Json(members)))
|
Ok((StatusCode::OK, Json(members)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub async fn post(
|
||||||
global_checks(&mut conn, &app_state.config, uuid).await?;
|
global_checks(&mut conn, &app_state.config, uuid).await?;
|
||||||
|
|
||||||
let member =
|
let member =
|
||||||
Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, None, member_uuid).await?;
|
Member::fetch_one_with_uuid(&mut conn, &app_state.cache_pool, None, member_uuid).await?;
|
||||||
|
|
||||||
let caller = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?;
|
let caller = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub async fn get(
|
||||||
let me = Me::get(&mut conn, uuid).await?;
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
let member =
|
let member =
|
||||||
Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, Some(&me), member_uuid)
|
Member::fetch_one_with_uuid(&mut conn, &app_state.cache_pool, Some(&me), member_uuid)
|
||||||
.await?;
|
.await?;
|
||||||
Member::check_membership(&mut conn, uuid, member.guild_uuid).await?;
|
Member::check_membership(&mut conn, uuid, member.guild_uuid).await?;
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ pub async fn delete(
|
||||||
let me = Me::get(&mut conn, uuid).await?;
|
let me = Me::get(&mut conn, uuid).await?;
|
||||||
|
|
||||||
let member =
|
let member =
|
||||||
Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, Some(&me), member_uuid)
|
Member::fetch_one_with_uuid(&mut conn, &app_state.cache_pool, Some(&me), member_uuid)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let deleter = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?;
|
let deleter = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?;
|
||||||
|
|
|
@ -284,7 +284,7 @@ impl Me {
|
||||||
cache_pool: &redis::Client,
|
cache_pool: &redis::Client,
|
||||||
new_status: i16,
|
new_status: i16,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if new_status > 4 || new_status < 0 {
|
if !(0..=4).contains(&new_status) {
|
||||||
return Err(Error::BadRequest("Invalid status code".to_string()));
|
return Err(Error::BadRequest("Invalid status code".to_string()));
|
||||||
}
|
}
|
||||||
self.online_status = new_status;
|
self.online_status = new_status;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use diesel::{
|
use diesel::{
|
||||||
ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper,
|
Associations, BoolExpressionMethods, ExpressionMethods, Identifiable, Insertable, JoinOnDsl,
|
||||||
delete, insert_into,
|
QueryDsl, Queryable, Selectable, SelectableHelper, define_sql_function, delete, insert_into,
|
||||||
|
sql_types::{Nullable, VarChar},
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -9,14 +10,21 @@ use uuid::Uuid;
|
||||||
use crate::{
|
use crate::{
|
||||||
Conn,
|
Conn,
|
||||||
error::Error,
|
error::Error,
|
||||||
objects::{GuildBan, Me, Permissions, Role},
|
objects::PaginationRequest,
|
||||||
schema::{guild_bans, guild_members},
|
schema::{friends, guild_bans, guild_members, users},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{User, load_or_empty};
|
use super::{
|
||||||
|
Friend, Guild, GuildBan, Me, Pagination, Permissions, Role, User, load_or_empty,
|
||||||
|
user::UserBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Queryable, Identifiable, Selectable, Insertable)]
|
define_sql_function! { fn coalesce(x: Nullable<VarChar>, y: Nullable<VarChar>, z: VarChar) -> Text; }
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable, Identifiable, Selectable, Insertable, Associations)]
|
||||||
#[diesel(table_name = guild_members)]
|
#[diesel(table_name = guild_members)]
|
||||||
|
#[diesel(belongs_to(UserBuilder, foreign_key = user_uuid))]
|
||||||
|
#[diesel(belongs_to(Guild, foreign_key = guild_uuid))]
|
||||||
#[diesel(primary_key(uuid))]
|
#[diesel(primary_key(uuid))]
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
pub struct MemberBuilder {
|
pub struct MemberBuilder {
|
||||||
|
@ -55,6 +63,32 @@ impl MemberBuilder {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn build_with_parts(
|
||||||
|
&self,
|
||||||
|
conn: &mut Conn,
|
||||||
|
cache_pool: &redis::Client,
|
||||||
|
user_builder: UserBuilder,
|
||||||
|
friend: Option<Friend>,
|
||||||
|
) -> Result<Member, Error> {
|
||||||
|
let mut user = user_builder.build();
|
||||||
|
|
||||||
|
if let Some(friend) = friend {
|
||||||
|
user.friends_since = Some(friend.accepted_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
let roles = Role::fetch_from_member(conn, cache_pool, self).await?;
|
||||||
|
|
||||||
|
Ok(Member {
|
||||||
|
uuid: self.uuid,
|
||||||
|
nickname: self.nickname.clone(),
|
||||||
|
user_uuid: self.user_uuid,
|
||||||
|
guild_uuid: self.guild_uuid,
|
||||||
|
is_owner: self.is_owner,
|
||||||
|
user,
|
||||||
|
roles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn check_permission(
|
pub async fn check_permission(
|
||||||
&self,
|
&self,
|
||||||
conn: &mut Conn,
|
conn: &mut Conn,
|
||||||
|
@ -73,7 +107,7 @@ impl MemberBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Member {
|
pub struct Member {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub nickname: Option<String>,
|
pub nickname: Option<String>,
|
||||||
|
@ -116,56 +150,153 @@ impl Member {
|
||||||
pub async fn fetch_one(
|
pub async fn fetch_one(
|
||||||
conn: &mut Conn,
|
conn: &mut Conn,
|
||||||
cache_pool: &redis::Client,
|
cache_pool: &redis::Client,
|
||||||
me: &Me,
|
me: Option<&Me>,
|
||||||
user_uuid: Uuid,
|
user_uuid: Uuid,
|
||||||
guild_uuid: Uuid,
|
guild_uuid: Uuid,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
let member: MemberBuilder;
|
||||||
|
let user: UserBuilder;
|
||||||
|
let friend: Option<Friend>;
|
||||||
|
use friends::dsl as fdsl;
|
||||||
use guild_members::dsl;
|
use guild_members::dsl;
|
||||||
let member: MemberBuilder = dsl::guild_members
|
if let Some(me) = me {
|
||||||
.filter(dsl::user_uuid.eq(user_uuid))
|
(member, user, friend) = dsl::guild_members
|
||||||
.filter(dsl::guild_uuid.eq(guild_uuid))
|
.filter(dsl::guild_uuid.eq(guild_uuid))
|
||||||
.select(MemberBuilder::as_select())
|
.filter(dsl::user_uuid.eq(user_uuid))
|
||||||
.get_result(conn)
|
.inner_join(users::table)
|
||||||
.await?;
|
.left_join(
|
||||||
|
fdsl::friends.on(fdsl::uuid1
|
||||||
|
.eq(me.uuid)
|
||||||
|
.and(fdsl::uuid2.eq(users::uuid))
|
||||||
|
.or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
MemberBuilder::as_select(),
|
||||||
|
UserBuilder::as_select(),
|
||||||
|
Option::<Friend>::as_select(),
|
||||||
|
))
|
||||||
|
.get_result(conn)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
(member, user) = dsl::guild_members
|
||||||
|
.filter(dsl::guild_uuid.eq(guild_uuid))
|
||||||
|
.filter(dsl::user_uuid.eq(user_uuid))
|
||||||
|
.inner_join(users::table)
|
||||||
|
.select((MemberBuilder::as_select(), UserBuilder::as_select()))
|
||||||
|
.get_result(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
member.build(conn, cache_pool, Some(me)).await
|
friend = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
member
|
||||||
|
.build_with_parts(conn, cache_pool, user, friend)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_one_with_member(
|
pub async fn fetch_one_with_uuid(
|
||||||
conn: &mut Conn,
|
conn: &mut Conn,
|
||||||
cache_pool: &redis::Client,
|
cache_pool: &redis::Client,
|
||||||
me: Option<&Me>,
|
me: Option<&Me>,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
let member: MemberBuilder;
|
||||||
|
let user: UserBuilder;
|
||||||
|
let friend: Option<Friend>;
|
||||||
|
use friends::dsl as fdsl;
|
||||||
use guild_members::dsl;
|
use guild_members::dsl;
|
||||||
let member: MemberBuilder = dsl::guild_members
|
if let Some(me) = me {
|
||||||
.filter(dsl::uuid.eq(uuid))
|
(member, user, friend) = dsl::guild_members
|
||||||
.select(MemberBuilder::as_select())
|
.filter(dsl::uuid.eq(uuid))
|
||||||
.get_result(conn)
|
.inner_join(users::table)
|
||||||
.await?;
|
.left_join(
|
||||||
|
fdsl::friends.on(fdsl::uuid1
|
||||||
|
.eq(me.uuid)
|
||||||
|
.and(fdsl::uuid2.eq(users::uuid))
|
||||||
|
.or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
MemberBuilder::as_select(),
|
||||||
|
UserBuilder::as_select(),
|
||||||
|
Option::<Friend>::as_select(),
|
||||||
|
))
|
||||||
|
.get_result(conn)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
(member, user) = dsl::guild_members
|
||||||
|
.filter(dsl::uuid.eq(uuid))
|
||||||
|
.inner_join(users::table)
|
||||||
|
.select((MemberBuilder::as_select(), UserBuilder::as_select()))
|
||||||
|
.get_result(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
member.build(conn, cache_pool, me).await
|
friend = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
member
|
||||||
|
.build_with_parts(conn, cache_pool, user, friend)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_all(
|
pub async fn fetch_page(
|
||||||
conn: &mut Conn,
|
conn: &mut Conn,
|
||||||
cache_pool: &redis::Client,
|
cache_pool: &redis::Client,
|
||||||
me: &Me,
|
me: &Me,
|
||||||
guild_uuid: Uuid,
|
guild_uuid: Uuid,
|
||||||
) -> Result<Vec<Self>, Error> {
|
pagination: PaginationRequest,
|
||||||
|
) -> Result<Pagination<Self>, Error> {
|
||||||
|
let per_page = pagination.per_page.unwrap_or(50);
|
||||||
|
let page_multiplier: i64 = ((pagination.page - 1) * per_page).into();
|
||||||
|
|
||||||
|
if !(10..=100).contains(&per_page) {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
"Invalid amount per page requested".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
use friends::dsl as fdsl;
|
||||||
use guild_members::dsl;
|
use guild_members::dsl;
|
||||||
let member_builders: Vec<MemberBuilder> = load_or_empty(
|
let member_builders: Vec<(MemberBuilder, UserBuilder, Option<Friend>)> = load_or_empty(
|
||||||
dsl::guild_members
|
dsl::guild_members
|
||||||
.filter(dsl::guild_uuid.eq(guild_uuid))
|
.filter(dsl::guild_uuid.eq(guild_uuid))
|
||||||
.select(MemberBuilder::as_select())
|
.inner_join(users::table)
|
||||||
|
.left_join(
|
||||||
|
fdsl::friends.on(fdsl::uuid1
|
||||||
|
.eq(me.uuid)
|
||||||
|
.and(fdsl::uuid2.eq(users::uuid))
|
||||||
|
.or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))),
|
||||||
|
)
|
||||||
|
.limit(per_page.into())
|
||||||
|
.offset(page_multiplier)
|
||||||
|
.order_by(coalesce(
|
||||||
|
dsl::nickname,
|
||||||
|
users::display_name,
|
||||||
|
users::username,
|
||||||
|
))
|
||||||
|
.select((
|
||||||
|
MemberBuilder::as_select(),
|
||||||
|
UserBuilder::as_select(),
|
||||||
|
Option::<Friend>::as_select(),
|
||||||
|
))
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.await,
|
.await,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut members = vec![];
|
let pages = Member::count(conn, guild_uuid).await? as f32 / per_page as f32;
|
||||||
|
|
||||||
for builder in member_builders {
|
let mut members = Pagination::<Member> {
|
||||||
members.push(builder.build(conn, cache_pool, Some(me)).await?);
|
objects: Vec::with_capacity(member_builders.len()),
|
||||||
|
amount: member_builders.len() as i32,
|
||||||
|
pages: pages.ceil() as i32,
|
||||||
|
page: pagination.page,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (member, user, friend) in member_builders {
|
||||||
|
members.objects.push(
|
||||||
|
member
|
||||||
|
.build_with_parts(conn, cache_pool, user, friend)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(members)
|
Ok(members)
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
use diesel::{Insertable, Queryable, Selectable};
|
use diesel::{ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{Conn, error::Error, schema::messages};
|
use crate::{
|
||||||
|
Conn,
|
||||||
|
error::Error,
|
||||||
|
schema::{channels, guilds, messages},
|
||||||
|
};
|
||||||
|
|
||||||
use super::User;
|
use super::Member;
|
||||||
|
|
||||||
#[derive(Clone, Queryable, Selectable, Insertable)]
|
#[derive(Clone, Queryable, Selectable, Insertable)]
|
||||||
#[diesel(table_name = messages)]
|
#[diesel(table_name = messages)]
|
||||||
|
@ -23,7 +28,16 @@ impl MessageBuilder {
|
||||||
conn: &mut Conn,
|
conn: &mut Conn,
|
||||||
cache_pool: &redis::Client,
|
cache_pool: &redis::Client,
|
||||||
) -> Result<Message, Error> {
|
) -> Result<Message, Error> {
|
||||||
let user = User::fetch_one(conn, cache_pool, self.user_uuid).await?;
|
use channels::dsl;
|
||||||
|
|
||||||
|
let guild_uuid = dsl::channels
|
||||||
|
.filter(dsl::uuid.eq(self.channel_uuid))
|
||||||
|
.inner_join(guilds::table)
|
||||||
|
.select(guilds::uuid)
|
||||||
|
.get_result(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let member = Member::fetch_one(conn, cache_pool, None, self.user_uuid, guild_uuid).await?;
|
||||||
|
|
||||||
Ok(Message {
|
Ok(Message {
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
|
@ -31,7 +45,7 @@ impl MessageBuilder {
|
||||||
user_uuid: self.user_uuid,
|
user_uuid: self.user_uuid,
|
||||||
message: self.message.clone(),
|
message: self.message.clone(),
|
||||||
reply_to: self.reply_to,
|
reply_to: self.reply_to,
|
||||||
user,
|
member,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,5 +57,5 @@ pub struct Message {
|
||||||
user_uuid: Uuid,
|
user_uuid: Uuid,
|
||||||
message: String,
|
message: String,
|
||||||
reply_to: Option<Uuid>,
|
reply_to: Option<Uuid>,
|
||||||
user: User,
|
member: Member,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lettre::{
|
||||||
transport::smtp::authentication::Credentials,
|
transport::smtp::authentication::Credentials,
|
||||||
};
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod bans;
|
mod bans;
|
||||||
|
@ -78,6 +78,20 @@ impl Cookies for Request<Body> {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Pagination<T> {
|
||||||
|
objects: Vec<T>,
|
||||||
|
amount: i32,
|
||||||
|
pages: i32,
|
||||||
|
page: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct PaginationRequest {
|
||||||
|
pub page: i32,
|
||||||
|
pub per_page: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
fn load_or_empty<T>(
|
fn load_or_empty<T>(
|
||||||
query_result: Result<Vec<T>, diesel::result::Error>,
|
query_result: Result<Vec<T>, diesel::result::Error>,
|
||||||
) -> Result<Vec<T>, diesel::result::Error> {
|
) -> Result<Vec<T>, diesel::result::Error> {
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct UserBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserBuilder {
|
impl UserBuilder {
|
||||||
fn build(self) -> User {
|
pub fn build(self) -> User {
|
||||||
User {
|
User {
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
username: self.username,
|
username: self.username,
|
||||||
|
@ -38,14 +38,14 @@ impl UserBuilder {
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
username: String,
|
username: String,
|
||||||
display_name: Option<String>,
|
display_name: Option<String>,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
pronouns: Option<String>,
|
pronouns: Option<String>,
|
||||||
about: Option<String>,
|
about: Option<String>,
|
||||||
online_status: i16,
|
online_status: i16,
|
||||||
friends_since: Option<DateTime<Utc>>,
|
pub friends_since: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue