Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: store input and output types in share server #47

Merged
merged 3 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

7 changes: 5 additions & 2 deletions crates/server/migrations/20241006174524_init.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
-- Add migration script here
CREATE TYPE data_type AS ENUM('json', 'yaml');

CREATE TABLE share (
id UUID PRIMARY KEY,
json text NOT NULL,
input_data text NOT NULL,
input_type data_type NOT NULL,
output_type data_type NOT NULL,
query text NOT NULL,
expires_at TIMESTAMPTZ NOT NULL
);
35 changes: 32 additions & 3 deletions crates/server/src/dtos/share_dto.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::model::share::Share;
use crate::model::share::{DataType, Share};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShareDTO {
pub id: Uuid,
pub json: String,
pub input_data: String,
pub input_type: DataTypeDTO,
pub output_type: DataTypeDTO,
pub query: String,
}

impl From<Share> for ShareDTO {
fn from(share: Share) -> Self {
Self {
id: share.id,
json: share.json,
input_data: share.input_data,
input_type: share.input_type.into(),
output_type: share.output_type.into(),
query: share.query,
}
}
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DataTypeDTO {
Json,
Yaml,
}

impl From<DataType> for DataTypeDTO {
fn from(data_type: DataType) -> Self {
match data_type {
DataType::Json => Self::Json,
DataType::Yaml => Self::Yaml,
}
}
}

impl From<DataTypeDTO> for DataType {
fn from(data_type: DataTypeDTO) -> Self {
match data_type {
DataTypeDTO::Json => Self::Json,
DataTypeDTO::Yaml => Self::Yaml,
}
}
}
10 changes: 5 additions & 5 deletions crates/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async fn main() {
let database_connections = env::var("DATABASE_CONNECTIONS")
.map(|s| {
s.parse()
.expect(&format!("DATABASE_CONNECTIONS must be a number. Got {s}"))
.unwrap_or_else(|_| panic!("DATABASE_CONNECTIONS must be a number. Got {s}"))
})
.unwrap_or(5);
let db_connection = PgPoolOptions::new()
Expand All @@ -34,9 +34,9 @@ async fn main() {

let max_share_expiration_time_secs = env::var("MAX_SHARE_EXPIRATION_TIME_SECS")
.map(|s| {
s.parse().expect(&format!(
"MAX_SHARE_EXPIRATION_TIME_SECS must be a number. Got {s}"
))
s.parse().unwrap_or_else(|_| {
panic!("MAX_SHARE_EXPIRATION_TIME_SECS must be a number. Got {s}")
})
})
.unwrap_or(24 * 7)
* 60
Expand All @@ -55,7 +55,7 @@ async fn main() {
let app = gq_server::app(db_connection, max_share_expiration_time_secs);
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect(&format!("Failed to bind address {addr}"));
.unwrap_or_else(|_| panic!("Failed to bind address {addr}"));

tracing::info!("Server started. Listening on {addr}");
axum::serve(listener, app)
Expand Down
11 changes: 10 additions & 1 deletion crates/server/src/model/share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct Share {
pub id: Uuid,
pub json: String,
pub input_data: String,
pub input_type: DataType,
pub output_type: DataType,
pub query: String,
pub expires_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "data_type", rename_all = "lowercase")]
pub enum DataType {
Json,
Yaml,
}
16 changes: 13 additions & 3 deletions crates/server/src/routes/shares.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::dtos::share_dto::ShareDTO;
use crate::dtos::share_dto::{DataTypeDTO, ShareDTO};
use crate::routes;
use crate::services::share::GetShareError;
use crate::{
Expand All @@ -21,7 +21,9 @@ pub const SHARES_CONTEXT: &str = "/shares";
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateShareRequest {
json: String,
input_data: String,
input_type: DataTypeDTO,
output_type: DataTypeDTO,
query: String,
expiration_time_secs: i64,
}
Expand All @@ -47,8 +49,16 @@ async fn create_share(
State(share_service): State<ShareService>,
Json(request): Json<CreateShareRequest>,
) -> impl IntoResponse {
// TODO: refactor CreateShareRequest into a model CreateShareRequest and a CreateShareRequestDTO
// so the create_share method does not contain that much attributes
let create_result = share_service
.create_share(request.json, request.query, request.expiration_time_secs)
.create_share(
request.input_data,
request.input_type.into(),
request.output_type.into(),
request.query,
request.expiration_time_secs,
)
.await;

let share_id = match create_result {
Expand Down
31 changes: 24 additions & 7 deletions crates/server/src/services/share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use chrono::{Duration, Utc};
use sqlx::PgPool;
use uuid::Uuid;

use crate::model::share::Share;
use crate::model::share::{DataType, Share};

#[derive(Debug, thiserror::Error)]
pub enum GetShareError {
Expand Down Expand Up @@ -38,10 +38,14 @@ impl ShareService {

pub async fn create_share(
&self,
json: String,
input_data: String,
input_type: DataType,
output_type: DataType,
query: String,
expiration_time_secs: i64,
) -> Result<Uuid, CreateShareError> {
// TODO: accept a CreateShare as input and validate it with a validate_create_response internal
// function to split the validation logic
if expiration_time_secs <= 0 || expiration_time_secs > self.max_expiration_time_secs {
return Err(CreateShareError::InvalidExpirationTime {
actual: expiration_time_secs,
Expand All @@ -54,9 +58,13 @@ impl ShareService {
let expires_at = now + Duration::seconds(expiration_time_secs);

sqlx::query!(
"INSERT INTO share (id, json, query, expires_at) VALUES ($1, $2, $3, $4)",
"INSERT INTO share
(id, input_data, input_type, output_type, query, expires_at)
VALUES ($1, $2, $3, $4, $5, $6)",
uuid,
json,
input_data,
input_type as DataType,
output_type as DataType,
query,
expires_at
)
Expand All @@ -67,9 +75,18 @@ impl ShareService {
}

pub async fn get_share(&self, id: Uuid) -> Result<Share, GetShareError> {
let share = sqlx::query_as!(Share, "SELECT * FROM share WHERE id = $1", id)
.fetch_optional(&self.db_connection)
.await?;
// See https://docs.rs/sqlx/0.4.2/sqlx/macro.query.html#force-a-differentcustom-type
// and https://github.com/launchbadge/sqlx/issues/1004#issuecomment-764921020 for
// more information about the custom type syntax
let share = sqlx::query_as!(
Share,
r#"SELECT id, input_data, input_type as "input_type: DataType",
output_type as "output_type: DataType", query, expires_at
FROM share WHERE id = $1"#,
id
)
.fetch_optional(&self.db_connection)
.await?;

let now = Utc::now();
let share = share.filter(|share| share.expires_at >= now);
Expand Down