diff options
author | Galen Guyer <galen@galenguyer.com> | 2022-05-22 01:37:57 -0400 |
---|---|---|
committer | Galen Guyer <galen@galenguyer.com> | 2022-05-22 01:37:57 -0400 |
commit | 2145e15274776bcdadd86d9f011251fbe7cc85ff (patch) | |
tree | 958fcb2bc2a801de13a92ba216b044b3a219e0ad | |
parent | 3613656a396b42d70259e9ff325ab9fcf0b55dd1 (diff) |
api kinda works to add records and stuff
-rw-r--r-- | migrations/2022-04-09-create-schema.sql | 3 | ||||
-rw-r--r-- | src/db/mod.rs | 1 | ||||
-rw-r--r-- | src/db/models.rs | 13 | ||||
-rw-r--r-- | src/db/records.rs | 33 | ||||
-rw-r--r-- | src/db/strings.rs | 7 | ||||
-rw-r--r-- | src/extractors.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 8 | ||||
-rw-r--r-- | src/routes/v1/mod.rs | 1 | ||||
-rw-r--r-- | src/routes/v1/records.rs | 81 | ||||
-rw-r--r-- | src/routes/v1/repsonse.rs | 2 | ||||
-rw-r--r-- | src/routes/v1/requests.rs | 9 | ||||
-rw-r--r-- | src/routes/v1/zones.rs | 40 |
12 files changed, 159 insertions, 41 deletions
diff --git a/migrations/2022-04-09-create-schema.sql b/migrations/2022-04-09-create-schema.sql index 786710d..89bfa3f 100644 --- a/migrations/2022-04-09-create-schema.sql +++ b/migrations/2022-04-09-create-schema.sql @@ -22,8 +22,9 @@ CREATE TABLE IF NOT EXISTS zones ( ); CREATE TABLE IF NOT EXISTS records ( - id SERIAL PRIMARY KEY, + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), zone_id varchar(255) NOT NULL, + name varchar(255) NOT NULL, type varchar(16) NOT NULL, content TEXT NOT NULL, ttl INTEGER NOT NULL, diff --git a/src/db/mod.rs b/src/db/mod.rs index fc37f5e..1cc94cd 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,3 +1,4 @@ +pub mod records; pub mod users; pub mod zones; diff --git a/src/db/models.rs b/src/db/models.rs index 6af7e15..d9591d8 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -29,11 +29,14 @@ pub struct Zone { #[derive(Serialize, Deserialize, FromRow, Debug)] pub struct Record { - pub id: u64, + pub id: Uuid, pub zone_id: String, - pub r#type: String, + pub name: String, + #[serde(rename = "type")] + #[sqlx(rename = "type")] + pub record_type: String, pub content: String, - pub ttl: u32, + pub ttl: i32, pub created_at: DateTime<Utc>, - pub modified_at: DateTime<Utc>, -}
\ No newline at end of file + pub modified_at: DateTime<Utc>, +} diff --git a/src/db/records.rs b/src/db/records.rs new file mode 100644 index 0000000..d92e489 --- /dev/null +++ b/src/db/records.rs @@ -0,0 +1,33 @@ +use crate::db::models::Record; +use crate::db::strings; +use sqlx::types::Uuid; +use sqlx::{Pool, Postgres}; + +pub async fn create_record( + pool: &Pool<Postgres>, + zone_id: &str, + name: &str, + record_type: &str, + content: &str, + ttl: u32, +) -> Result<Record, sqlx::Error> { + let mut transaction = pool.begin().await?; + let zone = sqlx::query_as::<_, Record>(&strings::CREATE_RECORD) + .bind(zone_id) + .bind(name) + .bind(record_type) + .bind(content) + .bind(ttl) + .fetch_one(&mut transaction) + .await?; + transaction.commit().await?; + Ok(zone) +} + +pub async fn get_records(pool: &Pool<Postgres>, zone_id: &str) -> Result<Vec<Record>, sqlx::Error> { + let records = sqlx::query_as::<_, Record>(&strings::GET_RECORDS) + .bind(zone_id) + .fetch_all(pool) + .await?; + Ok(records) +} diff --git a/src/db/strings.rs b/src/db/strings.rs index ebaa8c7..aa0249a 100644 --- a/src/db/strings.rs +++ b/src/db/strings.rs @@ -24,4 +24,11 @@ lazy_static! { SELECT id,owner_uuid,created_at,modified_at FROM zones WHERE id = $1 "; + pub(crate) static ref CREATE_RECORD: &'static str = r" + INSERT INTO records(zone_id,name,type,content,ttl) VALUES ($1, $2, $3, $4, $5) RETURNING * + "; + pub(crate) static ref GET_RECORDS: &'static str = r" + SELECT id,zone_id,name,type,content,ttl,created_at,modified_at + FROM records WHERE zone_id = $1 + "; } diff --git a/src/extractors.rs b/src/extractors.rs index 30e8235..dd294b6 100644 --- a/src/extractors.rs +++ b/src/extractors.rs @@ -70,7 +70,7 @@ where let token = Token { iss: claims.get("iss").unwrap().to_string(), - sub: Uuid::parse_str(&claims.get("sub").unwrap().to_string()).unwrap(), + sub: Uuid::parse_str(claims.get("sub").unwrap()).unwrap(), iat: claims.get("iat").unwrap().parse().unwrap(), exp: claims.get("exp").unwrap().parse().unwrap(), dn: claims.get("dn").unwrap().to_string(), diff --git a/src/main.rs b/src/main.rs index d8d88a6..01a70b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,8 +60,12 @@ async fn main() { "/zones", Router::new() .route("/", get(routes::v1::zones::list_zones)) - .route("/:id", post(routes::v1::zones::create_zone)) - .route("/:id", get(routes::v1::zones::get_zone)), + .route( + "/:zone_id", + get(routes::v1::records::get_records) + .post(routes::v1::zones::create_zone) + .put(routes::v1::records::create_record), + ), ), ), ) diff --git a/src/routes/v1/mod.rs b/src/routes/v1/mod.rs index 9e3453c..f561d02 100644 --- a/src/routes/v1/mod.rs +++ b/src/routes/v1/mod.rs @@ -1,3 +1,4 @@ +pub mod records; pub mod users; pub mod zones; diff --git a/src/routes/v1/records.rs b/src/routes/v1/records.rs new file mode 100644 index 0000000..6c71749 --- /dev/null +++ b/src/routes/v1/records.rs @@ -0,0 +1,81 @@ +use crate::db; +use crate::extractors::{Json, Jwt}; +use crate::routes::v1::{requests, zones}; +use axum::extract::Path; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::Extension; +use serde_json::json; +use sqlx::{Pool, Postgres}; +use std::sync::Arc; + +pub async fn get_records( + Path(id): Path<String>, + Jwt(user): Jwt, + Extension(pool): Extension<Arc<Pool<Postgres>>>, +) -> impl IntoResponse { + let domain = zones::ensure_trailing_dot(&id); + + let zone = db::zones::get_zone(&pool, &domain).await; + if zone.is_err() { + return ( + StatusCode::NOT_FOUND, + Json(json!({ "error": format!("Zone {domain} not found") })), + ); + } + let zone = zone.unwrap(); + + if zone.owner_uuid != user.sub { + return ( + StatusCode::FORBIDDEN, + Json(json!({ "error": "You do have permissions to access this zone" })), + ); + } + + let records = db::records::get_records(&pool, &zone.id).await.unwrap(); + + (StatusCode::OK, Json(json!(records))) +} + +pub async fn create_record( + Path(zone_id): Path<String>, + Jwt(user): Jwt, + Json(data): Json<requests::Record>, + Extension(pool): Extension<Arc<Pool<Postgres>>>, +) -> impl IntoResponse { + let zone = db::zones::get_zone(&pool, &zone_id).await; + + if zone.is_err() { + return ( + StatusCode::NOT_FOUND, + Json(json!({"error": "Zone not found"})), + ); + } + let zone = zone.unwrap(); + + if zone.owner_uuid != user.sub { + return ( + StatusCode::FORBIDDEN, + Json(json!({"error": "You do not have permissions to access this zone"})), + ); + } + + let record = db::records::create_record( + &pool, + &zone.id, + &data.name, + &data.record_type, + &data.content, + data.ttl, + ) + .await; + if record.is_err() { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": record.unwrap_err().to_string()})), + ); + } + let record = record.unwrap(); + + (StatusCode::OK, Json(json!(record))) +} diff --git a/src/routes/v1/repsonse.rs b/src/routes/v1/repsonse.rs index 73bd35d..cb35a41 100644 --- a/src/routes/v1/repsonse.rs +++ b/src/routes/v1/repsonse.rs @@ -3,5 +3,5 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct ErrorMessage { pub error: String, - pub reason: String, + pub reason: Option<String>, } diff --git a/src/routes/v1/requests.rs b/src/routes/v1/requests.rs index 4677928..aba7c00 100644 --- a/src/routes/v1/requests.rs +++ b/src/routes/v1/requests.rs @@ -13,3 +13,12 @@ pub struct Login { pub password: String, pub totp_code: Option<String>, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct Record { + pub name: String, + #[serde(rename = "type")] + pub record_type: String, + pub content: String, + pub ttl: u32, +} diff --git a/src/routes/v1/zones.rs b/src/routes/v1/zones.rs index 9867162..2bff4d8 100644 --- a/src/routes/v1/zones.rs +++ b/src/routes/v1/zones.rs @@ -11,8 +11,12 @@ use sqlx::{Error, Pool, Postgres}; use std::sync::Arc; lazy_static! { - static ref NAMESERVERS: Vec<String> = - Vec::from([String::from("ns1.fdns.dev."), String::from("ns2.fdns.dev."), String::from("ns3.fdns.dev."), String::from("ns4.fdns.dev.")]); + static ref NAMESERVERS: Vec<String> = Vec::from([ + String::from("ns1.fdns.dev."), + String::from("ns2.fdns.dev."), + String::from("ns3.fdns.dev."), + String::from("ns4.fdns.dev.") + ]); } pub async fn list_zones( @@ -30,11 +34,11 @@ pub async fn list_zones( } pub async fn create_zone( - Path(id): Path<String>, + Path(zone_id): Path<String>, Jwt(user): Jwt, Extension(pool): Extension<Arc<Pool<Postgres>>>, ) -> impl IntoResponse { - let domain = ensure_trailing_dot(&id); + let domain = ensure_trailing_dot(&zone_id); let domain = addr::parse_domain_name(&domain).unwrap(); if !domain.has_known_suffix() { return ( @@ -72,33 +76,7 @@ pub async fn create_zone( } } -pub async fn get_zone( - Path(id): Path<String>, - Jwt(user): Jwt, - Extension(pool): Extension<Arc<Pool<Postgres>>>, -) -> impl IntoResponse { - let domain = ensure_trailing_dot(&id); - - let zone = db::zones::get_zone(&pool, &domain).await; - if zone.is_err() { - return ( - StatusCode::NOT_FOUND, - Json(json!({ "error": format!("Zone {domain} not found") })), - ); - } - let zone = zone.unwrap(); - - if zone.owner_uuid != user.sub { - return ( - StatusCode::FORBIDDEN, - Json(json!({ "error": "You do have permissions to view this zone" })), - ); - } - - (StatusCode::OK, Json(json!(zone))) -} - -fn ensure_trailing_dot(domain: &str) -> String { +pub(crate) fn ensure_trailing_dot(domain: &str) -> String { if domain.ends_with('.') { return domain.to_string(); } |