From 6bffd594c0e4ae2b9aa56402c27305dcb7240e81 Mon Sep 17 00:00:00 2001 From: Martin Yang Date: Tue, 24 Mar 2026 22:40:08 -0400 Subject: [PATCH] Database and api route ready --- .gitignore | 1 + conf/autoload_configs/httapi.conf.xml | 20 +- conf/dialplan/default.xml | 5 + did_router/.env | 2 + did_router/Cargo.lock | 175 ++++++++++++++++++ did_router/Cargo.toml | 6 + did_router/diesel.toml | 9 + did_router/migrations/.diesel_lock | 0 did_router/migrations/.keep | 0 .../down.sql | 6 + .../up.sql | 36 ++++ .../down.sql | 3 + .../2026-03-23-013910-0000_create_dids/up.sql | 9 + did_router/src/database.rs | 82 ++++++++ did_router/src/main.rs | 84 +++++++-- did_router/src/schema.rs | 22 +++ 16 files changed, 437 insertions(+), 23 deletions(-) create mode 100644 .gitignore create mode 100644 did_router/.env create mode 100644 did_router/diesel.toml create mode 100644 did_router/migrations/.diesel_lock create mode 100644 did_router/migrations/.keep create mode 100644 did_router/migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 did_router/migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 did_router/migrations/2026-03-23-013910-0000_create_dids/down.sql create mode 100644 did_router/migrations/2026-03-23-013910-0000_create_dids/up.sql create mode 100644 did_router/src/database.rs create mode 100644 did_router/src/schema.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/conf/autoload_configs/httapi.conf.xml b/conf/autoload_configs/httapi.conf.xml index c3566d1..c4102e0 100644 --- a/conf/autoload_configs/httapi.conf.xml +++ b/conf/autoload_configs/httapi.conf.xml @@ -29,20 +29,20 @@ - - @@ -58,15 +58,15 @@ - @@ -79,11 +79,11 @@ - + - - + + diff --git a/conf/dialplan/default.xml b/conf/dialplan/default.xml index 88045bc..5dbbed1 100644 --- a/conf/dialplan/default.xml +++ b/conf/dialplan/default.xml @@ -45,5 +45,10 @@ + + + + + diff --git a/did_router/.env b/did_router/.env new file mode 100644 index 0000000..b692b9c --- /dev/null +++ b/did_router/.env @@ -0,0 +1,2 @@ +DATABASE_URL=postgres://freeswitch:T5NyU2NwQb5DD9oV@localhost/did_router + diff --git a/did_router/Cargo.lock b/did_router/Cargo.lock index f7c3205..a559391 100644 --- a/did_router/Cargo.lock +++ b/did_router/Cargo.lock @@ -257,6 +257,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -338,6 +344,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "deranged" version = "0.5.8" @@ -375,6 +416,60 @@ name = "did_router" version = "0.1.0" dependencies = [ "actix-web", + "diesel", + "diesel-derive-enum", + "dotenvy", + "serde", + "serde_json", + "xml-builder", +] + +[[package]] +name = "diesel" +version = "2.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ae09a41a4b89f94ec1e053623da8340d996bc32c6517d325a9daad9b239358" +dependencies = [ + "bitflags", + "byteorder", + "diesel_derives", + "downcast-rs", + "itoa", + "pq-sys", +] + +[[package]] +name = "diesel-derive-enum" +version = "3.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a8c082045d01debc8589f8a0db9f2855a37c99c9b031325c856b5b98e1625f" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_derives" +version = "2.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47618bf0fac06bb670c036e48404c26a865e6a71af4114dfd97dfe89936e404e" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" +dependencies = [ + "syn", ] [[package]] @@ -398,6 +493,38 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "dsl_auto_type" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e" +dependencies = [ + "darling", + "either", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -537,6 +664,18 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "0.2.12" @@ -641,6 +780,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -855,6 +1000,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pq-sys" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574ddd6a267294433f140b02a726b0640c43cf7c6f717084684aaa3b285aba61" +dependencies = [ + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -986,6 +1142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -1104,6 +1261,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.117" @@ -1270,6 +1433,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1391,6 +1560,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "xml-builder" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d5951f276a574fce1cf86598f6143778be7ac5585c241f5e39dbdb0371d8b4" + [[package]] name = "yoke" version = "0.8.1" diff --git a/did_router/Cargo.toml b/did_router/Cargo.toml index a44a41e..959ad4c 100644 --- a/did_router/Cargo.toml +++ b/did_router/Cargo.toml @@ -5,3 +5,9 @@ edition = "2024" [dependencies] actix-web = "4" +xml-builder = "*" +diesel = { version = "2.3", features = ["postgres"] } +dotenvy = "0.15" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +diesel-derive-enum = { version = "3.0.0-beta.1", features = ["postgres"] } diff --git a/did_router/diesel.toml b/did_router/diesel.toml new file mode 100644 index 0000000..a0d61bf --- /dev/null +++ b/did_router/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "migrations" diff --git a/did_router/migrations/.diesel_lock b/did_router/migrations/.diesel_lock new file mode 100644 index 0000000..e69de29 diff --git a/did_router/migrations/.keep b/did_router/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/did_router/migrations/00000000000000_diesel_initial_setup/down.sql b/did_router/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/did_router/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/did_router/migrations/00000000000000_diesel_initial_setup/up.sql b/did_router/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/did_router/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/did_router/migrations/2026-03-23-013910-0000_create_dids/down.sql b/did_router/migrations/2026-03-23-013910-0000_create_dids/down.sql new file mode 100644 index 0000000..b910c19 --- /dev/null +++ b/did_router/migrations/2026-03-23-013910-0000_create_dids/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP TABLE dids; +DROP TYPE did_target_type; diff --git a/did_router/migrations/2026-03-23-013910-0000_create_dids/up.sql b/did_router/migrations/2026-03-23-013910-0000_create_dids/up.sql new file mode 100644 index 0000000..6956d71 --- /dev/null +++ b/did_router/migrations/2026-03-23-013910-0000_create_dids/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here +CREATE TYPE did_target_type AS ENUM ('URL', 'MOH', 'EXTERNAL_NUMBER', 'NO_SERVICE', 'NIGHT_MODE', 'CUSTOME_MESSAGE'); +CREATE TABLE dids ( + id SERIAL PRIMARY KEY, + did VARCHAR(64) NOT NULL, + target_type DID_TARGET_TYPE NOT NULL DEFAULT 'NO_SERVICE', + target VARCHAR(512), + active BOOLEAN NOT NULL DEFAULT TRUE +) diff --git a/did_router/src/database.rs b/did_router/src/database.rs new file mode 100644 index 0000000..61b5ca0 --- /dev/null +++ b/did_router/src/database.rs @@ -0,0 +1,82 @@ +use diesel::prelude::*; +use diesel::pg::PgConnection; +use diesel::result::Error; +use dotenvy::dotenv; +use std::env; +use serde::{Serialize}; +use diesel_derive_enum::DbEnum; + + +#[derive(Debug, PartialEq, DbEnum, Serialize)] +#[db_enum(existing_type_path = "crate::schema::sql_types::DidTargetType")] +pub enum DidTargetType { + #[db_enum(rename ="URL")] + Url, + #[db_enum(rename ="MOH")] + Moh, + #[db_enum(rename ="EXTERNAL_NUMBER")] + ExternalNumber, + #[db_enum(rename ="NO_SERVICE")] + NoService, + #[db_enum(rename ="NIGHT_MODE")] + NightMode, + #[db_enum(rename ="CUSTOME_MESSAGE")] + CustomMessage +} + +#[derive(Queryable, Selectable, Serialize)] +#[diesel(table_name = crate::schema::dids)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Did { + pub id: i32, + pub did: String, + pub target_type: DidTargetType, + pub target: Option, + pub active: bool +} + +#[derive(Insertable)] +#[diesel(table_name = crate::schema::dids)] +pub struct NewDid<'a> { + pub did: &'a str, + pub target_type: DidTargetType, + pub target: Option<&'a str>, + pub active: bool +} + +pub fn connect() -> PgConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + PgConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("Error connecting to database")) +} + +pub fn add_did() -> Result { + use crate::schema::dids; + + let mut conn = connect(); + let new_did = NewDid { + did: "123456", + target_type: DidTargetType::Url, + target: Some("123456"), + active: true + }; + + let did = diesel::insert_into(dids::table) + .values(&new_did) + .returning(Did::as_returning()) + .get_result(&mut conn)?; + + Ok(did) +} + +pub fn list_did() ->Result, Error> { + use crate::schema::dids::dsl::*; + let mut conn = connect(); + + let res = dids + .load(&mut conn)?; + + Ok(res) +} diff --git a/did_router/src/main.rs b/did_router/src/main.rs index 555283f..85d067f 100644 --- a/did_router/src/main.rs +++ b/did_router/src/main.rs @@ -1,28 +1,86 @@ -use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; +pub mod database; +pub mod schema; -#[get("/")] -async fn hello() -> impl Responder { - HttpResponse::Ok().body("Hello world!") +use xml_builder::{XMLBuilder, XMLElement, XMLVersion}; +use actix_web::{get, post, web, App, + HttpResponse, + HttpServer, + http::header::ContentType, + Responder, +}; + +use database::{ add_did, + list_did +}; + +async fn route_did() -> impl Responder { + let mut xml = XMLBuilder::new() + .version(XMLVersion::XML1_1) + .encoding("UTF-8".into()) + .build(); + + let mut doc = XMLElement::new("document"); + doc.add_attribute("type", "xml/freeswitch-httapi"); + + let params = XMLElement::new("params"); + let mut work = XMLElement::new("work"); + + let mut playback = XMLElement::new("playback"); + playback.add_attribute("name", "exten"); + playback.add_attribute("file", "ivr/ivr-welcome_to_freeswitch.wav"); + work.add_child(playback).unwrap(); + + doc.add_child(params).unwrap(); + doc.add_child(work).unwrap(); + + xml.set_root_element(doc); + + let mut writer: Vec = Vec::new(); + xml.generate(&mut writer).unwrap(); + + HttpResponse::Ok() + .content_type(ContentType::xml()) + .body(writer) } -#[post("/echo")] -async fn echo(req_body: String) -> impl Responder { - HttpResponse::Ok().body(req_body) +async fn did_post() -> impl Responder { + add_did().unwrap(); + HttpResponse::Ok().body("DID added.") } -async fn manual_hello() -> impl Responder { - HttpResponse::Ok().body("Hey there!") +async fn did_index() -> impl Responder { + let dids = list_did().unwrap(); + HttpResponse::Ok().json(dids) +} + +fn api_config(cfg: &mut web::ServiceConfig) { + cfg.service( + web::resource("/fs") + .route(web::get().to(did_index)) + .route(web::post().to(did_post)) + .route(web::patch().to(|| async { HttpResponse::Ok().body("did patch") })) + .route(web::delete().to(|| async { HttpResponse::Ok().body("did delete") })) + ); + cfg.service( + web::resource("/fs/{id}") + .route(web::get().to(|| async { HttpResponse::Ok().body("did get")})) + .route(web::patch().to(|| async { HttpResponse::Ok().body("did patch")})) + .route(web::delete().to(|| async { HttpResponse::Ok().body("did delete")})) + ); + cfg.service( + web::resource("/route_did") + .route(web::post().to(route_did)) + ); } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() - .service(hello) - .service(echo) - .route("/hey", web::get().to(manual_hello)) + .service( + web::scope("/api").configure(api_config)) }) - .bind(("127.0.0.1", 8080))? + .bind(("0.0.0.0", 3000))? .run() .await } diff --git a/did_router/src/schema.rs b/did_router/src/schema.rs new file mode 100644 index 0000000..3ad4696 --- /dev/null +++ b/did_router/src/schema.rs @@ -0,0 +1,22 @@ +// @generated automatically by Diesel CLI. + +pub mod sql_types { + #[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "did_target_type"))] + pub struct DidTargetType; +} + +diesel::table! { + use diesel::sql_types::*; + use super::sql_types::DidTargetType; + + dids (id) { + id -> Int4, + #[max_length = 64] + did -> Varchar, + target_type -> DidTargetType, + #[max_length = 512] + target -> Nullable, + active -> Bool, + } +}