From 0924e3c3fa55660734ce167eb9ff99a66c39a268 Mon Sep 17 00:00:00 2001 From: xeovalyte Date: Fri, 21 Feb 2025 16:59:56 +0100 Subject: [PATCH] Added start of message create system --- server/migrations/005_create_news.sql | 20 ++++++ server/src/database/model.rs | 2 + server/src/database/model/message.rs | 55 ++++++++++++++++ server/src/model.rs | 2 + server/src/model/member.rs | 3 +- server/src/model/message.rs | 95 +++++++++++++++++++++++++++ server/src/model/user.rs | 2 +- server/src/routes.rs | 2 + server/src/routes/message.rs | 63 ++++++++++++++++++ 9 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 server/migrations/005_create_news.sql create mode 100644 server/src/database/model/message.rs create mode 100644 server/src/model/message.rs create mode 100644 server/src/routes/message.rs diff --git a/server/migrations/005_create_news.sql b/server/migrations/005_create_news.sql new file mode 100644 index 0000000..c686631 --- /dev/null +++ b/server/migrations/005_create_news.sql @@ -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) +); diff --git a/server/src/database/model.rs b/server/src/database/model.rs index 4ed8299..226a4b0 100644 --- a/server/src/database/model.rs +++ b/server/src/database/model.rs @@ -1,8 +1,10 @@ pub mod member; +pub mod message; pub mod session; pub mod user; pub use member::Member; +pub use message::Message; pub use session::Session; pub use user::User; pub use user::UserMember; diff --git a/server/src/database/model/message.rs b/server/src/database/model/message.rs new file mode 100644 index 0000000..5bc6f76 --- /dev/null +++ b/server/src/database/model/message.rs @@ -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, + pub scheduled_at: Option>, + pub status: MessageStatus, + pub title: String, + pub content: String, + pub channel: Channel, + pub thumbnail_url: Option, +} + +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(()) + } +} diff --git a/server/src/model.rs b/server/src/model.rs index b9bbe22..c3a46c5 100644 --- a/server/src/model.rs +++ b/server/src/model.rs @@ -1,6 +1,8 @@ pub mod member; +pub mod message; pub mod session; pub mod user; pub use member::Member; +pub use message::Message; pub use user::User; diff --git a/server/src/model/member.rs b/server/src/model/member.rs index cd188bf..a2a5460 100644 --- a/server/src/model/member.rs +++ b/server/src/model/member.rs @@ -25,6 +25,7 @@ bitflags! { const ZWEMZAKEN = 1 << 2; const WEDSTRIJDEN = 1 << 3; const ADMIN = 1 << 4; + const MESSAGES = 1 << 5; } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -85,7 +86,7 @@ impl From for Groups { impl From for Roles { 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()) } } diff --git a/server/src/model/message.rs b/server/src/model/message.rs new file mode 100644 index 0000000..67839a8 --- /dev/null +++ b/server/src/model/message.rs @@ -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, + pub scheduled_at: Option>, + pub status: MessageStatus, + pub title: String, + pub content: String, + pub channel: Channel, + pub thumbnail_url: Option, +} + +#[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 for Channel { + fn from(value: i64) -> Self { + Self::from_bits(value as u16).unwrap_or(Channel::empty()) + } +} + +pub struct MessageCreate { + pub scheduled_at: Option>, + pub title: String, + pub content: String, + pub channel: Channel, + pub thumbnail_url: Option, +} + +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 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 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, + } + } +} diff --git a/server/src/model/user.rs b/server/src/model/user.rs index ff2f317..83912f1 100644 --- a/server/src/model/user.rs +++ b/server/src/model/user.rs @@ -63,7 +63,7 @@ impl User { let user_roles = DbUserMember::get_roles(pool, &self.id) .await .unwrap_or(Roles::MEMBER); - if !user_roles.contains(roles) { + if !user_roles.intersects(roles) { return Err(AuthError::NoPermssions); } diff --git a/server/src/routes.rs b/server/src/routes.rs index d382eea..d7f51f5 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -3,6 +3,7 @@ use axum::{extract::State, http::HeaderMap, routing::get, Json, Router}; pub mod auth; pub mod member; +pub mod message; pub mod user; pub fn routes() -> Router { @@ -11,6 +12,7 @@ pub fn routes() -> Router { .merge(member::routes()) .merge(auth::routes()) .merge(user::routes()) + .merge(message::routes()) } async fn root( diff --git a/server/src/routes/message.rs b/server/src/routes/message.rs new file mode 100644 index 0000000..3a412b3 --- /dev/null +++ b/server/src/routes/message.rs @@ -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 { + 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, + headers: HeaderMap, + Json(request): Json, +) -> 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(()) +}