Created cli tool to create inital admin member

This commit is contained in:
xeovalyte 2025-02-25 19:22:05 +01:00
parent 972f744e73
commit 10297f9114
Signed by: xeovalyte
SSH Key Fingerprint: SHA256:GWI1hq+MNKR2UOcvk7n9tekASXT8vyazK7vDF9Xyciw
14 changed files with 247 additions and 40 deletions

110
server/Cargo.lock generated

@ -47,6 +47,56 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "argon2"
version = "0.5.3"
@ -266,10 +316,57 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.6",
]
[[package]]
name = "clap"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -994,6 +1091,12 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.14.0"
@ -2195,6 +2298,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.12.0"
@ -2540,6 +2649,7 @@ dependencies = [
"axum-extra",
"bitflags",
"chrono",
"clap",
"csv",
"dotenvy",
"itertools",

@ -17,12 +17,13 @@ dotenvy = "0.15.7"
validator = { version = "0.19.0", features = [ "derive" ] }
argon2 = "0.5"
bitflags = { version = "2.8", features = [ "serde" ] }
clap = { version = "4.5.31", features = ["derive"] }
# Tertiary crates
tracing = "0.1"
tracing-subscriber = "0.3"
chrono = "0.4"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.12", features = ["v4", "fast-rng", "serde"] }
serde_json = "1.0.137"
rand = "0.9"

@ -1,4 +1,4 @@
CREATE TABLE "members" (
CREATE TABLE IF NOT EXISTS "members" (
member_id varchar(7) NOT NULL PRIMARY KEY,
first_name text NOT NULL,
full_name text NOT NULL,

@ -1,4 +1,4 @@
CREATE TABLE "users" (
CREATE TABLE IF NOT EXISTS "users" (
user_id uuid NOT NULL PRIMARY KEY,
email text NOT NULL UNIQUE,
password text NOT NULL,

@ -1,4 +1,4 @@
CREATE TABLE "sessions" (
CREATE TABLE IF NOT EXISTS "sessions" (
session_id uuid NOT NULL PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users (user_id) ON UPDATE cascade ON DELETE cascade,
token text NOT NULL UNIQUE,

@ -1,6 +1,6 @@
CREATE TYPE message_status AS ENUM ('pending', 'sent', 'canceled');
CREATE TABLE messages (
CREATE TABLE IF NOT EXISTS messages (
message_id uuid NOT NULL PRIMARY KEY,
created_at timestamptz NOT NULL,
scheduled_at timestamptz,
@ -11,7 +11,7 @@ CREATE TABLE messages (
thumbnail_url text
);
CREATE TABLE messages_users (
CREATE TABLE IF NOT EXISTS 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,

@ -1,3 +1,3 @@
ALTER TABLE messages
ADD COLUMN member_groups bigint,
ADD COLUMN member_roles bigint;
ADD COLUMN member_groups bigint NOT NULL,
ADD COLUMN member_roles bigint NOT NULL;

@ -1,5 +1,5 @@
use chrono::{DateTime, Utc};
use sqlx::Postgres;
use sqlx::{PgPool, Postgres};
use crate::model::{
member::{Groups, Roles},
@ -61,4 +61,20 @@ impl Message {
Ok(())
}
pub async fn get(pool: &PgPool, channel: Channel) -> Result<Vec<Self>, sqlx::Error> {
let messages = sqlx::query_as!(
Self,
"
SELECT message_id, created_at, scheduled_at, status as \"status:MessageStatus\", title, content, channel, member_groups, member_roles, thumbnail_url FROM messages
WHERE status = 'sent'
AND channel & $1 > 0;
",
channel.bits() as i64
)
.fetch_all(pool)
.await?;
Ok(messages)
}
}

@ -1,13 +1,7 @@
use std::sync::Arc;
use axum::Router;
use tokio::{net::TcpListener, sync::Mutex};
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
use wrbapp_server::routes::member::migrate::MigrationStore;
use wrbapp_server::routes::routes;
use wrbapp_server::{database, AppState};
use wrbapp_server::database;
#[tokio::main]
async fn main() {
@ -30,23 +24,5 @@ async fn main() {
.await
.expect("Database connection failed");
let migration_store = Arc::new(Mutex::new(MigrationStore::default()));
let app_state = AppState {
pool,
migration_store,
};
// Serve app
let app = Router::new().nest("/v1", routes()).with_state(app_state);
let listener = TcpListener::bind("127.0.0.1:3000")
.await
.expect("Error while initializing listener");
tracing::info!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app)
.await
.expect("Error while serving axum application");
wrbapp_server::util::cli::parse(pool).await;
}

@ -1,7 +1,8 @@
use bitflags::bitflags;
use chrono::{DateTime, Utc};
use serde::Serialize;
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct Message {
pub message_id: uuid::Uuid,
pub created_at: DateTime<Utc>,
@ -15,7 +16,7 @@ pub struct Message {
pub thumbnail_url: Option<String>,
}
#[derive(Debug, Clone, Copy, sqlx::Type)]
#[derive(Debug, Clone, Copy, sqlx::Type, Serialize)]
#[sqlx(type_name = "message_status", rename_all = "lowercase")]
pub enum MessageStatus {
Pending,
@ -24,7 +25,7 @@ pub enum MessageStatus {
}
bitflags! {
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct Channel: u16 {
const ALGEMEEN = 1 << 0;
const BELANGRIJK = 1 << 1;

@ -7,8 +7,8 @@ use axum::{
use crate::{
auth::get_user_from_header,
database::model::{Member as DbMember, UserMember as DbUserMember},
model::{member::Roles, Member, User},
database::model::{Member as DbMember, Message as DbMessage, UserMember as DbUserMember},
model::{member::Roles, message::Channel, Member, Message, User},
util::convert_vec,
AppState,
};
@ -18,6 +18,7 @@ pub fn routes() -> Router<AppState> {
.route("/user", get(get_current_user))
.route("/user/{user_id}/members", post(members_insert))
.route("/user/{user_id}/members", delete(members_remove))
.route("/user/{user_id}/messages", get(get_messages))
}
pub async fn get_current_user(
@ -73,3 +74,16 @@ pub async fn members_remove(
Ok(())
}
pub async fn get_messages(
State(state): State<AppState>,
Path(user_id): Path<String>,
headers: HeaderMap,
) -> Result<Json<Vec<Message>>, crate::Error> {
let user = get_user_from_header(&state.pool, &headers).await?;
user.authorize(&state.pool, None, Some(user_id)).await?;
let messages = DbMessage::get(&state.pool, Channel::ALGEMEEN).await?;
Ok(Json(convert_vec(messages)))
}

@ -1,5 +1,7 @@
mod bitflags;
pub mod cli;
pub mod error;
mod helpers;
pub mod serve;
pub use helpers::convert_vec;

56
server/src/util/cli.rs Normal file

@ -0,0 +1,56 @@
use clap::{Parser, Subcommand};
use sqlx::{Acquire, PgPool};
use crate::model::{
member::{Groups, Roles},
Member,
};
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Serve,
CreateAdminMember,
}
pub async fn parse(pool: PgPool) {
let cli = Cli::parse();
match &cli.command {
Some(Commands::Serve) => {
crate::util::serve::serve(pool).await;
}
Some(Commands::CreateAdminMember) => {
create_admin_member(&pool).await.unwrap();
}
None => {}
}
}
pub async fn create_admin_member(pool: &PgPool) -> Result<(), sqlx::Error> {
use crate::database::model::Member as DbMember;
let member = DbMember {
member_id: "D000000".to_string(),
first_name: "Admin".to_string(),
full_name: "Admin Admin".to_string(),
registration_token: None,
diploma: None,
groups: Groups::empty(),
roles: Roles::ADMIN,
};
let mut transaction = pool.begin().await?;
DbMember::insert_many(&mut transaction, vec![member]).await?;
transaction.commit().await?;
Ok(())
}

31
server/src/util/serve.rs Normal file

@ -0,0 +1,31 @@
use std::sync::Arc;
use axum::Router;
use sqlx::PgPool;
use tokio::{net::TcpListener, sync::Mutex};
use crate::routes::member::migrate::MigrationStore;
use crate::routes::routes;
use crate::AppState;
pub async fn serve(pool: PgPool) {
let migration_store = Arc::new(Mutex::new(MigrationStore::default()));
let app_state = AppState {
pool,
migration_store,
};
// Serve app
let app = Router::new().nest("/v1", routes()).with_state(app_state);
let listener = TcpListener::bind("127.0.0.1:3000")
.await
.expect("Error while initializing listener");
tracing::info!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app)
.await
.expect("Error while serving axum application");
}