Database and api route ready

This commit is contained in:
2026-03-24 22:40:08 -04:00
parent 7d8a182afd
commit 6bffd594c0
16 changed files with 437 additions and 23 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target

View File

@@ -82,7 +82,7 @@
<params> <params>
<!-- default url can be overridden by app data --> <!-- default url can be overridden by app data -->
<param name="gateway-url" value="http://www.freeswitch.org/api/index.cgi" /> <param name="gateway-url" value="http://127.0.0.1:3000/api/route_did" />
<!-- set this to provide authentication credentials to the server --> <!-- set this to provide authentication credentials to the server -->
<!--<param name="gateway-credentials" value="muser:mypass"/>--> <!--<param name="gateway-credentials" value="muser:mypass"/>-->

View File

@@ -45,5 +45,10 @@
</condition> </condition>
</extension> </extension>
<extension name="did-route">
<condition field="destination_number" expression="^8598989002$">
<action application="httapi" data="{method=POST}" />
</condition>
</extension>
</context> </context>
</include> </include>

2
did_router/.env Normal file
View File

@@ -0,0 +1,2 @@
DATABASE_URL=postgres://freeswitch:T5NyU2NwQb5DD9oV@localhost/did_router

175
did_router/Cargo.lock generated
View File

@@ -257,6 +257,12 @@ dependencies = [
"alloc-stdlib", "alloc-stdlib",
] ]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.11.1"
@@ -338,6 +344,41 @@ dependencies = [
"typenum", "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]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.8" version = "0.5.8"
@@ -375,6 +416,60 @@ name = "did_router"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-web", "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]] [[package]]
@@ -398,6 +493,38 @@ dependencies = [
"syn", "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]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@@ -537,6 +664,18 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 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]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "0.2.12"
@@ -641,6 +780,12 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.1.0" version = "1.1.0"
@@ -855,6 +1000,17 @@ dependencies = [
"zerocopy", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -986,6 +1142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [ dependencies = [
"serde_core", "serde_core",
"serde_derive",
] ]
[[package]] [[package]]
@@ -1104,6 +1261,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.117"
@@ -1270,6 +1433,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@@ -1391,6 +1560,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "xml-builder"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d5951f276a574fce1cf86598f6143778be7ac5585c241f5e39dbdb0371d8b4"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.1" version = "0.8.1"

View File

@@ -5,3 +5,9 @@ edition = "2024"
[dependencies] [dependencies]
actix-web = "4" 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"] }

9
did_router/diesel.toml Normal file
View File

@@ -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"

View File

View File

View File

@@ -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();

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP TABLE dids;
DROP TYPE did_target_type;

View File

@@ -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
)

View File

@@ -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<String>,
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<Did, Error> {
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<Vec<Did>, Error> {
use crate::schema::dids::dsl::*;
let mut conn = connect();
let res = dids
.load(&mut conn)?;
Ok(res)
}

View File

@@ -1,28 +1,86 @@
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; pub mod database;
pub mod schema;
#[get("/")] use xml_builder::{XMLBuilder, XMLElement, XMLVersion};
async fn hello() -> impl Responder { use actix_web::{get, post, web, App,
HttpResponse::Ok().body("Hello world!") 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<u8> = Vec::new();
xml.generate(&mut writer).unwrap();
HttpResponse::Ok()
.content_type(ContentType::xml())
.body(writer)
} }
#[post("/echo")] async fn did_post() -> impl Responder {
async fn echo(req_body: String) -> impl Responder { add_did().unwrap();
HttpResponse::Ok().body(req_body) HttpResponse::Ok().body("DID added.")
} }
async fn manual_hello() -> impl Responder { async fn did_index() -> impl Responder {
HttpResponse::Ok().body("Hey there!") 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] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.service(hello) .service(
.service(echo) web::scope("/api").configure(api_config))
.route("/hey", web::get().to(manual_hello))
}) })
.bind(("127.0.0.1", 8080))? .bind(("0.0.0.0", 3000))?
.run() .run()
.await .await
} }

22
did_router/src/schema.rs Normal file
View File

@@ -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<Varchar>,
active -> Bool,
}
}