Compare commits

..
7 Commits
Author SHA1 Message Date
phoenix 8b49b63d59 Adding license (#36)
Release Tagging / release (push) Successful in 1m25s
Rust Build / Check (push) Successful in 1m24s
Rust Build / Test Suite (push) Successful in 1m26s
Rust Build / Rustfmt (push) Successful in 53s
Rust Build / build (push) Successful in 1m13s
Rust Build / Clippy (push) Successful in 3m37s
Reviewed-on: #36
2026-07-14 11:15:19 -04:00
phoenix 597af4e84c Change name (#35)
Release Tagging / release (push) Successful in 32s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Test Suite (push) Successful in 1m27s
Rust Build / Clippy (push) Successful in 1m12s
Rust Build / build (push) Successful in 55s
Rust Build / Check (push) Successful in 2m37s
Reviewed-on: #35
2026-07-13 11:28:29 -04:00
phoenix 02061bf7a3 Update rust (#34)
Rust Build / Check (push) Successful in 1m21s
Rust Build / build (push) Successful in 2m4s
Rust Build / Clippy (push) Successful in 2m8s
Release Tagging / release (push) Successful in 41s
Rust Build / Rustfmt (push) Successful in 37s
Rust Build / Test Suite (push) Successful in 1m41s
Reviewed-on: phoenix/textsender_models#34
2026-07-10 11:30:31 -04:00
phoenix 151eeaacf6 Update rust (#33)
Release Tagging / release (push) Successful in 45s
Rust Build / Check (push) Successful in 1m8s
Rust Build / Rustfmt (push) Successful in 28s
Rust Build / Test Suite (push) Successful in 1m31s
Rust Build / build (push) Successful in 1m5s
Rust Build / Clippy (push) Successful in 2m24s
Reviewed-on: phoenix/textsender_models#33
2026-07-05 16:49:32 -04:00
phoenix 137398405f Code refactoring (#32)
Release Tagging / release (push) Successful in 52s
Rust Build / Check (push) Successful in 1m9s
Rust Build / Rustfmt (push) Successful in 30s
Rust Build / Test Suite (push) Successful in 1m29s
Rust Build / Clippy (push) Successful in 1m2s
Rust Build / build (push) Successful in 57s
Reviewed-on: phoenix/textsender_models#32
2026-07-02 13:18:41 -04:00
phoenix 6508c6c535 tsk-30: Remove async from envy emodukle (#31)
Release Tagging / release (push) Successful in 58s
Rust Build / Check (push) Successful in 1m9s
Rust Build / Test Suite (push) Successful in 1m21s
Rust Build / Rustfmt (push) Successful in 46s
Rust Build / Clippy (push) Successful in 1m3s
Rust Build / build (push) Successful in 1m16s
Closes #30

Reviewed-on: phoenix/textsender_models#31
2026-07-01 23:00:39 -04:00
phoenix c1d391939b Update models (#29)
Release Tagging / release (push) Successful in 48s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Test Suite (push) Successful in 1m26s
Rust Build / Clippy (push) Successful in 1m27s
Rust Build / build (push) Successful in 1m29s
Rust Build / Check (push) Successful in 1m12s
textsender_models PR / Rustfmt (pull_request) Successful in 54s
textsender_models PR / Check (pull_request) Successful in 1m20s
Release Tagging / release (pull_request) Successful in 37s
textsender_models PR / Clippy (pull_request) Successful in 1m5s
Reviewed-on: phoenix/textsender_models#29
2026-06-27 17:20:20 -04:00
18 changed files with 1576 additions and 323 deletions
+11 -7
View File
@@ -1,19 +1,23 @@
name: textsender_models PR name: schedtxt_models PR
on: on:
pull_request: pull_request:
branches: branches:
- main - main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
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@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo check - run: cargo check
@@ -21,10 +25,10 @@ jobs:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - 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
@@ -33,10 +37,10 @@ jobs:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - 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
+2 -2
View File
@@ -13,14 +13,14 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5 uses: actions/checkout@v6
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.96 toolchain: 1.97
components: cargo components: cargo
- name: Extract Version from Cargo.toml - name: Extract Version from Cargo.toml
+14 -10
View File
@@ -5,15 +5,19 @@ on:
branches: branches:
- main - main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
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@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo check - run: cargo check
@@ -21,10 +25,10 @@ jobs:
name: Test Suite name: Test Suite
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo test - run: cargo test
@@ -32,10 +36,10 @@ jobs:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - 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
@@ -44,10 +48,10 @@ jobs:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - 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
@@ -56,9 +60,9 @@ jobs:
name: build name: build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo build - run: cargo build
Generated
+1226 -196
View File
File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -1,15 +1,17 @@
[package] [package]
name = "textsender_models" name = "schedtxt_models"
version = "0.4.0" version = "0.5.3"
edition = "2024" edition = "2024"
rust-version = "1.96" rust-version = "1.97"
description = "Models used for the textsender project" license = "MIT"
description = "Models used for the schedtxt project"
[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.150" }
time = { version = "0.3.49", features = ["formatting", "macros", "parsing", "serde"] } time = { version = "0.3.53", features = ["formatting", "macros", "parsing", "serde"] }
uuid = { version = "1.23.3", features = ["v4", "serde"] } sqlx = { version = "0.9.0", features = ["runtime-tokio", "tls-native-tls", "postgres", "time", "uuid"] }
uuid = { version = "1.23.5", 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" }
+22
View File
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,2 @@
# schedtxt_models
Models library for the `schedtxt` project.
+23 -3
View File
@@ -1,6 +1,4 @@
use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, serde::Deserialize, serde::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: String,
@@ -21,3 +19,25 @@ 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,
})
}
}
+3 -6
View File
@@ -1,16 +1,13 @@
use serde::{Deserialize, Serialize}; #[derive(
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] )]
pub struct Contact { pub struct Contact {
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] #[serde(skip_serializing_if = "crate::init::is_uuid_nil")]
pub id: Option<uuid::Uuid>, pub id: Option<uuid::Uuid>,
// empty?
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub firstname: Option<String>, pub firstname: Option<String>,
// empty?
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub lastname: Option<String>, pub lastname: Option<String>,
// empty?
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>, pub nickname: Option<String>,
pub phone_number: String, pub phone_number: String,
+16 -16
View File
@@ -1,4 +1,4 @@
pub async fn get_db_url() -> crate::envy::EnvVar { pub 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 async fn get_db_url() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_secret_main_key() -> crate::envy::EnvVar { pub 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 async fn get_secret_main_key() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_service_passphrase() -> crate::envy::EnvVar { pub 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 async fn get_service_passphrase() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_secret_key() -> crate::envy::EnvVar { pub 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 async fn get_secret_key() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_root_directory() -> crate::envy::EnvVar { pub 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 async fn get_root_directory() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_app_base_api_url() -> crate::envy::EnvVar { pub fn get_app_base_api_url() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::TEXTSENDER_BASE_API_URL; let key = crate::envy::keys::SCHEDTXT_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 async fn get_app_auth_base_api_url() -> crate::envy::EnvVar { pub fn get_app_auth_base_api_url() -> crate::envy::EnvVar {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let key = crate::envy::keys::TEXTSENDER_AUTH_BASE_API_URL; let key = crate::envy::keys::SCHEDTXT_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 async fn get_app_env() -> crate::envy::EnvVar { pub 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 async fn get_app_env() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_backend_port() -> crate::envy::EnvVar { pub 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 async fn get_frontend_url() -> crate::envy::EnvVar { pub 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 async fn get_frontend_url() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_rust_log() -> crate::envy::EnvVar { pub 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 async fn get_rust_log() -> crate::envy::EnvVar {
crate::envy::init_envvar(key, &value) crate::envy::init_envvar(key, &value)
} }
pub async fn get_allowed_origins() -> crate::envy::EnvVar { pub 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 async fn get_allowed_origins() -> crate::envy::EnvVar {
envvar envvar
} }
/// Get environment not specified in the code /// Get environment not specified in the library
pub async fn get_env(environment: &str) -> crate::envy::EnvVar { pub 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);
+8 -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 textsender app /// Used for the schedtxt 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 textsender api url /// Environment key for schedtxt api url
pub const TEXTSENDER_BASE_API_URL: &str = "TEXTSENDER_BASE_API_URL"; pub const SCHEDTXT_BASE_API_URL: &str = "SCHEDTXT_BASE_API_URL";
/// Environment key for textsender auth api url /// Environment key for textsender auth api url
pub const TEXTSENDER_AUTH_BASE_API_URL: &str = "TEXTSENDER_AUTH_BASE_API_URL"; pub const SCHEDTXT_AUTH_BASE_API_URL: &str = "SCHEDTXT_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,10 @@ 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 TEXTSENDER_BASE_API_URL: &str = pub const SCHEDTXT_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);
+22 -4
View File
@@ -20,16 +20,17 @@ 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 mut amount_of_delimiters_found: i32 = 0; let amount_of_delimiters_found: i32 = {
let mut count = 0;
for v in envvar.value.chars() { for v in envvar.value.chars() {
if v == delimiter { if v == delimiter {
amount_of_delimiters_found += 1; count += 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;
@@ -37,3 +38,20 @@ pub fn init_delimiter(envvar: &mut EnvVar, delimiter: char) {
envvar.has_delimiter = has_delimiter; envvar.has_delimiter = has_delimiter;
} }
} }
#[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");
}
}
+41 -3
View File
@@ -2,14 +2,52 @@
/// 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 {
Ok(var let values: Vec<String> = match var
.value .value
.split(var.delimiter) .split(var.delimiter)
.map(|c| c.parse::<String>().unwrap()) .map(|c| c.parse::<String>())
.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 -6
View File
@@ -1,14 +1,17 @@
use serde::{Deserialize, Serialize}; /// Message status - Instant
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(Clone, Debug, Default, Deserialize, Serialize)] #[derive(
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,
pub scheduled_message_event_id: uuid::Uuid, #[serde(skip_serializing_if = "crate::init::is_uuid_nil")]
// Json field pub scheduled_message_event_id: Option<uuid::Uuid>,
pub response: String, /// Stores a json response of the sent message
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,
+3 -3
View File
@@ -1,9 +1,9 @@
pub mod event; pub mod event;
pub mod scheduling; pub mod scheduling;
use serde::{Deserialize, Serialize}; #[derive(
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] )]
pub struct Message { pub struct Message {
#[serde(skip_serializing_if = "crate::init::is_uuid_nil")] #[serde(skip_serializing_if = "crate::init::is_uuid_nil")]
pub id: Option<uuid::Uuid>, pub id: Option<uuid::Uuid>,
+16 -14
View File
@@ -1,24 +1,26 @@
use serde::{Deserialize, Serialize}; #[derive(
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] )]
pub struct ScheduledMessageEvent { pub struct ScheduledMessageEvent {
id: uuid::Uuid, pub id: uuid::Uuid,
contact_id: uuid::Uuid, pub contact_id: uuid::Uuid,
message_id: uuid::Uuid, pub message_id: uuid::Uuid,
scheduled_message_id: uuid::Uuid, pub scheduled_message_id: uuid::Uuid,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
created: Option<time::OffsetDateTime>, pub created: Option<time::OffsetDateTime>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
)]
pub struct ScheduledMessage { pub struct ScheduledMessage {
id: uuid::Uuid, pub id: uuid::Uuid,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
scheduled: Option<time::OffsetDateTime>, pub scheduled: Option<time::OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")] #[serde(with = "time::serde::rfc3339::option")]
created: Option<time::OffsetDateTime>, pub created: Option<time::OffsetDateTime>,
status: String, pub status: String,
user_id: uuid::Uuid, pub user_id: uuid::Uuid,
} }
pub const READY: &str = "READY"; pub const READY: &str = "READY";
+129 -24
View File
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::Deserialize;
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Claims { pub struct Claims {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -34,7 +34,7 @@ where
} }
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] #[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,
@@ -54,7 +54,7 @@ mod util {
pub fn time_to_std_time( pub fn time_to_std_time(
provided_time: &time::OffsetDateTime, provided_time: &time::OffsetDateTime,
) -> Result<std::time::SystemTime, std::time::SystemTimeError> { ) -> Result<std::time::SystemTime, std::time::SystemTimeError> {
let converted = std::time::SystemTime::from(*provided_time); let converted: std::time::SystemTime = (*provided_time).into();
Ok(converted) Ok(converted)
} }
} }
@@ -70,21 +70,65 @@ pub struct TokenResource {
/// Token type /// Token type
pub const TOKEN_TYPE: &str = "JWT"; 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( pub fn create_token(
key: &String, key: &str,
token_resource: &TokenResource, token_resource: TokenResource,
duration: time::Duration, duration: time::Duration,
) -> Result<(String, i64), josekit::JoseError> { ) -> Result<CreateTokenResult, josekit::JoseError> {
let mut header = josekit::jws::JwsHeader::new(); let mut header = josekit::jws::JwsHeader::new();
header.set_token_type(TOKEN_TYPE); 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(); let mut payload = josekit::jwt::JwtPayload::new();
let message = &token_resource.message; payload.set_subject(token_resource.message);
let issuer = &token_resource.issuer; payload.set_issuer(token_resource.issuer);
let audiences: &Vec<String> = &token_resource.audiences; payload.set_audience(token_resource.audiences);
payload.set_subject(message);
payload.set_issuer(issuer);
payload.set_audience(audiences.clone());
if !token_resource.user_id.is_nil() { if !token_resource.user_id.is_nil() {
match payload.set_claim("user_id", Some(serde_json::json!(token_resource.user_id))) { match payload.set_claim("user_id", Some(serde_json::json!(token_resource.user_id))) {
Ok(_) => {} Ok(_) => {}
@@ -93,20 +137,81 @@ pub fn create_token(
} }
} }
} }
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() { match get_issued() {
Ok(issued) => { Ok(issued) => {
let expire = issued + duration; let expire = issued + duration;
payload.set_issued_at(&util::time_to_std_time(&issued).unwrap()); let issued_at = match util::time_to_std_time(&issued) {
payload.set_expires_at(&util::time_to_std_time(&expire).unwrap()); Ok(issued_at) => issued_at,
Err(err) => {
let signer = josekit::jws::alg::hmac::HmacJwsAlgorithm::Hs256 return Err(josekit::JoseError::InvalidClaim(err.into()));
.signer_from_bytes(key.as_bytes()) }
.unwrap(); };
Ok(( let expires_at = match util::time_to_std_time(&expire) {
josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(), Ok(expires_at) => expires_at,
(expire - time::OffsetDateTime::UNIX_EPOCH).whole_seconds(), 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:?}");
}
} }
Err(e) => Err(josekit::JoseError::InvalidClaim(e.into())),
} }
} }
+19 -13
View File
@@ -1,14 +1,12 @@
use serde::{Deserialize, Serialize}; #[derive(
Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, utoipa::ToSchema,
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] )]
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>,
@@ -18,21 +16,24 @@ pub struct User {
pub salt_id: uuid::Uuid, pub salt_id: uuid::Uuid,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(
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)] #[serde(skip)]
pub salt_id: uuid::Uuid, pub salt_id: uuid::Uuid,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(
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,
@@ -40,7 +41,9 @@ pub struct Organization {
pub created: Option<time::OffsetDateTime>, pub created: Option<time::OffsetDateTime>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(
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")]
@@ -48,7 +51,9 @@ pub struct UserLoginHistory {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(
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")]
@@ -56,7 +61,9 @@ pub struct ServiceUserLoginHistory {
pub service_user_id: uuid::Uuid, pub service_user_id: uuid::Uuid,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(
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,
@@ -69,9 +76,8 @@ pub struct UserProfile {
pub last_login: Option<time::OffsetDateTime>, pub last_login: Option<time::OffsetDateTime>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
pub struct Salt { pub struct Salt {
// #[serde(skip_serializing_if = "crate::init::is_uuid_nil")]
pub id: uuid::Uuid, pub id: uuid::Uuid,
#[serde(skip_serializing_if = "String::is_empty")] #[serde(skip_serializing_if = "String::is_empty")]
pub salt: String, pub salt: String,