Migrated to bitflags
This commit is contained in:
parent
cf379a1288
commit
07493b83a5
1
server/Cargo.lock
generated
1
server/Cargo.lock
generated
@ -2537,6 +2537,7 @@ dependencies = [
|
|||||||
"argon2",
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
"csv",
|
"csv",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
@ -16,6 +16,7 @@ serde = "1.0"
|
|||||||
dotenvy = "0.15.7"
|
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" ] }
|
||||||
|
|
||||||
|
|
||||||
# Tertiary crates
|
# Tertiary crates
|
||||||
|
@ -4,7 +4,7 @@ CREATE TABLE "members" (
|
|||||||
full_name text NOT NULL,
|
full_name text NOT NULL,
|
||||||
registration_token text NOT NULL UNIQUE,
|
registration_token text NOT NULL UNIQUE,
|
||||||
diploma text,
|
diploma text,
|
||||||
hours text[] NOT NULL,
|
swim_groups bigint NOT NULL,
|
||||||
groups text[] NOT NULL
|
groups bigint NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -22,8 +22,11 @@ use rand::prelude::*;
|
|||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
|
||||||
|
use crate::database::model::User;
|
||||||
|
|
||||||
mod bearer;
|
mod bearer;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod scopes;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Permissions<'a>(pub HashSet<&'a str>);
|
pub struct Permissions<'a>(pub HashSet<&'a str>);
|
||||||
|
19
server/src/auth/scopes.rs
Normal file
19
server/src/auth/scopes.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use crate::bitflags_serde_impl;
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Scopes: u64 {
|
||||||
|
const USER_READ = 1 << 0;
|
||||||
|
const USER_WRITE = 1 << 1;
|
||||||
|
const USER_DELETE = 1 << 2;
|
||||||
|
|
||||||
|
const MEMBER_CREATE = 1 << 3;
|
||||||
|
const MEMBER_READ = 1 << 4;
|
||||||
|
const MEMBER_WRITE = 1 << 5;
|
||||||
|
const MEMBER_DELETE = 1 << 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags_serde_impl!(Scopes, u64);
|
@ -2,6 +2,8 @@ use rand::distr::{Alphanumeric, SampleString};
|
|||||||
use sqlx::{PgPool, Postgres, QueryBuilder};
|
use sqlx::{PgPool, Postgres, QueryBuilder};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
|
use crate::model::member::{Groups, SwimGroups};
|
||||||
|
|
||||||
#[derive(Debug, Validate, sqlx::FromRow)]
|
#[derive(Debug, Validate, sqlx::FromRow)]
|
||||||
pub struct Member {
|
pub struct Member {
|
||||||
#[validate(length(equal = 7))]
|
#[validate(length(equal = 7))]
|
||||||
@ -10,40 +12,22 @@ pub struct Member {
|
|||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
pub registration_token: Option<String>,
|
pub registration_token: Option<String>,
|
||||||
pub diploma: Option<String>,
|
pub diploma: Option<String>,
|
||||||
pub hours: Vec<String>,
|
pub swim_groups: SwimGroups,
|
||||||
pub groups: Vec<String>,
|
pub groups: Groups,
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SearchMember {
|
|
||||||
pub registration_tokens: Option<Vec<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Member {
|
impl Member {
|
||||||
pub async fn search(
|
pub async fn get_many_from_registration_tokens(
|
||||||
transaction: &PgPool,
|
pool: &PgPool,
|
||||||
search: SearchMember,
|
registration_tokens: Vec<String>,
|
||||||
) -> Result<Vec<Self>, sqlx::Error> {
|
) -> Result<Vec<Self>, sqlx::Error> {
|
||||||
if let None = search.registration_tokens {
|
let members = sqlx::query_as!(
|
||||||
return Err(sqlx::Error::RowNotFound);
|
Member,
|
||||||
}
|
"SELECT * FROM members WHERE registration_token = ANY($1);",
|
||||||
|
®istration_tokens
|
||||||
let mut query = QueryBuilder::new("SELECT * from members WHERE 1=1");
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
if let Some(registration_tokens) = search.registration_tokens {
|
.await?;
|
||||||
if registration_tokens.len() == 0 {
|
|
||||||
return Err(sqlx::Error::RowNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
query.push(" AND registration_token = ANY(");
|
|
||||||
query.push_bind(registration_tokens);
|
|
||||||
query.push(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
let members: Vec<Self> = query.build_query_as().fetch_all(transaction).await?;
|
|
||||||
|
|
||||||
if members.len() == 0 {
|
|
||||||
return Err(sqlx::Error::RowNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(members)
|
Ok(members)
|
||||||
}
|
}
|
||||||
@ -69,7 +53,7 @@ impl Member {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut query_builder = QueryBuilder::new(
|
let mut query_builder = QueryBuilder::new(
|
||||||
"INSERT INTO members(member_id, first_name, full_name, registration_token, diploma, hours, groups) "
|
"INSERT INTO members(member_id, first_name, full_name, registration_token, diploma, swim_groups, groups) "
|
||||||
);
|
);
|
||||||
|
|
||||||
query_builder.push_values(members.into_iter(), |mut b, member| {
|
query_builder.push_values(members.into_iter(), |mut b, member| {
|
||||||
@ -80,8 +64,8 @@ impl Member {
|
|||||||
b.push_bind(member.full_name);
|
b.push_bind(member.full_name);
|
||||||
b.push_bind(registration_token);
|
b.push_bind(registration_token);
|
||||||
b.push_bind(member.diploma);
|
b.push_bind(member.diploma);
|
||||||
b.push_bind(member.hours);
|
b.push_bind(member.swim_groups.bits() as i64);
|
||||||
b.push_bind(member.groups);
|
b.push_bind(member.groups.bits() as i64);
|
||||||
});
|
});
|
||||||
|
|
||||||
let query = query_builder.build();
|
let query = query_builder.build();
|
||||||
@ -99,7 +83,21 @@ impl Member {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for member in members {
|
for member in members {
|
||||||
sqlx::query!("UPDATE ONLY members SET first_name = $1, full_name = $2, diploma = $3, hours = $4, groups = $5 WHERE member_id = $6", member.first_name, member.full_name, member.diploma, &member.hours, &member.groups, member.member_id).execute(&mut **transaction).await?;
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE ONLY members
|
||||||
|
SET first_name = $1, full_name = $2, diploma = $3, swim_groups = $4, groups = $5
|
||||||
|
WHERE member_id = $6
|
||||||
|
",
|
||||||
|
member.first_name,
|
||||||
|
member.full_name,
|
||||||
|
member.diploma,
|
||||||
|
member.swim_groups.bits() as i64,
|
||||||
|
member.groups.bits() as i64,
|
||||||
|
member.member_id
|
||||||
|
)
|
||||||
|
.execute(&mut **transaction)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,17 +1,91 @@
|
|||||||
#[derive(Debug, Clone, serde::Serialize)]
|
use bitflags::bitflags;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Name {
|
pub struct Name {
|
||||||
pub first: String,
|
pub first: String,
|
||||||
pub full: String,
|
pub full: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Member {
|
pub struct Member {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub registration_token: Option<String>,
|
pub registration_token: Option<String>,
|
||||||
pub diploma: Option<String>,
|
pub diploma: Option<String>,
|
||||||
pub hours: Vec<String>,
|
pub swim_groups: SwimGroups,
|
||||||
pub groups: Vec<String>,
|
pub groups: Groups,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Groups: u64 {
|
||||||
|
const NONE = 1 << 0;
|
||||||
|
const KADER = 1 << 1;
|
||||||
|
const ZWEMZAKEN = 1 << 2;
|
||||||
|
const WEDSTRIJDEN = 1 << 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SwimGroups: u64 {
|
||||||
|
const NONE = 1 << 0;
|
||||||
|
|
||||||
|
const A1 = 1 << 1;
|
||||||
|
const A2 = 1 << 2;
|
||||||
|
const A3 = 1 << 3;
|
||||||
|
const A4 = 1 << 4;
|
||||||
|
const A5 = 1 << 5;
|
||||||
|
const A6 = 1 << 6;
|
||||||
|
|
||||||
|
const B1 = 1 << 7;
|
||||||
|
const B2 = 1 << 8;
|
||||||
|
const B3 = 1 << 9;
|
||||||
|
const B4 = 1 << 10;
|
||||||
|
const B5 = 1 << 11;
|
||||||
|
const B6 = 1 << 12;
|
||||||
|
|
||||||
|
const C1 = 1 << 13;
|
||||||
|
const C2 = 1 << 14;
|
||||||
|
const C3 = 1 << 15;
|
||||||
|
const C4 = 1 << 16;
|
||||||
|
const C5 = 1 << 17;
|
||||||
|
const C6 = 1 << 18;
|
||||||
|
|
||||||
|
const D1 = 1 << 19;
|
||||||
|
const D2 = 1 << 20;
|
||||||
|
const D3 = 1 << 21;
|
||||||
|
const D4 = 1 << 22;
|
||||||
|
const D5 = 1 << 23;
|
||||||
|
const D6 = 1 << 24;
|
||||||
|
|
||||||
|
const E1 = 1 << 25;
|
||||||
|
const E2 = 1 << 26;
|
||||||
|
const E3 = 1 << 27;
|
||||||
|
const E4 = 1 << 28;
|
||||||
|
const E5 = 1 << 29;
|
||||||
|
const E6 = 1 << 30;
|
||||||
|
|
||||||
|
const Z1 = 1 << 31;
|
||||||
|
const Z2 = 1 << 32;
|
||||||
|
const Z3 = 1 << 33;
|
||||||
|
const Z4 = 1 << 34;
|
||||||
|
const Z5 = 1 << 35;
|
||||||
|
const Z6 = 1 << 36;
|
||||||
|
|
||||||
|
const WEDSTRIJD = 1 << 37;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for SwimGroups {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Self::from_bits(value as u64).unwrap_or(SwimGroups::NONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for Groups {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Self::from_bits(value as u64).unwrap_or(Groups::NONE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::database::model::Member as DbMember;
|
use crate::database::model::Member as DbMember;
|
||||||
@ -25,7 +99,7 @@ impl From<DbMember> for Member {
|
|||||||
},
|
},
|
||||||
registration_token: value.registration_token,
|
registration_token: value.registration_token,
|
||||||
diploma: value.diploma,
|
diploma: value.diploma,
|
||||||
hours: value.hours,
|
swim_groups: value.swim_groups,
|
||||||
groups: value.groups,
|
groups: value.groups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +113,7 @@ impl From<Member> for DbMember {
|
|||||||
full_name: value.name.full,
|
full_name: value.name.full,
|
||||||
registration_token: None,
|
registration_token: None,
|
||||||
diploma: value.diploma,
|
diploma: value.diploma,
|
||||||
hours: value.hours,
|
swim_groups: value.swim_groups,
|
||||||
groups: value.groups,
|
groups: value.groups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use axum::{extract::State, routing::post, Json, Router};
|
use axum::{extract::State, routing::post, Json, Router};
|
||||||
|
|
||||||
use crate::auth::verify_password_hash;
|
use crate::auth::verify_password_hash;
|
||||||
use crate::database::model::member::SearchMember;
|
|
||||||
use crate::database::model::Member as DbMember;
|
use crate::database::model::Member as DbMember;
|
||||||
use crate::database::model::Session as DbSession;
|
use crate::database::model::Session as DbSession;
|
||||||
use crate::database::model::User as DbUser;
|
use crate::database::model::User as DbUser;
|
||||||
@ -55,12 +54,8 @@ pub async fn register<'a>(
|
|||||||
Json(auth_request): Json<RegisterRequest>,
|
Json(auth_request): Json<RegisterRequest>,
|
||||||
) -> Result<String, crate::Error> {
|
) -> Result<String, crate::Error> {
|
||||||
// Get all the members to link with the user
|
// Get all the members to link with the user
|
||||||
let members = DbMember::search(
|
let members =
|
||||||
&state.pool,
|
DbMember::get_many_from_registration_tokens(&state.pool, auth_request.registration_tokens)
|
||||||
SearchMember {
|
|
||||||
registration_tokens: Some(auth_request.registration_tokens),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let member_ids: Vec<String> = members.into_iter().map(|m| m.member_id).collect();
|
let member_ids: Vec<String> = members.into_iter().map(|m| m.member_id).collect();
|
||||||
|
@ -10,7 +10,10 @@ use sqlx::PgPool;
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::{AuthError, Permissions},
|
auth::{AuthError, Permissions},
|
||||||
database::model::Member as DbMember,
|
database::model::Member as DbMember,
|
||||||
model::{member::Name, Member},
|
model::{
|
||||||
|
member::{Groups, Name, SwimGroups},
|
||||||
|
Member,
|
||||||
|
},
|
||||||
util::convert_vec,
|
util::convert_vec,
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
@ -116,7 +119,7 @@ struct Row {
|
|||||||
#[serde(rename = "E-mail")]
|
#[serde(rename = "E-mail")]
|
||||||
email: String,
|
email: String,
|
||||||
#[serde(rename = "Verenigingssporten")]
|
#[serde(rename = "Verenigingssporten")]
|
||||||
hours: String,
|
swim_groups: String,
|
||||||
#[serde(rename = "Diploma dropdown 1")]
|
#[serde(rename = "Diploma dropdown 1")]
|
||||||
diploma: Option<String>,
|
diploma: Option<String>,
|
||||||
}
|
}
|
||||||
@ -161,22 +164,22 @@ impl Row {
|
|||||||
members
|
members
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hours_parsed(&self) -> Vec<String> {
|
fn swim_groups_parsed(&self) -> SwimGroups {
|
||||||
let mut hours: Vec<String> = Vec::new();
|
let mut swim_groups: Vec<String> = Vec::new();
|
||||||
|
|
||||||
let group_parts: Vec<&str> = self.hours.split(", ").collect();
|
let group_parts: Vec<&str> = self.swim_groups.split(", ").collect();
|
||||||
|
|
||||||
for group in group_parts {
|
for group in group_parts {
|
||||||
let hour_parts: Vec<&str> = group.split(" - ").collect();
|
let hour_parts: Vec<&str> = group.split(" - ").collect();
|
||||||
|
|
||||||
for part in hour_parts {
|
if let Some(group) = hour_parts.get(1) {
|
||||||
if &*part != "Groep" {
|
swim_groups.push(group.to_uppercase())
|
||||||
hours.push(part.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hours.into_iter().unique().collect()
|
let swim_groups_string = swim_groups.join("|");
|
||||||
|
|
||||||
|
bitflags::parser::from_str(&swim_groups_string).unwrap_or(SwimGroups::empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +201,8 @@ impl Into<Member> for Row {
|
|||||||
name,
|
name,
|
||||||
registration_token: None,
|
registration_token: None,
|
||||||
diploma: self.diploma.clone(),
|
diploma: self.diploma.clone(),
|
||||||
hours: self.hours_parsed(),
|
swim_groups: self.swim_groups_parsed(),
|
||||||
groups: Vec::new(),
|
groups: Groups::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,7 +264,7 @@ fn generate_diff(members_new: Vec<Member>, members_old: Vec<Member>) -> MembersD
|
|||||||
name: new_member.name.clone(),
|
name: new_member.name.clone(),
|
||||||
registration_token: old_member.registration_token,
|
registration_token: old_member.registration_token,
|
||||||
diploma: new_member.diploma.clone(),
|
diploma: new_member.diploma.clone(),
|
||||||
hours: new_member.hours.clone(),
|
swim_groups: new_member.swim_groups.clone(),
|
||||||
groups: old_member.groups,
|
groups: old_member.groups,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
mod bitflags;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
|
18
server/src/util/bitflags.rs
Normal file
18
server/src/util/bitflags.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! bitflags_serde_impl {
|
||||||
|
($type:ident, $int_type:ident) => {
|
||||||
|
impl serde::Serialize for $type {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_i64(self.bits() as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for $type {
|
||||||
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
let v: i64 = Deserialize::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
Ok($type::from_bits_truncate(v as $int_type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -39,7 +39,7 @@ impl IntoResponse for Error {
|
|||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
let (status_code, code) = match self {
|
let (status_code, code) = match self {
|
||||||
Self::Sqlx(ref err_kind) => match err_kind {
|
Self::Sqlx(ref err_kind) => match err_kind {
|
||||||
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "DATABSE_ROW_NOT_FOUND"),
|
sqlx::Error::RowNotFound => (StatusCode::NOT_FOUND, "DATABASE_ROW_NOT_FOUND"),
|
||||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_ERROR"),
|
_ => (StatusCode::INTERNAL_SERVER_ERROR, "DATABASE_ERROR"),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user