refactor with updated naming convention
This commit is contained in:
parent
34e0302242
commit
c9b755521e
40 changed files with 146 additions and 145 deletions
|
|
@ -5,13 +5,13 @@ use uuid::Uuid;
|
|||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum Encodable {
|
||||
pub enum Datum {
|
||||
Text(Option<String>),
|
||||
Timestamp(Option<DateTime<Utc>>),
|
||||
Uuid(Option<Uuid>),
|
||||
}
|
||||
|
||||
impl Encodable {
|
||||
impl Datum {
|
||||
// TODO: Can something similar be achieved with a generic return type?
|
||||
/// Bind this as a parameter to a sqlx query.
|
||||
pub fn bind_onto<'a>(
|
||||
|
|
@ -3,7 +3,7 @@ use std::fmt::Display;
|
|||
use interim_pgtypes::escape_identifier;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::encodable::Encodable;
|
||||
use crate::datum::Datum;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct QueryFragment {
|
||||
|
|
@ -12,7 +12,7 @@ pub struct QueryFragment {
|
|||
/// the lines of `["select * from foo where id = ", " and status = ", ""]`.
|
||||
/// `plain_sql` should always have exactly one more element than `params`.
|
||||
plain_sql: Vec<String>,
|
||||
params: Vec<Encodable>,
|
||||
params: Vec<Datum>,
|
||||
}
|
||||
|
||||
impl QueryFragment {
|
||||
|
|
@ -34,7 +34,7 @@ impl QueryFragment {
|
|||
.join("")
|
||||
}
|
||||
|
||||
pub fn to_params(&self) -> Vec<Encodable> {
|
||||
pub fn to_params(&self) -> Vec<Datum> {
|
||||
self.params.clone()
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ impl QueryFragment {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_param(param: Encodable) -> Self {
|
||||
pub fn from_param(param: Datum) -> Self {
|
||||
Self {
|
||||
plain_sql: vec!["".to_owned(), "".to_owned()],
|
||||
params: vec![param],
|
||||
|
|
@ -99,7 +99,7 @@ impl QueryFragment {
|
|||
pub enum PgExpressionAny {
|
||||
Comparison(PgComparisonExpression),
|
||||
Identifier(PgIdentifierExpression),
|
||||
Literal(Encodable),
|
||||
Literal(Datum),
|
||||
ToJson(PgToJsonExpression),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use thiserror::Error;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::client::AppDbClient;
|
||||
use crate::encodable::Encodable;
|
||||
use crate::datum::Datum;
|
||||
use crate::presentation::Presentation;
|
||||
|
||||
/// A materialization of a database column, fit for consumption by an end user.
|
||||
|
|
@ -51,7 +51,7 @@ impl Field {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_value_encodable(&self, row: &PgRow) -> Result<Encodable, ParseError> {
|
||||
pub fn get_datum(&self, row: &PgRow) -> Result<Datum, ParseError> {
|
||||
let value_ref = row
|
||||
.try_get_raw(self.name.as_str())
|
||||
.or(Err(ParseError::FieldNotFound))?;
|
||||
|
|
@ -60,12 +60,10 @@ impl Field {
|
|||
dbg!(&ty);
|
||||
Ok(match ty {
|
||||
"TEXT" | "VARCHAR" => {
|
||||
Encodable::Text(<Option<String> as Decode<Postgres>>::decode(value_ref).unwrap())
|
||||
Datum::Text(<Option<String> as Decode<Postgres>>::decode(value_ref).unwrap())
|
||||
}
|
||||
"UUID" => {
|
||||
Encodable::Uuid(<Option<Uuid> as Decode<Postgres>>::decode(value_ref).unwrap())
|
||||
}
|
||||
"TIMESTAMPTZ" => Encodable::Timestamp(
|
||||
"UUID" => Datum::Uuid(<Option<Uuid> as Decode<Postgres>>::decode(value_ref).unwrap()),
|
||||
"TIMESTAMPTZ" => Datum::Timestamp(
|
||||
<Option<DateTime<Utc>> as Decode<Postgres>>::decode(value_ref).unwrap(),
|
||||
),
|
||||
_ => return Err(ParseError::UnknownType),
|
||||
|
|
|
|||
|
|
@ -1,24 +1,34 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::Decode;
|
||||
use sqlx::{Decode, Postgres};
|
||||
use strum::{EnumIter, EnumString};
|
||||
|
||||
/// Languages represented as
|
||||
/// [ISO 639-3 codes](https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes).
|
||||
#[derive(
|
||||
Clone, Debug, Decode, Deserialize, strum::Display, PartialEq, Serialize, EnumIter, EnumString,
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, strum::Display, PartialEq, Serialize, EnumIter, EnumString)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
// [`sqlx`] implements Decode and Encode to/from the Postgres `TEXT` type based
|
||||
// on the [`std::fmt::Display`] and [`std::str::FromStr`] traits, so it should
|
||||
// use the transformations applied by [`strum`].
|
||||
// <https://docs.rs/sqlx/latest/sqlx/types/struct.Text.html>
|
||||
// NOTE: The [`sqlx::Encode`] and [`sqlx::Decode`] derive macros do not seem to
|
||||
// use the [`strum`] serializations. The corresponding traits should be
|
||||
// implemented explicitly (if used).
|
||||
pub enum Language {
|
||||
Deu,
|
||||
Eng,
|
||||
Spa,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
/// Returns language name to be presented in UI.
|
||||
pub fn as_locale_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Deu => "Deutsch",
|
||||
Self::Eng => "English",
|
||||
Self::Spa => "Español",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Language {
|
||||
/// Language defaults to English when necessary, as the product is being
|
||||
/// developed with a primarily English speaking/reading/writing market in
|
||||
|
|
@ -28,12 +38,11 @@ impl Default for Language {
|
|||
}
|
||||
}
|
||||
|
||||
impl Language {
|
||||
pub fn as_locale_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Deu => "Deutsch",
|
||||
Self::Eng => "English",
|
||||
Self::Spa => "Español",
|
||||
}
|
||||
impl Decode<'_, Postgres> for Language {
|
||||
fn decode(
|
||||
value: <Postgres as sqlx::Database>::ValueRef<'_>,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let value = <&str as Decode<Postgres>>::decode(value)?;
|
||||
Ok(Self::from_str(value)?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
pub mod client;
|
||||
pub mod encodable;
|
||||
pub mod datum;
|
||||
pub mod expression;
|
||||
pub mod field;
|
||||
pub mod field_form_prompt;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::str::FromStr;
|
|||
|
||||
use derive_builder::Builder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Decode, Encode, Postgres, query_as};
|
||||
use sqlx::{Decode, Postgres, query_as};
|
||||
use strum::EnumString;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ from workspace_user_perms as p
|
|||
|
||||
// TODO: The sqlx::Decode derive macro doesn't follow the strum serialization.
|
||||
// Does sqlx::Encode?
|
||||
#[derive(Clone, Debug, Deserialize, Encode, EnumString, PartialEq, Serialize, strum::Display)]
|
||||
#[derive(Clone, Debug, Deserialize, EnumString, PartialEq, Serialize, strum::Display)]
|
||||
#[serde(rename = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum PermissionValue {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ use interim_models::client::AppDbClient;
|
|||
use oauth2::basic::BasicClient;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
|
||||
use crate::app_error::AppError;
|
||||
use crate::auth;
|
||||
use crate::base_pooler::WorkspacePooler;
|
||||
use crate::sessions::PgStore;
|
||||
use crate::settings::Settings;
|
||||
use crate::{
|
||||
auth, errors::AppError, sessions::PgStore, settings::Settings,
|
||||
workspace_pooler::WorkspacePooler,
|
||||
};
|
||||
|
||||
/// Global app configuration
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -14,8 +14,8 @@ use oauth2::{
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
app_error::AppError,
|
||||
app_state::{App, ReqwestClient},
|
||||
app::{App, ReqwestClient},
|
||||
errors::AppError,
|
||||
sessions::{AppSession, PgStore},
|
||||
settings::Settings,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ use tower_http::{
|
|||
compression::CompressionLayer, set_header::response::SetResponseHeaderLayer, trace::TraceLayer,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app_state::App, middleware::lowercase_uri_path, routes::new_router, worker::run_worker,
|
||||
};
|
||||
use crate::{app::App, middleware::lowercase_uri_path, routes::new_router, worker::run_worker};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
|
|
|
|||
|
|
@ -5,20 +5,17 @@ use interim_models::MIGRATOR;
|
|||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::{
|
||||
app_state::App,
|
||||
app::App,
|
||||
cli::{Cli, Commands, serve_command, worker_command},
|
||||
settings::Settings,
|
||||
};
|
||||
|
||||
mod app_error;
|
||||
mod app_state;
|
||||
mod app;
|
||||
mod auth;
|
||||
mod base_pooler;
|
||||
mod base_user_perms;
|
||||
mod cli;
|
||||
mod errors;
|
||||
mod field_info;
|
||||
mod middleware;
|
||||
mod navbar;
|
||||
mod navigator;
|
||||
mod renderable_role_tree;
|
||||
mod routes;
|
||||
|
|
@ -26,6 +23,9 @@ mod sessions;
|
|||
mod settings;
|
||||
mod user;
|
||||
mod worker;
|
||||
mod workspace_nav;
|
||||
mod workspace_pooler;
|
||||
mod workspace_user_perms;
|
||||
|
||||
/// Run CLI
|
||||
#[tokio::main]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use axum::{
|
|||
use interim_models::portal::Portal;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{app_error::AppError, app_state::App};
|
||||
use crate::{app::App, errors::AppError};
|
||||
|
||||
/// Helper type for semantically generating URI paths, e.g. for redirects.
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use tower_http::{
|
|||
};
|
||||
|
||||
use crate::auth;
|
||||
use crate::{app_state::App, settings::Settings};
|
||||
use crate::{app::App, settings::Settings};
|
||||
|
||||
mod relations_single;
|
||||
mod workspaces_multi;
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ use sqlx::query;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::{AppError, forbidden},
|
||||
app_state::{App, AppDbConn},
|
||||
base_pooler::{RoleAssignment, WorkspacePooler},
|
||||
app::{App, AppDbConn},
|
||||
errors::{AppError, forbidden},
|
||||
navigator::Navigator,
|
||||
user::CurrentUser,
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use sqlx::postgres::types::Oid;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::{AppError, forbidden},
|
||||
app_state::AppDbConn,
|
||||
app::AppDbConn,
|
||||
errors::{AppError, forbidden},
|
||||
navigator::Navigator,
|
||||
user::CurrentUser,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ use axum::{
|
|||
extract::{Path, State},
|
||||
response::{IntoResponse as _, Response},
|
||||
};
|
||||
use interim_models::{encodable::Encodable, field::Field, portal::Portal};
|
||||
use interim_models::{datum::Datum, field::Field, portal::Portal};
|
||||
use interim_pgtypes::{escape_identifier, pg_attribute::PgAttribute, pg_class::PgClass};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{postgres::PgRow, query};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::AppError,
|
||||
app_state::AppDbConn,
|
||||
base_pooler::{RoleAssignment, WorkspacePooler},
|
||||
app::AppDbConn,
|
||||
errors::AppError,
|
||||
field_info::FieldInfo,
|
||||
user::CurrentUser,
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
@ -106,23 +106,23 @@ pub(super) async fn get(
|
|||
#[derive(Serialize)]
|
||||
struct DataRow {
|
||||
pkey: String,
|
||||
data: Vec<Encodable>,
|
||||
data: Vec<Datum>,
|
||||
}
|
||||
|
||||
let mut data_rows: Vec<DataRow> = vec![];
|
||||
let mut pkeys: Vec<String> = vec![];
|
||||
for row in rows.iter() {
|
||||
let mut pkey_values: HashMap<String, Encodable> = HashMap::new();
|
||||
let mut pkey_values: HashMap<String, Datum> = HashMap::new();
|
||||
for attr in pkey_attrs.clone() {
|
||||
let field = Field::default_from_attr(&attr)
|
||||
.ok_or(anyhow::anyhow!("unsupported primary key column type"))?;
|
||||
pkey_values.insert(field.name.clone(), field.get_value_encodable(row)?);
|
||||
pkey_values.insert(field.name.clone(), field.get_datum(row)?);
|
||||
}
|
||||
let pkey = serde_json::to_string(&pkey_values)?;
|
||||
pkeys.push(pkey.clone());
|
||||
let mut row_data: Vec<Encodable> = vec![];
|
||||
let mut row_data: Vec<Datum> = vec![];
|
||||
for field in fields.iter() {
|
||||
row_data.push(field.field.get_value_encodable(row)?);
|
||||
row_data.push(field.field.get_datum(row)?);
|
||||
}
|
||||
data_rows.push(DataRow {
|
||||
pkey,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use axum::{
|
|||
// https://docs.rs/axum-extra/0.10.1/axum_extra/extract/struct.Form.html#differences-from-axumextractform
|
||||
use axum_extra::extract::Form;
|
||||
use interim_models::{
|
||||
encodable::Encodable,
|
||||
datum::Datum,
|
||||
portal::Portal,
|
||||
workspace::Workspace,
|
||||
workspace_user_perm::{self, WorkspaceUserPerm},
|
||||
|
|
@ -20,11 +20,11 @@ use sqlx::{postgres::types::Oid, query};
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::{AppError, forbidden},
|
||||
app_state::{App, AppDbConn},
|
||||
base_pooler::{RoleAssignment, WorkspacePooler},
|
||||
app::{App, AppDbConn},
|
||||
errors::{AppError, forbidden},
|
||||
navigator::Navigator,
|
||||
user::CurrentUser,
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -38,7 +38,7 @@ pub(super) struct PathParams {
|
|||
/// takes a form where the keys are column names, with keys optionally repeated
|
||||
/// to insert multiple rows at once. If any key is repeated, the others should
|
||||
/// be repeated the same number of times. Form values are expected to be JSON-
|
||||
/// serialized representations of the `[Encodable]` type.
|
||||
/// serialized representations of the `[Datum]` type.
|
||||
#[debug_handler(state = App)]
|
||||
pub(super) async fn post(
|
||||
State(mut workspace_pooler): State<WorkspacePooler>,
|
||||
|
|
@ -87,12 +87,12 @@ pub(super) async fn post(
|
|||
let n_rows = form.values().map(|value| value.len()).max().unwrap_or(0);
|
||||
if n_rows > 0 {
|
||||
let mut param_index = 1;
|
||||
let mut params: Vec<Encodable> = vec![];
|
||||
let mut params: Vec<Datum> = vec![];
|
||||
let mut row_list: Vec<String> = vec![];
|
||||
for i in 0..n_rows {
|
||||
let mut param_slots: Vec<String> = vec![];
|
||||
for col in col_names.iter() {
|
||||
let maybe_value: Option<Encodable> = form
|
||||
let maybe_value: Option<Datum> = form
|
||||
.get(col)
|
||||
.and_then(|col_values| col_values.get(i))
|
||||
.map(|value_raw| serde_json::from_str(value_raw))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use axum::{
|
|||
};
|
||||
use axum_extra::routing::RouterExt as _;
|
||||
|
||||
use crate::app_state::App;
|
||||
use crate::app::App;
|
||||
|
||||
mod add_field_handler;
|
||||
mod add_portal_handler;
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ use sqlx::postgres::types::Oid;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::AppError,
|
||||
app_state::AppDbConn,
|
||||
base_pooler::{RoleAssignment, WorkspacePooler},
|
||||
navbar::{NavLocation, RelLocation, WorkspaceNav},
|
||||
app::AppDbConn,
|
||||
errors::AppError,
|
||||
navigator::Navigator,
|
||||
settings::Settings,
|
||||
user::CurrentUser,
|
||||
workspace_nav::{NavLocation, RelLocation, WorkspaceNav},
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
@ -59,7 +59,7 @@ pub(super) async fn get(
|
|||
let attr_names: Vec<String> = attrs.iter().map(|attr| attr.attname.clone()).collect();
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "lens.html")]
|
||||
#[template(path = "portal_table.html")]
|
||||
struct ResponseTemplate {
|
||||
attr_names: Vec<String>,
|
||||
filter: Option<PgExpressionAny>,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ use axum::{
|
|||
use interim_models::workspace_user_perm::WorkspaceUserPerm;
|
||||
|
||||
use crate::{
|
||||
app_error::AppError, app_state::AppDbConn, navigator::Navigator, settings::Settings,
|
||||
user::CurrentUser,
|
||||
app::AppDbConn, errors::AppError, navigator::Navigator, settings::Settings, user::CurrentUser,
|
||||
};
|
||||
|
||||
pub(super) async fn get(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use axum::{Router, response::Redirect, routing::get};
|
||||
use axum_extra::routing::RouterExt as _;
|
||||
|
||||
use crate::app_state::App;
|
||||
use crate::app::App;
|
||||
|
||||
mod list_handlers;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ use sqlx::query;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::{AppError, forbidden},
|
||||
app_state::AppDbConn,
|
||||
base_pooler::{RoleAssignment, WorkspacePooler},
|
||||
app::AppDbConn,
|
||||
errors::{AppError, forbidden},
|
||||
navigator::Navigator,
|
||||
settings::Settings,
|
||||
user::CurrentUser,
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use axum::{
|
|||
};
|
||||
use axum_extra::routing::RouterExt as _;
|
||||
|
||||
use crate::app_state::App;
|
||||
use crate::app::App;
|
||||
|
||||
use super::relations_single;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ use serde::Deserialize;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::{AppError, forbidden},
|
||||
app_state::{App, AppDbConn},
|
||||
base_pooler::{RoleAssignment, WorkspacePooler},
|
||||
navbar::WorkspaceNav,
|
||||
app::{App, AppDbConn},
|
||||
errors::{AppError, forbidden},
|
||||
navigator::Navigator,
|
||||
settings::Settings,
|
||||
user::CurrentUser,
|
||||
workspace_nav::WorkspaceNav,
|
||||
workspace_pooler::{RoleAssignment, WorkspacePooler},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use chrono::{DateTime, TimeDelta, Utc};
|
|||
use sqlx::{PgPool, query, query_as};
|
||||
use tracing::{Instrument, trace_span};
|
||||
|
||||
use crate::{app_error::AppError, app_state::App};
|
||||
use crate::{app::App, errors::AppError};
|
||||
|
||||
const EXPIRY_DAYS: i64 = 7;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use config::{Config, Environment};
|
|||
use dotenvy::dotenv;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::app_state::App;
|
||||
use crate::app::App;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub(crate) struct Settings {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ use sqlx::query_as;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
app_error::AppError,
|
||||
app_state::App,
|
||||
app::App,
|
||||
auth::{AuthInfo, SESSION_KEY_AUTH_INFO, SESSION_KEY_AUTH_REDIRECT},
|
||||
errors::AppError,
|
||||
sessions::AppSession,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use tracing::Instrument as _;
|
||||
|
||||
use crate::app_state::App;
|
||||
use crate::app::App;
|
||||
|
||||
pub async fn run_worker(_state: App) -> Result<()> {
|
||||
async move { Ok(()) }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use sqlx::{Executor, PgPool, postgres::PgPoolOptions, raw_sql};
|
|||
use tokio::sync::{OnceCell, RwLock};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::app_state::App;
|
||||
use crate::app::App;
|
||||
|
||||
const MAX_CONNECTIONS: u32 = 4;
|
||||
const IDLE_SECONDS: u64 = 3600;
|
||||
|
|
@ -30,12 +30,12 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="encodable-editor__container">
|
||||
<div class="datum-editor__container">
|
||||
{#if assignable_fields.length > 0}
|
||||
<div class="encodable-editor__type-selector">
|
||||
<div class="datum-editor__type-selector">
|
||||
<button
|
||||
bind:this={type_selector_menu_button_element}
|
||||
class="encodable-editor__type-selector-menu-button"
|
||||
class="datum-editor__type-selector-menu-button"
|
||||
onclick={handle_type_selector_menu_button_click}
|
||||
type="button"
|
||||
>
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
</button>
|
||||
<div
|
||||
bind:this={type_selector_popover_element}
|
||||
class="encodable-editor__type-selector-popover"
|
||||
class="datum-editor__type-selector-popover"
|
||||
popover="auto"
|
||||
>
|
||||
{#each assignable_fields as assignable_field_info}
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="encodable-editor__content">
|
||||
<div class="datum-editor__content">
|
||||
{#if field_info.field.presentation.t === "Text" || field_info.field.presentation.t === "Uuid"}
|
||||
<input bind:value={editor_state.text_value} type="text" />
|
||||
{:else if field_info.field.presentation.t === "Timestamp"}
|
||||
|
|
@ -2,9 +2,7 @@ import { z } from "zod";
|
|||
|
||||
type Assert<_T extends true> = void;
|
||||
|
||||
// -------- Encodable -------- //
|
||||
|
||||
export const all_encodable_tags = [
|
||||
export const all_datum_tags = [
|
||||
"Text",
|
||||
"Timestamp",
|
||||
"Uuid",
|
||||
|
|
@ -12,28 +10,28 @@ export const all_encodable_tags = [
|
|||
|
||||
// Type checking to ensure that all valid enum tags are included.
|
||||
type _ = Assert<
|
||||
Encodable["t"] extends (typeof all_encodable_tags)[number] ? true : false
|
||||
Datum["t"] extends (typeof all_datum_tags)[number] ? true : false
|
||||
>;
|
||||
|
||||
const encodable_text_schema = z.object({
|
||||
const datum_text_schema = z.object({
|
||||
t: z.literal("Text"),
|
||||
c: z.string().nullish().transform((x) => x ?? undefined),
|
||||
});
|
||||
|
||||
const encodable_timestamp_schema = z.object({
|
||||
const datum_timestamp_schema = z.object({
|
||||
t: z.literal("Timestamp"),
|
||||
c: z.coerce.date().nullish().transform((x) => x ?? undefined),
|
||||
});
|
||||
|
||||
const encodable_uuid_schema = z.object({
|
||||
const datum_uuid_schema = z.object({
|
||||
t: z.literal("Uuid"),
|
||||
c: z.string().nullish().transform((x) => x ?? undefined),
|
||||
});
|
||||
|
||||
export const encodable_schema = z.union([
|
||||
encodable_text_schema,
|
||||
encodable_timestamp_schema,
|
||||
encodable_uuid_schema,
|
||||
export const datum_schema = z.union([
|
||||
datum_text_schema,
|
||||
datum_timestamp_schema,
|
||||
datum_uuid_schema,
|
||||
]);
|
||||
|
||||
export type Encodable = z.infer<typeof encodable_schema>;
|
||||
export type Datum = z.infer<typeof datum_schema>;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import * as uuid from "uuid";
|
||||
|
||||
import { type Encodable } from "./encodable.svelte.ts";
|
||||
import { type Datum } from "./datum.svelte.ts";
|
||||
import { type Presentation } from "./presentation.svelte.ts";
|
||||
|
||||
type Assert<_T extends true> = void;
|
||||
|
|
@ -22,7 +22,7 @@ export const DEFAULT_EDITOR_STATE: EditorState = {
|
|||
is_null: false,
|
||||
};
|
||||
|
||||
export function editor_state_from_encodable(value: Encodable): EditorState {
|
||||
export function editor_state_from_datum(value: Datum): EditorState {
|
||||
if (value.t === "Text") {
|
||||
return {
|
||||
...DEFAULT_EDITOR_STATE,
|
||||
|
|
@ -53,10 +53,10 @@ export function editor_state_from_encodable(value: Encodable): EditorState {
|
|||
throw new Error("this should be unreachable");
|
||||
}
|
||||
|
||||
export function encodable_from_editor_state(
|
||||
export function datum_from_editor_state(
|
||||
value: EditorState,
|
||||
presentation: Presentation,
|
||||
): Encodable | undefined {
|
||||
): Datum | undefined {
|
||||
if (presentation.t === "Text") {
|
||||
return { t: "Text", c: value.text_value };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@
|
|||
/>
|
||||
|
||||
<script lang="ts">
|
||||
import EncodableEditor from "./encodable-editor.svelte";
|
||||
import DatumEditor from "./datum-editor.svelte";
|
||||
import ExpressionSelector from "./expression-selector.svelte";
|
||||
import { type PgExpressionAny } from "./expression.svelte";
|
||||
import ExpressionEditor from "./expression-editor.webc.svelte";
|
||||
import {
|
||||
DEFAULT_EDITOR_STATE,
|
||||
editor_state_from_encodable,
|
||||
editor_state_from_datum,
|
||||
type EditorState,
|
||||
encodable_from_editor_state,
|
||||
datum_from_editor_state,
|
||||
} from "./editor-state.svelte";
|
||||
import { type FieldInfo } from "./field.svelte";
|
||||
import { type Presentation } from "./presentation.svelte";
|
||||
|
|
@ -51,19 +51,19 @@
|
|||
|
||||
let editor_state = $state<EditorState>(
|
||||
value?.t === "Literal"
|
||||
? editor_state_from_encodable(value.c)
|
||||
? editor_state_from_datum(value.c)
|
||||
: DEFAULT_EDITOR_STATE,
|
||||
);
|
||||
let editor_field_info = $state<FieldInfo>(ASSIGNABLE_FIELDS[0]);
|
||||
|
||||
$effect(() => {
|
||||
if (value?.t === "Literal" && editor_field_info) {
|
||||
const encodable_value = encodable_from_editor_state(
|
||||
const datum_value = datum_from_editor_state(
|
||||
editor_state,
|
||||
editor_field_info.field.presentation,
|
||||
);
|
||||
if (encodable_value) {
|
||||
value.c = encodable_value;
|
||||
if (datum_value) {
|
||||
value.c = datum_value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
{/each}
|
||||
</select>
|
||||
{:else if value.t === "Literal"}
|
||||
<EncodableEditor
|
||||
<DatumEditor
|
||||
bind:editor_state
|
||||
bind:field_info={editor_field_info}
|
||||
assignable_fields={ASSIGNABLE_FIELDS}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import cube_icon from "../assets/heroicons/20/solid/cube.svg?raw";
|
|||
import cube_transparent_icon from "../assets/heroicons/20/solid/cube-transparent.svg?raw";
|
||||
import hashtag_icon from "../assets/heroicons/20/solid/hashtag.svg?raw";
|
||||
import variable_icon from "../assets/heroicons/20/solid/variable.svg?raw";
|
||||
import { encodable_schema } from "./encodable.svelte.ts";
|
||||
import { datum_schema } from "./datum.svelte.ts";
|
||||
|
||||
export const all_expression_types = [
|
||||
"Comparison",
|
||||
|
|
@ -89,7 +89,7 @@ const pg_expression_any_identifier_schema = z.object({
|
|||
|
||||
const pg_expression_any_literal_schema = z.object({
|
||||
t: z.literal("Literal"),
|
||||
c: encodable_schema,
|
||||
c: datum_schema,
|
||||
});
|
||||
|
||||
const pg_to_json_expression_schema = z.object({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { type Encodable } from "./encodable.svelte.ts";
|
||||
import { type Datum } from "./datum.svelte.ts";
|
||||
import { presentation_schema } from "./presentation.svelte.ts";
|
||||
|
||||
export const field_schema = z.object({
|
||||
|
|
@ -28,7 +28,7 @@ export type Coords = [number, number];
|
|||
|
||||
export type Row = {
|
||||
key: string | number;
|
||||
data: Encodable[];
|
||||
data: Datum[];
|
||||
};
|
||||
|
||||
export function coords_eq(a: Coords, b: Coords): boolean {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { type Encodable } from "./encodable.svelte.ts";
|
||||
import { type Datum } from "./datum.svelte.ts";
|
||||
|
||||
type Assert<_T extends true> = void;
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ export const presentation_schema = z.union([
|
|||
|
||||
export type Presentation = z.infer<typeof presentation_schema>;
|
||||
|
||||
export function get_empty_encodable_for(presentation: Presentation): Encodable {
|
||||
export function get_empty_datum_for(presentation: Presentation): Datum {
|
||||
if (presentation.t === "Timestamp") {
|
||||
return { t: "Timestamp", c: undefined };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@
|
|||
import icon_cube_transparent from "../assets/heroicons/20/solid/cube-transparent.svg?raw";
|
||||
import icon_exclamation_circle from "../assets/heroicons/20/solid/exclamation-circle.svg?raw";
|
||||
import icon_sparkles from "../assets/heroicons/20/solid/sparkles.svg?raw";
|
||||
import { type Encodable, encodable_schema } from "./encodable.svelte";
|
||||
import EncodableEditor from "./encodable-editor.svelte";
|
||||
import { type Datum, datum_schema } from "./datum.svelte";
|
||||
import DatumEditor from "./datum-editor.svelte";
|
||||
import {
|
||||
DEFAULT_EDITOR_STATE,
|
||||
encodable_from_editor_state,
|
||||
datum_from_editor_state,
|
||||
type EditorState,
|
||||
} from "./editor-state.svelte";
|
||||
import {
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
field_info_schema,
|
||||
} from "./field.svelte";
|
||||
import FieldHeader from "./field-header.svelte";
|
||||
import { get_empty_encodable_for } from "./presentation.svelte";
|
||||
import { get_empty_datum_for } from "./presentation.svelte";
|
||||
|
||||
type Props = {
|
||||
columns?: string[];
|
||||
|
|
@ -43,8 +43,8 @@
|
|||
// This will be identical to coords_initial, unless the change altered a
|
||||
// primary key.
|
||||
coords_updated: Coords;
|
||||
value_initial: Encodable;
|
||||
value_updated: Encodable;
|
||||
value_initial: Datum;
|
||||
value_updated: Datum;
|
||||
};
|
||||
|
||||
type LazyData = {
|
||||
|
|
@ -55,10 +55,10 @@
|
|||
type Selection = {
|
||||
region: "main" | "inserter";
|
||||
coords: Coords;
|
||||
original_value: Encodable;
|
||||
original_value: Datum;
|
||||
};
|
||||
|
||||
type ParsedPkey = Record<string, Encodable>;
|
||||
type ParsedPkey = Record<string, Datum>;
|
||||
|
||||
let selections = $state<Selection[]>([]);
|
||||
let editing = $state(false);
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
|
||||
function set_selections(arr: Omit<Selection, "original_value">[]) {
|
||||
selections = arr.map((sel) => {
|
||||
let cell_data: Encodable | undefined;
|
||||
let cell_data: Datum | undefined;
|
||||
if (sel.region === "main") {
|
||||
cell_data = lazy_data?.rows[sel.coords[0]].data[sel.coords[1]];
|
||||
} else if (sel.region === "inserter") {
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
});
|
||||
if (arr.length === 1) {
|
||||
const [sel] = arr;
|
||||
let cell_data: Encodable | undefined;
|
||||
let cell_data: Datum | undefined;
|
||||
if (sel.region === "main") {
|
||||
cell_data = lazy_data?.rows[sel.coords[0]].data[sel.coords[1]];
|
||||
} else if (sel.region === "inserter") {
|
||||
|
|
@ -218,7 +218,7 @@
|
|||
function try_sync_edit_to_cells() {
|
||||
if (lazy_data && editing && selections.length === 1) {
|
||||
const [sel] = selections;
|
||||
const parsed = encodable_from_editor_state(
|
||||
const parsed = datum_from_editor_state(
|
||||
editor_state,
|
||||
lazy_data.fields[sel.coords[1]].field.presentation,
|
||||
);
|
||||
|
|
@ -246,7 +246,7 @@
|
|||
if (lazy_data && editing && editor_state && selections.length === 1) {
|
||||
const [sel] = selections;
|
||||
const field = lazy_data.fields[sel.coords[1]];
|
||||
const parsed = encodable_from_editor_state(
|
||||
const parsed = datum_from_editor_state(
|
||||
editor_state,
|
||||
field.field.presentation,
|
||||
);
|
||||
|
|
@ -337,7 +337,7 @@
|
|||
{
|
||||
key: inserter_rows.length,
|
||||
data: lazy_data.fields.map(({ field: { presentation } }) =>
|
||||
get_empty_encodable_for(presentation),
|
||||
get_empty_datum_for(presentation),
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
@ -416,7 +416,7 @@
|
|||
rows: z.array(
|
||||
z.object({
|
||||
pkey: z.string(),
|
||||
data: z.array(encodable_schema),
|
||||
data: z.array(datum_schema),
|
||||
}),
|
||||
),
|
||||
fields: z.array(field_info_schema),
|
||||
|
|
@ -431,7 +431,7 @@
|
|||
{
|
||||
key: 0,
|
||||
data: body.fields.map(({ field: { presentation } }) =>
|
||||
get_empty_encodable_for(presentation),
|
||||
get_empty_datum_for(presentation),
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
@ -591,7 +591,7 @@
|
|||
</div>
|
||||
<div class="lens-editor">
|
||||
{#if selections.length === 1 && editor_state}
|
||||
<EncodableEditor
|
||||
<DatumEditor
|
||||
bind:editor_state
|
||||
field_info={lazy_data.fields[selections[0].coords[1]]}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue