Added start of message create system

This commit is contained in:
xeovalyte 2025-02-21 16:59:56 +01:00
parent 9eb92ffff1
commit 0924e3c3fa
Signed by: xeovalyte
SSH Key Fingerprint: SHA256:GWI1hq+MNKR2UOcvk7n9tekASXT8vyazK7vDF9Xyciw
9 changed files with 242 additions and 2 deletions

View File

@ -0,0 +1,20 @@
CREATE TYPE message_status AS ENUM ('pending', 'sent', 'canceled');
CREATE TABLE messages (
message_id uuid NOT NULL PRIMARY KEY,
created_at timestamptz NOT NULL,
scheduled_at timestamptz,
status message_status NOT NULL,
title text NOT NULL,
content text NOT NULL,
channel bigint NOT NULL,
thumbnail_url text
);
CREATE TABLE messages_users (
message_id uuid NOT NULL REFERENCES users (user_id) ON UPDATE cascade ON DELETE cascade,
user_id uuid NOT NULL REFERENCES users (user_id) ON UPDATE cascade ON DELETE cascade,
is_read boolean NOT NULL,
CONSTRAINT messages_users_pkey PRIMARY KEY (message_id, user_id)
);

View File

@ -1,8 +1,10 @@
pub mod member; pub mod member;
pub mod message;
pub mod session; pub mod session;
pub mod user; pub mod user;
pub use member::Member; pub use member::Member;
pub use message::Message;
pub use session::Session; pub use session::Session;
pub use user::User; pub use user::User;
pub use user::UserMember; pub use user::UserMember;

View File

@ -0,0 +1,55 @@
use chrono::{DateTime, Utc};
use sqlx::{PgPool, Postgres, QueryBuilder};
use crate::model::message::{Channel, MessageStatus};
#[derive(Debug)]
pub struct Message {
pub message_id: uuid::Uuid,
pub created_at: DateTime<Utc>,
pub scheduled_at: Option<DateTime<Utc>>,
pub status: MessageStatus,
pub title: String,
pub content: String,
pub channel: Channel,
pub thumbnail_url: Option<String>,
}
impl Message {
pub async fn insert(
&self,
transaction: &mut sqlx::Transaction<'_, Postgres>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"
INSERT INTO messages (
message_id,
created_at, scheduled_at,
status,
title, content,
channel,
thumbnail_url
) VALUES (
$1,
$2, $3,
$4,
$5, $6,
$7,
$8
)
",
self.message_id,
self.created_at,
self.scheduled_at,
self.status as MessageStatus,
self.title,
self.content,
self.channel.bits() as i64,
self.thumbnail_url,
)
.execute(&mut **transaction)
.await?;
Ok(())
}
}

View File

@ -1,6 +1,8 @@
pub mod member; pub mod member;
pub mod message;
pub mod session; pub mod session;
pub mod user; pub mod user;
pub use member::Member; pub use member::Member;
pub use message::Message;
pub use user::User; pub use user::User;

View File

@ -25,6 +25,7 @@ bitflags! {
const ZWEMZAKEN = 1 << 2; const ZWEMZAKEN = 1 << 2;
const WEDSTRIJDEN = 1 << 3; const WEDSTRIJDEN = 1 << 3;
const ADMIN = 1 << 4; const ADMIN = 1 << 4;
const MESSAGES = 1 << 5;
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
@ -85,7 +86,7 @@ impl From<i64> for Groups {
impl From<i64> for Roles { impl From<i64> for Roles {
fn from(value: i64) -> Self { fn from(value: i64) -> Self {
Self::from_bits(value as u64).unwrap_or(Roles::MEMBER) Self::from_bits(value as u64).unwrap_or(Roles::empty())
} }
} }

View File

@ -0,0 +1,95 @@
use bitflags::bitflags;
use chrono::{DateTime, Utc};
#[derive(Debug)]
pub struct Message {
pub message_id: uuid::Uuid,
pub created_at: DateTime<Utc>,
pub scheduled_at: Option<DateTime<Utc>>,
pub status: MessageStatus,
pub title: String,
pub content: String,
pub channel: Channel,
pub thumbnail_url: Option<String>,
}
#[derive(Debug, Clone, Copy, sqlx::Type)]
#[sqlx(type_name = "message_status", rename_all = "lowercase")]
pub enum MessageStatus {
Pending,
Sent,
Canceled,
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct Channel: u16 {
const ALGEMEEN = 1 << 0;
const BELANGRIJK = 1 << 1;
const WEDSTRIJDEN = 1 << 2;
}
}
impl From<i64> for Channel {
fn from(value: i64) -> Self {
Self::from_bits(value as u16).unwrap_or(Channel::empty())
}
}
pub struct MessageCreate {
pub scheduled_at: Option<DateTime<Utc>>,
pub title: String,
pub content: String,
pub channel: Channel,
pub thumbnail_url: Option<String>,
}
impl Message {
pub fn new(message_create: MessageCreate) -> Self {
let message_id = uuid::Uuid::new_v4();
let created_at = Utc::now();
Self {
message_id,
created_at,
scheduled_at: message_create.scheduled_at,
title: message_create.title,
content: message_create.content,
channel: message_create.channel,
thumbnail_url: message_create.thumbnail_url,
status: MessageStatus::Pending,
}
}
}
use crate::database::model::Message as DbMessage;
impl From<DbMessage> for Message {
fn from(value: DbMessage) -> Self {
Self {
message_id: value.message_id,
created_at: value.created_at,
scheduled_at: value.scheduled_at,
status: value.status,
title: value.title,
content: value.content,
channel: value.channel,
thumbnail_url: value.thumbnail_url,
}
}
}
impl From<Message> for DbMessage {
fn from(value: Message) -> Self {
Self {
message_id: value.message_id,
created_at: value.created_at,
scheduled_at: value.scheduled_at,
status: value.status,
title: value.title,
content: value.content,
channel: value.channel,
thumbnail_url: value.thumbnail_url,
}
}
}

View File

@ -63,7 +63,7 @@ impl User {
let user_roles = DbUserMember::get_roles(pool, &self.id) let user_roles = DbUserMember::get_roles(pool, &self.id)
.await .await
.unwrap_or(Roles::MEMBER); .unwrap_or(Roles::MEMBER);
if !user_roles.contains(roles) { if !user_roles.intersects(roles) {
return Err(AuthError::NoPermssions); return Err(AuthError::NoPermssions);
} }

View File

@ -3,6 +3,7 @@ use axum::{extract::State, http::HeaderMap, routing::get, Json, Router};
pub mod auth; pub mod auth;
pub mod member; pub mod member;
pub mod message;
pub mod user; pub mod user;
pub fn routes() -> Router<AppState> { pub fn routes() -> Router<AppState> {
@ -11,6 +12,7 @@ pub fn routes() -> Router<AppState> {
.merge(member::routes()) .merge(member::routes())
.merge(auth::routes()) .merge(auth::routes())
.merge(user::routes()) .merge(user::routes())
.merge(message::routes())
} }
async fn root( async fn root(

View File

@ -0,0 +1,63 @@
use axum::{
extract::State,
http::HeaderMap,
routing::{get, post},
Json, Router,
};
use serde::Deserialize;
use crate::{
auth::get_user_from_header,
database::model::Message as DbMessage,
model::{
member::Roles,
message::{Channel, MessageCreate},
Message,
},
AppState,
};
pub fn routes() -> Router<AppState> {
Router::new().route("/messages", post(message_create))
}
#[derive(Debug, Deserialize)]
pub struct MessageCreateRequest {
title: String,
content: String,
channel: String,
}
pub async fn message_create(
State(state): State<AppState>,
headers: HeaderMap,
Json(request): Json<MessageCreateRequest>,
) -> Result<(), crate::Error> {
let user = get_user_from_header(&state.pool, &headers).await?;
user.authorize(&state.pool, Some(Roles::ADMIN | Roles::MESSAGES), None)
.await?;
let channel: Channel = bitflags::parser::from_str_strict(&request.channel).map_err(|_| {
crate::Error::BadRequest {
expected: String::from("Error while parsing channel"),
}
})?;
let db_message: DbMessage = Message::new(MessageCreate {
title: request.title,
content: request.content,
channel,
scheduled_at: None,
thumbnail_url: None,
})
.into();
let mut transaction = state.pool.begin().await?;
db_message.insert(&mut transaction).await?;
transaction.commit().await?;
Ok(())
}