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/responses #22

Merged
merged 12 commits into from
Dec 30, 2023
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
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
argon2 = "0.5.2"
async-trait = "0.1.74"
axum = "0.6.20"
chrono = { version = "0.4.31", features = ["serde"] }
Expand All @@ -27,7 +26,6 @@ serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
thiserror = "1.0.50"
tokio = { version = "1.33.0", features = ["full"] }
totp-rs = { version = "5.4.0", features = ["qr", "gen_secret"] }
tower = { version = "0.4.13", features = ["limit", "buffer"] }
tower-http = { version = "0.4.4", features = ["trace"] }
tracing = "0.1.39"
Expand All @@ -37,6 +35,12 @@ utoipa = { version = "4.1.0", features = ["yaml", "chrono"] }
validator = { version = "0.16", features = ["derive"] }
version-compare = "0.1.1"

[dev-dependencies]
rand = "0.8.5"
reqwest = { version = "0.11.23", features = ["json"] }
test-log = "0.2.14"
openidconnect = "3.4.0"

[features]
default = ["all-databases"]

Expand All @@ -45,3 +49,5 @@ all-databases = ["postgres", "mysql"]
postgres = ["rbdc-pg"]
mysql = ["rbdc-mysql"]

test = []

27 changes: 23 additions & 4 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,26 @@ script = "docker compose -f testing/oidc-mock/docker-compose.yaml up -d"
script = "docker run --name postgres -e POSTGRES_PASSWORD=password -e POSTGRES_USERNAME=postgres -p 5150:5432 -d postgres && sleep 1"

[tasks.postgres_tests]
env = { postgres_username = "postgres", postgres_password = "password", postgres_endpoint = "localhost:5150", postgres_database = "postgres", "oidc_discovery_url" = "http://localhost:5151/" }
env = { DATABASE = "POSTGRES", POSTGRES_USERNAME = "postgres", POSTGRES_PASSWORD = "password", POSTGRES_ENDPOINT = "localhost:5150", POSTGRES_DATABASE = "postgres", "OIDC_DISCOVERY_URL" = "http://localhost:5151", OIDC_CLIENT_ID = "client", OIDC_CLIENT_SECRET = "secret", RUST_LOG = "debug" }
command = "cargo"
args = ["test", "--features", "postgres"]
args = [
"test",
"--no-default-features",
"--features",
"postgres,test",
"--test",
"http_tests",
"--",
"--nocapture",
"--test-threads=1"
]

[tasks.postgres]
run_task = { name = ["oidc-server-mock", "postgres_database", "postgres_tests"], fork = true, cleanup_task = "postgres_cleanup"}
run_task = { name = [
"oidc-server-mock",
"postgres_database",
"postgres_tests",
], fork = true, cleanup_task = "postgres_cleanup" }

[tasks.postgres_cleanup]
script = "docker kill postgres;docker rm postgres;docker kill oidc-server-mock;docker rm oidc-server-mock"
Expand All @@ -53,7 +67,12 @@ run_task = { name = ["postgres"] }
[tasks.docs_lint]
dependencies = ["docs_generate"]
command = "npx"
args = ["redocly", "lint", "--skip-rule=no-empty-servers", "target/openapi.yaml"]
args = [
"redocly",
"lint",
"--skip-rule=no-empty-servers",
"target/openapi.yaml",
]

[tasks.docs_build]
dependencies = ["docs_lint"]
Expand Down
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// just placeholder to get OUT_DIR

fn main() {}

8 changes: 5 additions & 3 deletions src/database/drop.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
DROP TABLE IF EXISTS field;
DROP TABLE IF EXISTS prompt;
DROP TABLE IF EXISTS target;
DROP TABLE IF EXISTS feedback_prompt_field_response;
DROP TABLE IF EXISTS feedback_prompt_response;
DROP TABLE IF EXISTS feedback_prompt_field;
DROP TABLE IF EXISTS feedback_prompt;
DROP TABLE IF EXISTS feedback_target;

