Compare commits

..
Author SHA1 Message Date
phoenix bdaec1aa9d Adding envy module
Release Tagging / release (pull_request) Successful in 2m30s
Rust Build / Check (pull_request) Successful in 2m47s
Rust Build / Rustfmt (pull_request) Failing after 41s
Rust Build / Test Suite (pull_request) Successful in 1m2s
Rust Build / build (pull_request) Successful in 38s
Rust Build / Clippy (pull_request) Successful in 49s
2026-05-31 15:36:22 -04:00
phoenix 1246d64636 Updating dependencies 2026-05-31 15:35:39 -04:00
19 changed files with 317 additions and 1874 deletions
-46
View File
@@ -1,46 +0,0 @@
name: schedtxt_models PR
on:
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
name: Check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: cargo check
fmt:
name: Rustfmt
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy
- run: cargo clippy -- -D warnings
+4 -4
View File
@@ -3,24 +3,24 @@ name: Release Tagging
on: on:
push: push:
branches: branches:
- main - v0.3.0-migrate
pull_request: pull_request:
branches: branches:
- main - v0.3.0-migrate
jobs: jobs:
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v5
with: with:
fetch-depth: 0 # Important for git describe --tags fetch-depth: 0 # Important for git describe --tags
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
components: cargo components: cargo
- name: Extract Version from Cargo.toml - name: Extract Version from Cargo.toml
+14 -20
View File
@@ -3,44 +3,40 @@ name: Rust Build
on: on:
push: push:
branches: branches:
- main - v0.3.0-migrate
pull_request:
concurrency: branches:
group: ${{ github.workflow }}-${{ github.ref }} - v0.3.0-migrate
cancel-in-progress: true
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: cargo check - run: cargo check
test: test:
name: Test Suite name: Test Suite
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: cargo test - run: cargo test
fmt: fmt:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: cargo fmt --all -- --check - run: cargo fmt --all -- --check
@@ -48,11 +44,10 @@ jobs:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy - run: rustup component add clippy
- run: cargo clippy -- -D warnings - run: cargo clippy -- -D warnings
@@ -60,9 +55,8 @@ jobs:
name: build name: build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.97 toolchain: 1.95
- uses: Swatinem/rust-cache@v2
- run: cargo build - run: cargo build
Generated
+196 -1355
View File
File diff suppressed because it is too large Load Diff
+12 -21
View File
@@ -1,30 +1,21 @@
[package] [package]
name = "schedtxt_models" name = "textsender_models"
version = "0.5.3" version = "0.3.0"
edition = "2024" edition = "2024"
rust-version = "1.97" rust-version = "1.95"
license = "MIT" description = "models used for the textsender project"
description = "Models used for the schedtxt project" # license = "MIT"
[dependencies] [dependencies]
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.150" } serde_json = { version = "1.0.149" }
time = { version = "0.3.53", features = ["formatting", "macros", "parsing", "serde"] } rand = { version = "0.10.1" }
sqlx = { version = "0.9.0", features = ["runtime-tokio", "tls-native-tls", "postgres", "time", "uuid"] } time = { version = "0.3.47", features = ["formatting", "macros", "parsing", "serde"] }
uuid = { version = "1.23.5", features = ["v4", "serde"] } uuid = { version = "1.23.1", features = ["v4", "serde"] }
dotenvy = { version = "0.15.7" } dotenvy = { version = "0.15.7" }
const_format = { version = "0.2.36" } const_format = { version = "0.2.36" }
josekit = { version = "0.10.3" } # josekit = { version = "0.10.3" }
utoipa = { version = "5.5.0", features = ["uuid", "time"] } # utoipa = { version = "5.4.0", features = ["uuid", "time"] }
[features]
default = ["full"]
full = ["user", "message", "contact", "config", "envy", "token"]
user = []
message = []
contact = []
config = []
envy = []
token = []
[dev-dependencies] [dev-dependencies]
tempfile = { version = "3.27.0" }
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) 2026 Kun Deng.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-2
View File
@@ -1,2 +0,0 @@
# schedtxt_models
Models library for the `schedtxt` project.
+4 -24
View File
@@ -1,7 +1,9 @@
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct TwilioConfig { pub struct TwilioConfig {
#[serde(alias = "auth_id")] #[serde(alias = "auth_id")]
pub account_sid: String, pub account_sid: uuid::Uuid,
#[serde(skip_serializing_if = "String::is_empty")] #[serde(skip_serializing_if = "String::is_empty")]
pub service_sid: String, pub service_sid: String,
#[serde(skip_serializing_if = "String::is_empty")] #[serde(skip_serializing_if = "String::is_empty")]
@@ -19,25 +21,3 @@ impl TwilioConfig {
println!("Number: {:?}", self.phone_number); println!("Number: {:?}", self.phone_number);
} }
} }
pub fn load_config() -> Result<TwilioConfig, std::io::Error> {
let auth_sid_var = crate::envy::environment::get_env("TWILIO_AUTH_SID");
let service_sid_var = crate::envy::environment::get_env("TWILIO_SERVICE_SID");
let auth_token_var = crate::envy::environment::get_env("TWILIO_AUTH_TOKEN");
let phone_number_var = crate::envy::environment::get_env("TWILIO_PHONE_NUMBER");
if auth_sid_var.value.is_empty()
|| service_sid_var.value.is_empty()
|| auth_token_var.value.is_empty()
|| phone_number_var.value.is_empty()
{
Err(std::io::Error::other("Config not set"))
} else {
Ok(TwilioConfig {
service_sid: service_sid_var.value,
auth_token: auth_token_var.value,
account_sid: auth_sid_var.value,
phone_number: phone_number_var.value,
})
}
}
+12 -13
View File
@@ -1,16 +1,15 @@
#[derive( use serde::{Deserialize, Serialize};
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Contact { pub struct Contact {
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] pub id: uuid::Uuid,
pub id: Option<uuid::Uuid>, // empty?
#[serde(skip_serializing_if = "Option::is_none")] pub firstname: String,
pub firstname: Option<String>, // empty?
#[serde(skip_serializing_if = "Option::is_none")] pub lastname: String,
pub lastname: Option<String>, // empty?
#[serde(skip_serializing_if = "Option::is_none")] pub nickname: String,
pub nickname: Option<String>,
pub phone_number: String, pub phone_number: String,
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] // empty
pub user_id: Option<uuid::Uuid>, pub user_id: uuid::Uuid,
} }
+16 -16
View File
@@ -1,4 +1,4 @@
pub fn get_db_url() -> crate::envy::EnvVar { pub async fn get_db_url() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::DB_URL; let key = crate::envy::keys::DB_URL;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -6,7 +6,7 @@ pub fn get_db_url() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_secret_main_key() -> crate::envy::EnvVar { pub async fn get_secret_main_key() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::SECRET_MAIN_KEY; let key = crate::envy::keys::SECRET_MAIN_KEY;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -14,7 +14,7 @@ pub fn get_secret_main_key() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_service_passphrase() -> crate::envy::EnvVar { pub async fn get_service_passphrase() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::SERVICE_PASSPHRASE; let key = crate::envy::keys::SERVICE_PASSPHRASE;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -22,7 +22,7 @@ pub fn get_service_passphrase() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_secret_key() -> crate::envy::EnvVar { pub async fn get_secret_key() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::SECRET_KEY; let key = crate::envy::keys::SECRET_KEY;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -30,7 +30,7 @@ pub fn get_secret_key() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_root_directory() -> crate::envy::EnvVar { pub async fn get_root_directory() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::ROOT_DIRECTORY; let key = crate::envy::keys::ROOT_DIRECTORY;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -38,23 +38,23 @@ pub fn get_root_directory() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_app_base_api_url() -> crate::envy::EnvVar { pub async fn get_app_base_api_url() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::SCHEDTXT_BASE_API_URL; let key = crate::envy::keys::TEXTSENDER_BASE_API_URL;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_app_auth_base_api_url() -> crate::envy::EnvVar { pub async fn get_app_auth_base_api_url() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::SCHEDTXT_AUTH_BASE_API_URL; let key = crate::envy::keys::TEXTSENDER_AUTH_BASE_API_URL;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_app_env() -> crate::envy::EnvVar { pub async fn get_app_env() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::APP_ENV; let key = crate::envy::keys::APP_ENV;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -62,14 +62,14 @@ pub fn get_app_env() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_backend_port() -> crate::envy::EnvVar { pub async fn get_backend_port() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::BACKEND_PORT; let key = crate::envy::keys::BACKEND_PORT;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_frontend_url() -> crate::envy::EnvVar { pub async fn get_frontend_url() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::FRONTEND_URL; let key = crate::envy::keys::FRONTEND_URL;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -77,7 +77,7 @@ pub fn get_frontend_url() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_rust_log() -> crate::envy::EnvVar { pub async fn get_rust_log() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::RUST_LOG; let key = crate::envy::keys::RUST_LOG;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -85,7 +85,7 @@ pub fn get_rust_log() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub fn get_allowed_origins() -> crate::envy::EnvVar { pub async fn get_allowed_origins() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::ALLOWED_ORIGINS; let key = crate::envy::keys::ALLOWED_ORIGINS;
let value = std::env::var(key).expect(key); let value = std::env::var(key).expect(key);
@@ -96,8 +96,8 @@ pub fn get_allowed_origins() -> crate::envy::EnvVar {
envvar envvar
} }
/// Get environment not specified in the library /// Get environment not specified in the code
pub fn get_env(environment: &str) -> crate::envy::EnvVar { pub async fn get_env(environment: &str) -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let my_error = format!("{environment} {}", crate::envy::keys::error::GENERAL_ERROR); let my_error = format!("{environment} {}", crate::envy::keys::error::GENERAL_ERROR);
let value = std::env::var(environment).expect(&my_error); let value = std::env::var(environment).expect(&my_error);
+7 -8
View File
@@ -2,7 +2,7 @@
pub const DB_URL: &str = "DATABASE_URL"; pub const DB_URL: &str = "DATABASE_URL";
/// Environment key for secret main key /// Environment key for secret main key
/// Used for the schedtxt app /// Used for the textsender app
pub const SECRET_MAIN_KEY: &str = "SECRET_MAIN_KEY"; pub const SECRET_MAIN_KEY: &str = "SECRET_MAIN_KEY";
/// Environment key for service logins /// Environment key for service logins
@@ -15,11 +15,11 @@ pub const SECRET_KEY: &str = "SECRET_KEY";
/// Environment key for root directory for the textsender app /// Environment key for root directory for the textsender app
pub const ROOT_DIRECTORY: &str = "ROOT_DIRECTORY"; pub const ROOT_DIRECTORY: &str = "ROOT_DIRECTORY";
/// Environment key for schedtxt api url /// Environment key for textsender api url
pub const SCHEDTXT_BASE_API_URL: &str = "SCHEDTXT_BASE_API_URL"; pub const TEXTSENDER_BASE_API_URL: &str = "TEXTSENDER_BASE_API_URL";
/// Environment key for textsender auth api url /// Environment key for textsender auth api url
pub const SCHEDTXT_AUTH_BASE_API_URL: &str = "SCHEDTXT_AUTH_BASE_API_URL"; pub const TEXTSENDER_AUTH_BASE_API_URL: &str = "TEXTSENDER_AUTH_BASE_API_URL";
/// Environment key for App status /// Environment key for App status
pub const APP_ENV: &str = "APP_ENV"; pub const APP_ENV: &str = "APP_ENV";
@@ -41,10 +41,9 @@ pub mod error {
pub const SERVICE_LOGIN: &str = concatcp!(super::SERVICE_PASSPHRASE, " ", GENERAL_ERROR); pub const SERVICE_LOGIN: &str = concatcp!(super::SERVICE_PASSPHRASE, " ", GENERAL_ERROR);
pub const SECRET_KEY: &str = concatcp!(super::SECRET_KEY, " ", GENERAL_ERROR); pub const SECRET_KEY: &str = concatcp!(super::SECRET_KEY, " ", GENERAL_ERROR);
pub const ROOT_DIRECTORY: &str = concatcp!(super::ROOT_DIRECTORY, " ", GENERAL_ERROR); pub const ROOT_DIRECTORY: &str = concatcp!(super::ROOT_DIRECTORY, " ", GENERAL_ERROR);
pub const SCHEDTXT_BASE_API_URL: &str = pub const TEXTSENDER_BASE_API_URL: &str = concatcp!(super::TEXTSENDER_BASE_API_URL, " ", GENERAL_ERROR);
concatcp!(super::SCHEDTXT_BASE_API_URL, " ", GENERAL_ERROR); pub const TEXTSENDER_AUTH_BASE_API_URL: &str =
pub const SCHEDTXT_AUTH_BASE_API_URL: &str = concatcp!(super::TEXTSENDER_AUTH_BASE_API_URL, " ", GENERAL_ERROR);
concatcp!(super::SCHEDTXT_AUTH_BASE_API_URL, " ", GENERAL_ERROR);
pub const APP_ENV: &str = concatcp!(super::APP_ENV, " ", GENERAL_ERROR); pub const APP_ENV: &str = concatcp!(super::APP_ENV, " ", GENERAL_ERROR);
pub const BACKEND_PORT: &str = concatcp!(super::BACKEND_PORT, " ", GENERAL_ERROR); pub const BACKEND_PORT: &str = concatcp!(super::BACKEND_PORT, " ", GENERAL_ERROR);
pub const FRONTEND_URL: &str = concatcp!(super::FRONTEND_URL, " ", GENERAL_ERROR); pub const FRONTEND_URL: &str = concatcp!(super::FRONTEND_URL, " ", GENERAL_ERROR);
+8 -25
View File
@@ -20,17 +20,16 @@ pub fn init_envvar(key: &str, value: &str) -> EnvVar {
} }
pub fn init_delimiter(envvar: &mut EnvVar, delimiter: char) { pub fn init_delimiter(envvar: &mut EnvVar, delimiter: char) {
let amount_of_delimiters_found: i32 = { let mut amount_of_delimiters_found: i32 = 0;
let mut count = 0;
for v in envvar.value.chars() { for v in envvar.value.chars() {
if v == delimiter { if v == delimiter {
count += 1; amount_of_delimiters_found += 1;
} }
} }
count
};
let has_delimiter = amount_of_delimiters_found >= 1; let has_delimiter = amount_of_delimiters_found >= 1;
if has_delimiter { if has_delimiter {
envvar.has_delimiter = has_delimiter; envvar.has_delimiter = has_delimiter;
envvar.delimiter = delimiter; envvar.delimiter = delimiter;
@@ -39,19 +38,3 @@ pub fn init_delimiter(envvar: &mut EnvVar, delimiter: char) {
} }
} }
#[cfg(test)]
mod tests {
#[test]
fn test_init_delimiter() {
let delimiter: char = ',';
let value = format!("red,green,white,black");
let mut env_var = super::EnvVar {
key: String::from("COLORS"),
value: value.clone(),
..Default::default()
};
super::init_delimiter(&mut env_var, delimiter);
assert_eq!(value, env_var.value, "Colors do not match");
}
}
+3 -41
View File
@@ -2,52 +2,14 @@
/// extract it into some strings /// extract it into some strings
pub fn delimitize(var: &crate::envy::EnvVar) -> Result<Vec<String>, std::io::Error> { pub fn delimitize(var: &crate::envy::EnvVar) -> Result<Vec<String>, std::io::Error> {
if var.has_delimiter { if var.has_delimiter {
let values: Vec<String> = match var Ok(var
.value .value
.split(var.delimiter) .split(var.delimiter)
.map(|c| c.parse::<String>()) .map(|c| c.parse::<String>().unwrap())
.collect() .collect())
{
Ok(v) => v,
Err(err) => {
return Err(std::io::Error::other(err));
}
};
Ok(values)
} else { } else {
Err(std::io::Error::other( Err(std::io::Error::other(
"Environment variable does not have a delimiter", "Environment variable does not have a delimiter",
)) ))
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delimitize() {
let envvar = crate::envy::EnvVar {
key: "OCEANS".to_string(),
value: "Atlantic|Pacific|Indian|Arctic".to_string(),
has_delimiter: true,
delimiter: '|',
};
let all_values = vec![
"Atlantic".to_string(),
"Pacific".to_string(),
"Indian".to_string(),
"Arctic".to_string(),
];
match delimitize(&envvar) {
Ok(values) => {
assert_eq!(values, all_values, "Failure in delimitizing EnvVar values");
}
Err(err) => {
assert!(false, "Error delimitizing: {err:?}");
}
}
}
}
-9
View File
@@ -4,12 +4,3 @@ pub mod envy;
pub mod message; pub mod message;
pub mod token; pub mod token;
pub mod user; pub mod user;
pub mod init {
pub fn is_uuid_nil(uuid: &Option<uuid::Uuid>) -> bool {
match uuid {
Some(id) => id.is_nil(),
None => true,
}
}
}
+6 -9
View File
@@ -1,17 +1,14 @@
/// Message status - Instant use serde::{Deserialize, Serialize};
pub const MESSAGE_EVENT_RESPONSE_STATUS_INSTANT: &str = "INSTANT"; pub const MESSAGE_EVENT_RESPONSE_STATUS_INSTANT: &str = "INSTANT";
/// Message status - Scheduled
pub const MESSAGE_EVENT_RESPONSE_STATUS_SCHEDULED: &str = "SCHEDULED"; pub const MESSAGE_EVENT_RESPONSE_STATUS_SCHEDULED: &str = "SCHEDULED";
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct MessageEventResponse { pub struct MessageEventResponse {
pub id: uuid::Uuid, pub id: uuid::Uuid,
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] pub scheduled_message_event_id: uuid::Uuid,
pub scheduled_message_event_id: Option<uuid::Uuid>, // Json field
/// Stores a json response of the sent message pub response: String,
pub response: serde_json::Value,
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
pub contact_id: uuid::Uuid, pub contact_id: uuid::Uuid,
pub message_id: uuid::Uuid, pub message_id: uuid::Uuid,
+5 -7
View File
@@ -1,13 +1,11 @@
pub mod event; pub mod event;
pub mod scheduling; pub mod scheduling;
#[derive( use serde::{Deserialize, Serialize};
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Message { pub struct Message {
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] pub id: uuid::Uuid,
pub id: Option<uuid::Uuid>,
pub content: String, pub content: String,
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] pub user_id: uuid::Uuid,
pub user_id: Option<uuid::Uuid>,
} }
+14 -16
View File
@@ -1,26 +1,24 @@
#[derive( use serde::{Deserialize, Serialize};
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ScheduledMessageEvent { pub struct ScheduledMessageEvent {
pub id: uuid::Uuid, id: uuid::Uuid,
pub contact_id: uuid::Uuid, contact_id: uuid::Uuid,
pub message_id: uuid::Uuid, message_id: uuid::Uuid,
pub scheduled_message_id: uuid::Uuid, scheduled_message_id: uuid::Uuid,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub created: Option<time::OffsetDateTime>, created: Option<time::OffsetDateTime>,
} }
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct ScheduledMessage { pub struct ScheduledMessage {
pub id: uuid::Uuid, id: uuid::Uuid,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub scheduled: Option<time::OffsetDateTime>, scheduled: Option<time::OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub created: Option<time::OffsetDateTime>, created: Option<time::OffsetDateTime>,
pub status: String, status: String,
pub user_id: uuid::Uuid, user_id: uuid::Uuid,
} }
pub const READY: &str = "READY"; pub const READY: &str = "READY";
+5 -197
View File
@@ -1,40 +1,18 @@
use serde::Deserialize; use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Claims { pub struct Claims {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
#[serde(skip_serializing_if = "Option::is_none")] pub role: String,
pub role: Option<String>,
#[serde(alias = "iss")] #[serde(alias = "iss")]
pub issued: String, pub issued: String,
#[serde(alias = "exp")] #[serde(alias = "exp")]
#[serde(deserialize_with = "deserialize_i64_from_f64")]
pub expired: i64, pub expired: i64,
#[serde(alias = "iat")] #[serde(alias = "iat")]
#[serde(deserialize_with = "deserialize_i64_from_f64")]
pub issued_at: i64, pub issued_at: i64,
} }
fn deserialize_i64_from_f64<'de, D>(deserializer: D) -> Result<i64, D::Error> #[derive(Clone, Debug, Default, Deserialize, Serialize)]
where
D: serde::Deserializer<'de>,
{
let val = f64::deserialize(deserializer)?;
// Handle NaN and infinity cases
if val.is_nan() || val.is_infinite() {
return Err(serde::de::Error::custom("invalid float value"));
}
// Round to nearest integer and convert
let rounded = val.round();
// Check if the rounded value can fit in i64
if rounded < (i64::MIN as f64) || rounded > (i64::MAX as f64) {
Err(serde::de::Error::custom("float out of i64 range"))
} else {
Ok(rounded as i64)
}
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct LoginResult { pub struct LoginResult {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
pub access_token: String, pub access_token: String,
@@ -43,175 +21,5 @@ pub struct LoginResult {
pub issued_at: i64, pub issued_at: i64,
} }
/// Alias for LoginResult // Alias for LoginResult
pub type Login = LoginResult; pub type Login = LoginResult;
pub fn get_issued() -> time::Result<time::OffsetDateTime> {
Ok(time::OffsetDateTime::now_utc())
}
mod util {
pub fn time_to_std_time(
provided_time: &time::OffsetDateTime,
) -> Result<std::time::SystemTime, std::time::SystemTimeError> {
let converted: std::time::SystemTime = (*provided_time).into();
Ok(converted)
}
}
#[derive(Debug, utoipa::ToSchema)]
pub struct TokenResource {
pub message: String,
pub issuer: String,
pub audiences: Vec<String>,
pub user_id: uuid::Uuid,
}
/// Token type
pub const TOKEN_TYPE: &str = "JWT";
pub struct CreateTokenResult {
pub access_token: String,
pub issued: i64,
pub expires_in: i64,
pub token_issued: time::OffsetDateTime,
}
pub fn create_token(
key: &str,
token_resource: TokenResource,
duration: time::Duration,
) -> Result<CreateTokenResult, josekit::JoseError> {
let mut header = josekit::jws::JwsHeader::new();
header.set_token_type(TOKEN_TYPE);
match init_payload(token_resource, duration) {
Ok((payload, issued, expire)) => {
let signer = match josekit::jws::alg::hmac::HmacJwsAlgorithm::Hs256
.signer_from_bytes(key.as_bytes())
{
Ok(signer) => signer,
Err(err) => {
return Err(err);
}
};
let access_token = match josekit::jwt::encode_with_signer(&payload, &header, &signer) {
Ok(access_token) => access_token,
Err(err) => {
return Err(err);
}
};
Ok(CreateTokenResult {
access_token,
issued: issued.unix_timestamp(),
expires_in: expire.unix_timestamp(),
token_issued: issued,
})
}
Err(e) => Err(josekit::JoseError::InvalidClaim(e.into())),
}
}
fn init_payload(
token_resource: TokenResource,
duration: time::Duration,
) -> Result<
(
josekit::jwt::JwtPayload,
time::OffsetDateTime,
time::OffsetDateTime,
),
josekit::JoseError,
> {
let mut payload = josekit::jwt::JwtPayload::new();
payload.set_subject(token_resource.message);
payload.set_issuer(token_resource.issuer);
payload.set_audience(token_resource.audiences);
if !token_resource.user_id.is_nil() {
match payload.set_claim("user_id", Some(serde_json::json!(token_resource.user_id))) {
Ok(_) => {}
Err(err) => {
return Err(err);
}
}
}
match init_payload_issued(&mut payload, duration) {
Ok((issued, expire)) => Ok((payload, issued, expire)),
Err(err) => Err(err),
}
}
fn init_payload_issued(
payload: &mut josekit::jwt::JwtPayload,
duration: time::Duration,
) -> Result<(time::OffsetDateTime, time::OffsetDateTime), josekit::JoseError> {
match get_issued() {
Ok(issued) => {
let expire = issued + duration;
let issued_at = match util::time_to_std_time(&issued) {
Ok(issued_at) => issued_at,
Err(err) => {
return Err(josekit::JoseError::InvalidClaim(err.into()));
}
};
let expires_at = match util::time_to_std_time(&expire) {
Ok(expires_at) => expires_at,
Err(err) => {
return Err(josekit::JoseError::InvalidClaim(err.into()));
}
};
payload.set_issued_at(&issued_at);
payload.set_expires_at(&expires_at);
Ok((issued, expire))
}
Err(err) => Err(josekit::JoseError::InvalidClaim(err.into())),
}
}
#[cfg(test)]
mod tests {
const TEST_MESSAGE: &str = "Testing for schedtxt";
const TEST_ISSUER: &str = "schedtxt-test";
const TEST_AUDIENCE: &str = "area-test";
const TEST_USER_ID: uuid::Uuid = uuid::uuid!("9ab1c75f-d184-4913-ae99-544b4dcfcb41");
const TEST_KEY: &str = "8342nhf7ycrt4983q7ryfc93w478ryfc3w9487ryfc342w98i7cy";
#[test]
fn test_create_token() {
let token_resource = super::TokenResource {
message: String::from(TEST_MESSAGE),
issuer: String::from(TEST_ISSUER),
audiences: vec![String::from(TEST_AUDIENCE)],
user_id: TEST_USER_ID,
};
let token_duration = time::Duration::minutes(30);
let key = String::from(TEST_KEY);
match super::create_token(&key, token_resource, token_duration) {
Ok(cst) => match time::OffsetDateTime::from_unix_timestamp(cst.issued) {
Ok(d_result) => {
let they_match = {
d_result.year() == cst.token_issued.year()
&& d_result.month() == cst.token_issued.month()
&& d_result.day() == cst.token_issued.day()
&& d_result.hour() == cst.token_issued.hour()
&& d_result.minute() == cst.token_issued.minute()
&& d_result.second() == cst.token_issued.second()
};
assert!(they_match, "Issued times do not match");
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
},
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
}
}
+11 -39
View File
@@ -1,39 +1,34 @@
#[derive( use serde::{Deserialize, Serialize};
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct User { pub struct User {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub phone_number: String, pub phone_number: String,
pub username: String, pub username: String,
pub password: String, pub password: String,
// Firstname is a pointer
pub firstname: String, pub firstname: String,
// Lastname is a pointer
pub lastname: String, pub lastname: String,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub created: Option<time::OffsetDateTime>, pub created: Option<time::OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub last_login: Option<time::OffsetDateTime>, pub last_login: Option<time::OffsetDateTime>,
#[serde(skip)]
pub salt_id: uuid::Uuid,
} }
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct ServiceUser { pub struct ServiceUser {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub username: String, pub username: String,
pub passphrase: String, pub passphrase: String,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub created: Option<time::OffsetDateTime>, pub created: Option<time::OffsetDateTime>,
// When serializing to json, if empty do not populate
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
pub last_login: Option<time::OffsetDateTime>, pub last_login: Option<time::OffsetDateTime>,
#[serde(skip)]
pub salt_id: uuid::Uuid,
} }
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct Organization { pub struct Organization {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub name: String, pub name: String,
@@ -41,9 +36,7 @@ pub struct Organization {
pub created: Option<time::OffsetDateTime>, pub created: Option<time::OffsetDateTime>,
} }
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct UserLoginHistory { pub struct UserLoginHistory {
pub id: uuid::Uuid, pub id: uuid::Uuid,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
@@ -51,9 +44,7 @@ pub struct UserLoginHistory {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
} }
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct ServiceUserLoginHistory { pub struct ServiceUserLoginHistory {
pub id: uuid::Uuid, pub id: uuid::Uuid,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
@@ -61,9 +52,7 @@ pub struct ServiceUserLoginHistory {
pub service_user_id: uuid::Uuid, pub service_user_id: uuid::Uuid,
} }
#[derive( #[derive(Clone, Debug, Default, Deserialize, Serialize)]
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct UserProfile { pub struct UserProfile {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
pub phone_number: String, pub phone_number: String,
@@ -76,23 +65,6 @@ pub struct UserProfile {
pub last_login: Option<time::OffsetDateTime>, pub last_login: Option<time::OffsetDateTime>,
} }
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
pub struct Salt {
pub id: uuid::Uuid,
#[serde(skip_serializing_if = "String::is_empty")]
pub salt: String,
}
impl Salt {
pub fn to_json(&self, output_pretty: bool) -> Result<String, serde_json::Error> {
if output_pretty {
serde_json::to_string_pretty(&self)
} else {
serde_json::to_string(&self)
}
}
}
pub fn init_user_profile(usr: &User) -> UserProfile { pub fn init_user_profile(usr: &User) -> UserProfile {
UserProfile { UserProfile {
user_id: usr.id, user_id: usr.id,