add: channels/messages scopes and api endpoints

This commit is contained in:
trisua 2025-06-13 22:07:36 -04:00
parent 8f16068a34
commit b29760d7ec
10 changed files with 195 additions and 63 deletions

View file

@ -1,6 +1,6 @@
use axum::{Extension, Json, extract::Path, response::IntoResponse};
use axum_extra::extract::CookieJar;
use tetratto_core::model::{channels::Channel, ApiReturn, Error};
use tetratto_core::model::{oauth, channels::Channel, ApiReturn, Error};
use crate::{
get_user_from_token,
routes::api::v1::{
@ -15,7 +15,7 @@ pub async fn create_request(
Json(req): Json<CreateChannel>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityCreateChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -47,7 +47,7 @@ pub async fn create_group_request(
Json(req): Json<CreateGroupChannel>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -111,7 +111,7 @@ pub async fn delete_request(
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -133,7 +133,7 @@ pub async fn update_title_request(
Json(req): Json<UpdateChannelTitle>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -155,7 +155,7 @@ pub async fn update_position_request(
Json(req): Json<UpdateChannelPosition>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -177,7 +177,7 @@ pub async fn add_member_request(
Json(req): Json<KickMember>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -199,7 +199,7 @@ pub async fn kick_member_request(
Json(req): Json<KickMember>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -223,3 +223,73 @@ pub async fn kick_member_request(
Err(e) => Json(e.into()),
}
}
pub async fn get_dm_channels_request(
jar: CookieJar,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data.get_channels_by_user(user.id).await {
Ok(c) => Json(ApiReturn {
ok: true,
message: "Success".to_string(),
payload: Some(c),
}),
Err(e) => Json(e.into()),
}
}
pub async fn get_community_channels_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
if data
.get_membership_by_owner_community_no_void(user.id, id)
.await
.is_err()
{
// must be a member of the community to request channels
return Json(Error::NotAllowed.into());
}
match data.get_channels_by_community(id).await {
Ok(c) => Json(ApiReturn {
ok: true,
message: "Success".to_string(),
payload: Some(c),
}),
Err(e) => Json(e.into()),
}
}
pub async fn get_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
if get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels).is_none() {
return Json(Error::NotAllowed.into());
}
match data.get_channel_by_id(id).await {
Ok(c) => Json(ApiReturn {
ok: true,
message: "Success".to_string(),
payload: Some(c),
}),
Err(e) => Json(e.into()),
}
}

View file

@ -2,15 +2,16 @@ use std::{collections::HashMap, time::Duration};
use axum::{
extract::{
ws::{Message as WsMessage, WebSocket, WebSocketUpgrade},
Path,
Path, Query,
},
response::{IntoResponse, Response},
response::IntoResponse,
Extension, Json,
};
use axum_extra::extract::CookieJar;
use tetratto_core::{
cache::{Cache, redis::Commands},
model::{
oauth,
auth::User,
channels::Message,
socket::{PacketType, SocketMessage, SocketMethod},
@ -18,36 +19,42 @@ use tetratto_core::{
},
DataManager,
};
use crate::{get_user_from_token, routes::api::v1::CreateMessage, State};
use crate::{
get_user_from_token,
routes::{api::v1::CreateMessage, pages::PaginatedQuery},
State,
};
use serde::Deserialize;
use futures_util::{sink::SinkExt, stream::StreamExt};
#[derive(Clone, Deserialize)]
pub struct SocketHeaders {
pub user: String,
pub is_channel: bool,
}
/// Handle a subscription to the websocket.
pub async fn subscription_handler(
jar: CookieJar,
ws: WebSocketUpgrade,
Extension(data): Extension<State>,
Path(id): Path<String>,
) -> Response {
let data = &(data.read().await);
let data = data.0.clone();
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadSockets) {
Some(ua) => ua,
None => return Err(Error::NotAllowed.to_string()),
};
ws.on_upgrade(|socket| async move {
let data = data.clone();
Ok(ws.on_upgrade(|socket| async move {
tokio::spawn(async move {
handle_socket(socket, data, id).await;
handle_socket(socket, data, id, user).await;
});
})
}))
}
pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: String) {
pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: String, user: User) {
let (mut sink, mut stream) = socket.split();
let mut user: Option<User> = None;
let mut headers: Option<SocketHeaders> = None;
let channel_id = format!("chats/{community_id}");
@ -63,7 +70,7 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
};
if data.method != SocketMethod::Headers && user.is_none() && headers.is_none() {
if data.method != SocketMethod::Headers && headers.is_none() {
// we've sent something else before authenticating... that's not right
let _ = sink.close().await;
return;
@ -74,24 +81,6 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
let data: SocketHeaders = data.data();
headers = Some(data.clone());
user = Some(
match dbc
.get_user_by_id(match data.user.parse::<usize>() {
Ok(c) => c,
Err(_) => {
let _ = sink.close().await;
return;
}
})
.await
{
Ok(ua) => ua,
Err(_) => {
let _ = sink.close().await;
return;
}
},
);
if data.is_channel {
// verify permissions for single channel
@ -112,8 +101,6 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
};
let user = user.as_ref().unwrap();
let membership = match dbc
.get_membership_by_owner_community(user.id, channel.id)
.await
@ -142,7 +129,6 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
// get channel permissions
let user = user.unwrap();
let headers = headers.unwrap();
let mut channel_read_statuses: HashMap<usize, bool> = HashMap::new();
@ -280,7 +266,7 @@ pub async fn create_request(
Json(req): Json<CreateMessage>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateMessages) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -311,7 +297,7 @@ pub async fn delete_request(
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserDeleteMessages) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@ -325,3 +311,42 @@ pub async fn delete_request(
Err(e) => Json(e.into()),
}
}
pub async fn from_channel_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
Query(props): Query<PaginatedQuery>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateMessages) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
let channel = match data.get_channel_by_id(id).await {
Ok(c) => c,
Err(e) => return Json(e.into()),
};
let membership = match data
.get_membership_by_owner_community(user.id, channel.community)
.await
{
Ok(m) => m,
Err(e) => return Json(e.into()),
};
if !channel.check_read(user.id, Some(membership.role)) {
return Json(Error::NotAllowed.into());
}
match data.get_messages_by_channel(id, 24, props.page).await {
Ok(m) => Json(ApiReturn {
ok: true,
message: "Success".to_string(),
payload: Some(m),
}),
Err(e) => Json(e.into()),
}
}