Created cli tool to create inital admin member
This commit is contained in:
parent
972f744e73
commit
10297f9114
110
server/Cargo.lock
generated
110
server/Cargo.lock
generated
@ -47,6 +47,56 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@ -266,10 +316,57 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -994,6 +1091,12 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@ -2195,6 +2298,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@ -2540,6 +2649,7 @@ dependencies = [
|
|||||||
"axum-extra",
|
"axum-extra",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"clap",
|
||||||
"csv",
|
"csv",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
@ -17,12 +17,13 @@ dotenvy = "0.15.7"
|
|||||||
validator = { version = "0.19.0", features = [ "derive" ] }
|
validator = { version = "0.19.0", features = [ "derive" ] }
|
||||||
argon2 = "0.5"
|
argon2 = "0.5"
|
||||||
bitflags = { version = "2.8", features = [ "serde" ] }
|
bitflags = { version = "2.8", features = [ "serde" ] }
|
||||||
|
clap = { version = "4.5.31", features = ["derive"] }
|
||||||
|
|
||||||
|
|
||||||
# Tertiary crates
|
# Tertiary crates
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
uuid = { version = "1.12", features = ["v4", "fast-rng", "serde"] }
|
uuid = { version = "1.12", features = ["v4", "fast-rng", "serde"] }
|
||||||
serde_json = "1.0.137"
|
serde_json = "1.0.137"
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
CREATE TABLE "members" (
|
CREATE TABLE IF NOT EXISTS "members" (
|
||||||
member_id varchar(7) NOT NULL PRIMARY KEY,
|
member_id varchar(7) NOT NULL PRIMARY KEY,
|
||||||
first_name text NOT NULL,
|
first_name text NOT NULL,
|
||||||
full_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,
|
user_id uuid NOT NULL PRIMARY KEY,
|
||||||
email text NOT NULL UNIQUE,
|
email text NOT NULL UNIQUE,
|
||||||
password text NOT NULL,
|
password text NOT NULL,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
CREATE TABLE "sessions" (
|
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||||
session_id uuid NOT NULL PRIMARY KEY,
|
session_id uuid NOT NULL PRIMARY KEY,
|
||||||
user_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,
|
||||||
token text NOT NULL UNIQUE,
|
token text NOT NULL UNIQUE,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
CREATE TYPE message_status AS ENUM ('pending', 'sent', 'canceled');
|
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,
|
message_id uuid NOT NULL PRIMARY KEY,
|
||||||
created_at timestamptz NOT NULL,
|
created_at timestamptz NOT NULL,
|
||||||
scheduled_at timestamptz,
|
scheduled_at timestamptz,
|
||||||
@ -11,7 +11,7 @@ CREATE TABLE messages (
|
|||||||
thumbnail_url text
|
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,
|
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,
|
user_id uuid NOT NULL REFERENCES users (user_id) ON UPDATE cascade ON DELETE cascade,
|
||||||
is_read boolean NOT NULL,
|
is_read boolean NOT NULL,
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
ALTER TABLE messages
|
ALTER TABLE messages
|
||||||
ADD COLUMN member_groups bigint,
|
ADD COLUMN member_groups bigint NOT NULL,
|
||||||
ADD COLUMN member_roles bigint;
|
ADD COLUMN member_roles bigint NOT NULL;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use sqlx::Postgres;
|
use sqlx::{PgPool, Postgres};
|
||||||
|
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
member::{Groups, Roles},
|
member::{Groups, Roles},
|
||||||
@ -61,4 +61,20 @@ impl Message {
|
|||||||
|
|
||||||
Ok(())
|
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::Level;
|
||||||
use tracing_subscriber::FmtSubscriber;
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
use wrbapp_server::routes::member::migrate::MigrationStore;
|
use wrbapp_server::database;
|
||||||
use wrbapp_server::routes::routes;
|
|
||||||
use wrbapp_server::{database, AppState};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@ -30,23 +24,5 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.expect("Database connection failed");
|
.expect("Database connection failed");
|
||||||
|
|
||||||
let migration_store = Arc::new(Mutex::new(MigrationStore::default()));
|
wrbapp_server::util::cli::parse(pool).await;
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
pub message_id: uuid::Uuid,
|
pub message_id: uuid::Uuid,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
@ -15,7 +16,7 @@ pub struct Message {
|
|||||||
pub thumbnail_url: Option<String>,
|
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")]
|
#[sqlx(type_name = "message_status", rename_all = "lowercase")]
|
||||||
pub enum MessageStatus {
|
pub enum MessageStatus {
|
||||||
Pending,
|
Pending,
|
||||||
@ -24,7 +25,7 @@ pub enum MessageStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
pub struct Channel: u16 {
|
pub struct Channel: u16 {
|
||||||
const ALGEMEEN = 1 << 0;
|
const ALGEMEEN = 1 << 0;
|
||||||
const BELANGRIJK = 1 << 1;
|
const BELANGRIJK = 1 << 1;
|
||||||
|
@ -7,8 +7,8 @@ use axum::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::get_user_from_header,
|
auth::get_user_from_header,
|
||||||
database::model::{Member as DbMember, UserMember as DbUserMember},
|
database::model::{Member as DbMember, Message as DbMessage, UserMember as DbUserMember},
|
||||||
model::{member::Roles, Member, User},
|
model::{member::Roles, message::Channel, Member, Message, User},
|
||||||
util::convert_vec,
|
util::convert_vec,
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
@ -18,6 +18,7 @@ pub fn routes() -> Router<AppState> {
|
|||||||
.route("/user", get(get_current_user))
|
.route("/user", get(get_current_user))
|
||||||
.route("/user/{user_id}/members", post(members_insert))
|
.route("/user/{user_id}/members", post(members_insert))
|
||||||
.route("/user/{user_id}/members", delete(members_remove))
|
.route("/user/{user_id}/members", delete(members_remove))
|
||||||
|
.route("/user/{user_id}/messages", get(get_messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_current_user(
|
pub async fn get_current_user(
|
||||||
@ -73,3 +74,16 @@ pub async fn members_remove(
|
|||||||
|
|
||||||
Ok(())
|
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;
|
mod bitflags;
|
||||||
|
pub mod cli;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
pub mod serve;
|
||||||
|
|
||||||
pub use helpers::convert_vec;
|
pub use helpers::convert_vec;
|
||||||
|
56
server/src/util/cli.rs
Normal file
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
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");
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user