40 changes: 35 additions & 5 deletions src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ macro_rules! database_configuration {
#[derive(Debug, Clone)]
pub enum DatabaseConfiguration {
$(
#[cfg(feature = "" $ident:lower)]
#[cfg(feature = $scheme)]
$ident($config),
)*
}
Expand All @@ -61,8 +61,8 @@ macro_rules! database_configuration {
#[inline(always)]
pub fn extract() -> crate::error::Result<Self> {
$(
#[cfg(feature = "" $ident:lower)]
if let Ok(config) = envy::prefixed(stringify!([<$ident:lower _>]).trim()).from_env::<$config>() {
#[cfg(feature = $scheme)]
if let Ok(config) = envy::prefixed(stringify!([<$ident:upper _>])).from_env::<$config>() {
return Ok(Self::$ident(config));
}
)*
Expand All @@ -77,12 +77,12 @@ macro_rules! database_configuration {

match self {
$(
#[cfg(feature = "" $ident:lower)]
#[cfg(feature = $scheme)]
Self::$ident(config) => {
let url = config.to_url($scheme);
connection.init($driver {}, url.as_str())?;

#[cfg(test)]
#[cfg(feature = "test")]
connection.exec(include_str!("drop.sql"), vec![]).await?;

// perform migrations
Expand Down Expand Up @@ -145,3 +145,33 @@ macro_rules! database_request {
}};
}

/// rbatis doesnt convert the LIMIT statements for postgres and mssql therefore we need a wrapper
/// REF: https://rbatis.github.io/rbatis.io/#/v4/?id=macros-select-page
#[macro_export]
macro_rules! impl_select_page_wrapper {
($table:path {}) => {
impl_select_page_wrapper!($table{select_page() => ""});
};
($table:path {$ident:ident ($($arg:ident: $ty:ty $(,)?)*) => $expr:expr}) => {
paste!{
impl_select_page!($table {$ident($($arg: $ty,)* limit_sql: &str) => $expr});

impl $table {
pub async fn [<$ident _wrapper>](executor: &dyn rbatis::executor::Executor, page_request: &dyn rbatis::IPageRequest, $($arg: $ty,)*) -> std::result::Result<rbatis::plugin::page::Page<$table>, rbatis::rbdc::Error> {

use std::ops::Deref;
let limit = page_request.page_size();
let offset = page_request.offset();

match $crate::DATABASE_CONFIG.deref() {
#[cfg(feature = "postgres")]
$crate::database::DatabaseConfiguration::Postgres(_) => Self::$ident(executor, page_request, $($arg,)* format!(" LIMIT {} OFFSET {} ", limit, offset).as_str()).await,
#[allow(unreachable_patterns)]
_ => Self::$ident(executor, page_request, $($arg,)* format!(" LIMIT {},{} ", limit, offset).as_str()).await
}
}
}
}

}
}
26 changes: 20 additions & 6 deletions src/database/postgres.sql
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
CREATE TABLE IF NOT EXISTS target (
CREATE TABLE IF NOT EXISTS feedback_target (
id VARCHAR(32) UNIQUE NOT NULL,
name VARCHAR(32) NOT NULL,
description VARCHAR(255),
updated_at TIMESTAMP,
created_at TIMESTAMP
);

CREATE TABLE IF NOT EXISTS prompt (
CREATE TABLE IF NOT EXISTS feedback_prompt (
id VARCHAR(32) UNIQUE NOT NULL,
title VARCHAR(32) NOT NULL,
target VARCHAR(32) REFERENCES target(id) NOT NULL,
target VARCHAR(32) REFERENCES feedback_target(id) NOT NULL,
active BOOLEAN NOT NULL,
updated_at TIMESTAMP,
created_at TIMESTAMP
);

CREATE TABLE IF NOT EXISTS field (
CREATE TABLE IF NOT EXISTS feedback_prompt_field (
id VARCHAR(32) UNIQUE NOT NULL,
title VARCHAR(255) NOT NULL,
prompt VARCHAR(32) REFERENCES prompt(id) NOT NULL,
prompt VARCHAR(32) REFERENCES feedback_prompt(id) NOT NULL,
type VARCHAR(32) NOT NULL,
options BPCHAR NOT NULL,
options JSON NOT NULL,
updated_at TIMESTAMP,
created_at TIMESTAMP
);

CREATE TABLE IF NOT EXISTS feedback_prompt_response (
id VARCHAR(32) UNIQUE NOT NULL,
prompt VARCHAR(32) REFERENCES feedback_prompt(id) NOT NULL,
created_at TIMESTAMP
);

CREATE TABLE IF NOT EXISTS feedback_prompt_field_response (
id VARCHAR(32) UNIQUE NOT NULL,
response VARCHAR(32) REFERENCES feedback_prompt_response(id) NOT NULL,
field VARCHAR(32) REFERENCES feedback_prompt_field(id) NOT NULL,
data JSON NOT NULL
);


86 changes: 82 additions & 4 deletions src/database/schema/feedback/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,27 @@
//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.

use crate::prelude::*;
use rbatis::rbdc::{DateTime, JsonV};

use super::FeedbackPromptInputType;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
#[serde(tag = "type")]
#[serde(untagged)]
#[serde(rename_all = "lowercase")]
pub enum FeedbackPromptInputOptions {
Text(TextOptions),
Rating(RatingOptions)
Rating(RatingOptions),
}

// TODO: gen with macro
impl PartialEq<FeedbackPromptInputOptions> for FeedbackPromptInputType {
fn eq(&self, other: &FeedbackPromptInputOptions) -> bool {
match self {
Self::Text => matches!(other, FeedbackPromptInputOptions::Text(_)),
Self::Rating => matches!(other, FeedbackPromptInputOptions::Rating(_)),
}
}
}

#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, TypedBuilder, ToSchema, Validate)]
Expand All @@ -33,14 +49,76 @@ pub struct TextOptions {
#[validate(length(max = 255))]
description: String,
#[validate(length(max = 255))]
placeholder: String
placeholder: String,
}

#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, TypedBuilder, ToSchema, Validate)]
#[builder(field_defaults(setter(into)))]
pub struct RatingOptions {
#[validate(length(max = 255))]
description: String,
max: u8
max: u8,
}

#[derive(
Deserialize, Serialize, Clone, Derivative, Debug, Getters, MutGetters, TypedBuilder, ToSchema,
)]
#[derivative(PartialEq)]
#[get = "pub"]
#[get_mut = "pub"]
#[builder(field_defaults(setter(into)))]
pub struct FeedbackPromptResponse {
#[builder(default_code = r#"nanoid::nanoid!()"#)]
id: String,
prompt: String,
#[derivative(PartialEq = "ignore")]
#[builder(default)]
created_at: DateTime,
}

crud!(FeedbackPromptResponse {});
impl_select_page_wrapper!(FeedbackPromptResponse {select_page_by_prompt(prompt: &str) => "WHERE prompt = #{prompt}"});

#[derive(
Deserialize, Serialize, Clone, PartialEq, Debug, Getters, MutGetters, TypedBuilder, ToSchema,
)]
#[get = "pub"]
#[get_mut = "pub"]
#[builder(field_defaults(setter(into)))]
pub struct FeedbackPromptFieldResponse {
#[builder(default_code = r#"nanoid::nanoid!()"#)]
id: String,
response: String,
field: String,
#[schema(value_type = FeedbackPromptFieldData)]
data: JsonV<FeedbackPromptFieldData>,
}

crud!(FeedbackPromptFieldResponse {});

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
#[serde(untagged)]
pub enum FeedbackPromptFieldData {
Text(TextResponse),
Rating(RatingResponse),
}

// TODO: use macro
impl PartialEq<FeedbackPromptFieldData> for FeedbackPromptInputType {
fn eq(&self, other: &FeedbackPromptFieldData) -> bool {
match self {
Self::Text => matches!(other, FeedbackPromptFieldData::Text(_)),
Self::Rating => matches!(other, FeedbackPromptFieldData::Rating(_)),
}
}
}

#[derive(Deserialize, Serialize, Clone, Debug, ToSchema, PartialEq)]
pub struct TextResponse {
data: String,
}

#[derive(Deserialize, Serialize, Clone, Debug, ToSchema, PartialEq)]
pub struct RatingResponse {
data: u8,
}
21 changes: 13 additions & 8 deletions src/database/schema/feedback/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
//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.

use rbatis::rbdc::DateTime;
use crate::prelude::*;
use rbatis::rbdc::{DateTime, JsonV};

use super::input::FeedbackPromptInputOptions;

Expand All @@ -31,14 +32,14 @@ use super::input::FeedbackPromptInputOptions;
Derivative,
Debug,
Getters,
MutGetters,
Setters,
TypedBuilder,
ToSchema,
Validate,
)]
#[derivative(PartialEq)]
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
#[builder(field_defaults(setter(into)))]
pub struct FeedbackPrompt {
#[builder(default_code = r#"nanoid::nanoid!()"#)]
Expand All @@ -57,9 +58,11 @@ pub struct FeedbackPrompt {
}

crud!(FeedbackPrompt {});
impl_select_page!(FeedbackPrompt {select_page_by_target(target: &str) => "`WHERE target = #{target}`"});
impl_select!(FeedbackPrompt {select_by_id(id: &str) -> Option => "`WHERE id = #{id} LIMIT 1`"});
impl_select_page_wrapper!(FeedbackPrompt {select_page_by_target(target: &str) => "`WHERE target = #{target}`"});

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum FeedbackPromptInputType {
Text,
Rating,
Expand All @@ -72,14 +75,14 @@ pub enum FeedbackPromptInputType {
Derivative,
Debug,
Getters,
MutGetters,
Setters,
TypedBuilder,
ToSchema,
Validate,
)]
#[derivative(PartialEq)]
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
#[builder(field_defaults(setter(into)))]
pub struct FeedbackPromptField {
#[builder(default_code = r#"nanoid::nanoid!()"#)]
Expand All @@ -88,7 +91,8 @@ pub struct FeedbackPromptField {
title: String,
prompt: String,
r#type: FeedbackPromptInputType,
options: FeedbackPromptInputOptions,
#[schema(value_type = FeedbackPromptInputOptions)]
options: JsonV<FeedbackPromptInputOptions>,
#[builder(default)]
#[derivative(PartialEq = "ignore")]
updated_at: DateTime,
Expand All @@ -98,4 +102,5 @@ pub struct FeedbackPromptField {
}

crud!(FeedbackPromptField {});
impl_select_page!(FeedbackPromptField {select_page_by_prompt(prompt: &str) => "`WHERE prompt = #{prompt}`"});
impl_select!(FeedbackPromptField {select_by_id(id: &str) -> Option => "`WHERE id = #{id} LIMIT 1`"});
impl_select_page_wrapper!(FeedbackPromptField {select_page_by_prompt(prompt: &str) => "`WHERE prompt = #{prompt}`"});
Loading
Loading