diff --git a/backend/migrations/20221219032311_folders.down.sql b/backend/migrations/20221219032311_folders.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20221219032311_folders.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20221219032311_folders.up.sql b/backend/migrations/20221219032311_folders.up.sql new file mode 100644 index 0000000000..c64fbf1ae3 --- /dev/null +++ b/backend/migrations/20221219032311_folders.up.sql @@ -0,0 +1,19 @@ +-- Add up migration script here +ALTER TABLE app_version +RENAME COLUMN flow_id TO app_id; + +CREATE TABLE folder ( + name VARCHAR(255), + workspace_id VARCHAR(50) NOT NULL, + display_name VARCHAR(100) NOT NULL, + owners VARCHAR(255)[] NOT NULL, + extra_perms JSONB NOT NULL DEFAULT '{}', + FOREIGN KEY (workspace_id) REFERENCES workspace(id) ON DELETE CASCADE, + PRIMARY KEY (workspace_id, name) +); + +CREATE INDEX folder_extra_perms ON folder USING GIN (extra_perms); +CREATE INDEX folder_owners ON folder USING GIN (owners); + +GRANT ALL ON folder TO windmill_user; +GRANT ALL ON folder TO windmill_admin; diff --git a/backend/migrations/20221219051235_folders_2.down.sql b/backend/migrations/20221219051235_folders_2.down.sql new file mode 100644 index 0000000000..44b8aaa365 --- /dev/null +++ b/backend/migrations/20221219051235_folders_2.down.sql @@ -0,0 +1,21 @@ +-- Add down migration script here +DO +$do$ + DECLARE + i text; + arr text[] := array['resource', 'script', 'variable', 'schedule', 'flow', 'app']; + BEGIN + FOREACH i IN ARRAY arr + LOOP + EXECUTE FORMAT( + $$ + DROP POLICY see_folder_extra_perms_user ON %1$I; + $$, + i + ); + END LOOP; + END +$do$; + +DROP POLICY see_extra_perms_user ON folder; +DROP POLICY see_extra_perms_groups ON folder; \ No newline at end of file diff --git a/backend/migrations/20221219051235_folders_2.up.sql b/backend/migrations/20221219051235_folders_2.up.sql new file mode 100644 index 0000000000..de89abe73d --- /dev/null +++ b/backend/migrations/20221219051235_folders_2.up.sql @@ -0,0 +1,52 @@ +-- Add up migration script here +DO +$do$ + DECLARE + i text; + arr text[] := array['resource', 'script', 'variable', 'schedule', 'flow', 'app']; + BEGIN + FOREACH i IN ARRAY arr + LOOP + EXECUTE FORMAT( + $$ + CREATE POLICY see_folder_extra_perms_user ON %1$I FOR ALL + USING (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[])) + WITH CHECK (SPLIT_PART(%1$I.path, '/', 1) = 'f' AND SPLIT_PART(%1$I.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[])); + $$, + i + ); + END LOOP; + END +$do$; +ALTER TABLE folder ENABLE ROW LEVEL SECURITY; + +CREATE POLICY see_extra_perms_user ON folder FOR ALL +USING (extra_perms ? CONCAT('u/', current_setting('session.user')) or (CONCAT('u/', current_setting('session.user')) = ANY(owners))) +WITH CHECK ((CONCAT('u/', current_setting('session.user')) = ANY(owners))); + +CREATE POLICY see_extra_perms_groups ON folder FOR ALL +USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[] or (exists( + SELECT o FROM unnest(owners) as o + WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])))) +WITH CHECK (exists( + SELECT o FROM unnest(owners) as o + WHERE o = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]))); + +DO +$do$ + DECLARE + i text; + arr text[] := array['script', 'flow', 'variable', 'resource', 'schedule']; + BEGIN + FOREACH i IN ARRAY arr + LOOP + EXECUTE FORMAT( + $$ + ALTER TABLE %1$I DROP CONSTRAINT proper_id; + ALTER TABLE %1$I ADD CONSTRAINT proper_id CHECK (path ~ '^[ufg](\/[\w-]+){2,}$'); + $$, + i + ); + END LOOP; + END +$do$; diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index e267888b62..0fe18b244b 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -204,6 +204,28 @@ }, "query": "\n INSERT INTO capture\n (workspace_id, path, created_by)\n VALUES ($1, $2, $3)\n ON CONFLICT (workspace_id, path)\n DO UPDATE SET created_at = now()\n " }, + "0bfd22be1d6966c61c9a5fedc2522be8a17f06391b882337c74c1817c99b533d": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text", + "Int8", + "Int8" + ] + } + }, + "query": "SELECT name FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3" + }, "0c5faa1e3233ca15240f80c9ba805f839d9f1f7450d1ea1f2b8e9faf8533892d": { "describe": { "columns": [], @@ -638,6 +660,28 @@ }, "query": "UPDATE queue SET suspend = $1 WHERE id = $2" }, + "1fab686dee77ae1ef35ff17239900ddbb55e86320ec820803f454c53571fdddf": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Varchar", + "Text", + "Text" + ] + } + }, + "query": "UPDATE folder SET owners = array_remove(owners, $1) WHERE name = $2 AND workspace_id = $3 RETURNING name" + }, "20e8a8343adc4a3a28a1c4908a13047cddafc4480cf028d3455fd17347cace73": { "describe": { "columns": [ @@ -717,28 +761,6 @@ }, "query": "DELETE FROM favorite WHERE workspace_id = $1 AND usr = $2 AND path = $3 AND favorite_kind = $4" }, - "23086afd75927486884944e48b768e956d1fd77ce08c6f345fcde083b1e9bbf1": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Int8", - "Jsonb", - "Varchar" - ] - } - }, - "query": "INSERT INTO app_version\n (flow_id, value, created_by)\n VALUES ($1, $2, $3) RETURNING id" - }, "230d58732a08164268ca10d248a93cced646632a76864b693ed2325d85b36c45": { "describe": { "columns": [ @@ -798,6 +820,19 @@ }, "query": "SELECT now()" }, + "26e4ec75366d1e46a98710f29066b40e66a802f98eeabbb3ae5bebe3aeb6b3f8": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + } + }, + "query": "DELETE FROM folder WHERE name = $1 AND workspace_id = $2" + }, "28c042adef65c3055edc324fbbd2f267285d3566cbec58404983323d410ace27": { "describe": { "columns": [ @@ -843,6 +878,26 @@ }, "query": "DELETE FROM usr_to_group WHERE usr = $1" }, + "29fbc3a8c35845a997cd548ed417b9cc3c82d815d99b3d435adcfbb5a9246124": { + "describe": { + "columns": [ + { + "name": "set_config", + "ordinal": 0, + "type_info": "Text" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT set_config('session.folders_read', $1, true)" + }, "2a4be8334db7d39f3d954193a8b0169cc4a4a07e081d2fa61d8764879d6a8ff5": { "describe": { "columns": [], @@ -948,6 +1003,51 @@ }, "query": "UPDATE schedule SET enabled = false, error = $1 WHERE workspace_id = $2 AND path = $3" }, + "2fe6c0806486fbdc6cfc4bb0d5fa8f4267c6e117523aa2c1ab6c6df368c43b32": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + }, + { + "name": "workspace_id", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "owners", + "ordinal": 2, + "type_info": "VarcharArray" + }, + { + "name": "extra_perms", + "ordinal": 3, + "type_info": "Jsonb" + }, + { + "name": "display_name", + "ordinal": 4, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + } + }, + "query": "SELECT * FROM folder WHERE name = $1 AND workspace_id = $2" + }, "31a6e8a73fa568e28b8ca6e35a38b663e0b07d15a212b81883ad9c327d99a7c4": { "describe": { "columns": [], @@ -985,6 +1085,28 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM resource_type WHERE name = $1 AND workspace_id = $2)" }, + "3593fe778f1b2cad6441108d88577641b5dd5d28bca17f615e5b6eb86a0f8ba8": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + } + }, + "query": "UPDATE folder SET owners = array_append(owners, $1) WHERE name = $2 AND workspace_id = $3 AND NOT $1 = ANY(owners) RETURNING name" + }, "372b0a81d37b6874727c1aa4dc1bec17f9475a5f446e8987ab7359f55b96436e": { "describe": { "columns": [], @@ -1096,6 +1218,75 @@ }, "query": "UPDATE app SET versions = array_append(versions, $1) WHERE id = $2" }, + "438fb925ee90d5115bd3c3be8ae48b56ba86017af3ca519bd3a15829edaa7d1b": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "path", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "summary", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "versions", + "ordinal": 3, + "type_info": "Int8Array" + }, + { + "name": "policy", + "ordinal": 4, + "type_info": "Jsonb" + }, + { + "name": "extra_perms", + "ordinal": 5, + "type_info": "Jsonb" + }, + { + "name": "value", + "ordinal": 6, + "type_info": "Jsonb" + }, + { + "name": "created_at", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "created_by", + "ordinal": 8, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + } + }, + "query": "SELECT app.id, app.path, app.summary, app.versions, app.policy,\n app.extra_perms, app_version.value, \n app_version.created_at, app_version.created_by from app, app_version \n WHERE app_version.id = $1 AND app.id = app_version.app_id AND app.workspace_id = $2" + }, "453501fbd61efd26647baf9b6ef702ce0bc2e920914e9f08fe5f2a5f4ab03f02": { "describe": { "columns": [ @@ -1408,75 +1599,6 @@ }, "query": "INSERT INTO token\n (token, email, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, $5)" }, - "547f0a03f954210b06ad3cbee0ff4ef90c378be68d6a105b8bcda8a7a8d834c6": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "path", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "summary", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "versions", - "ordinal": 3, - "type_info": "Int8Array" - }, - { - "name": "policy", - "ordinal": 4, - "type_info": "Jsonb" - }, - { - "name": "extra_perms", - "ordinal": 5, - "type_info": "Jsonb" - }, - { - "name": "value", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "created_by", - "ordinal": 8, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - } - }, - "query": "SELECT app.id, app.path, app.summary, app.versions, app.policy,\n app.extra_perms, app_version.value, \n app_version.created_at, app_version.created_by from app, app_version \n WHERE app_version.id = $1 AND app.id = app_version.flow_id AND app.workspace_id = $2" - }, "55960eb3cd5a82f593496349a2521607f8066d88ff11a2ca4a63b6e37e62b71b": { "describe": { "columns": [ @@ -2088,6 +2210,52 @@ }, "query": "UPDATE workspace SET deleted = true WHERE id = $1" }, + "705a34bdcd904b25f22e533ee6e659b6901584bde8e851c43b11869c332e8683": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + }, + { + "name": "workspace_id", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "owners", + "ordinal": 2, + "type_info": "VarcharArray" + }, + { + "name": "extra_perms", + "ordinal": 3, + "type_info": "Jsonb" + }, + { + "name": "display_name", + "ordinal": 4, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Text", + "Int8", + "Int8" + ] + } + }, + "query": "SELECT * FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3" + }, "70e6ca3b5ad81f70376d31c75ba5f2b5dd94dd2f7f4cacd5a64029ccc3315d6c": { "describe": { "columns": [ @@ -2923,28 +3091,6 @@ }, "query": "SELECT is_admin FROM usr where username = $1 AND workspace_id = $2 AND disabled = false" }, - "9251404312374679a4be2f9def508fcadce446405e91d95100c3397ec26e5882": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Int8", - "Jsonb", - "Varchar" - ] - } - }, - "query": "INSERT INTO app_version\n (flow_id, value, created_by)\n VALUES ($1, $2, $3) RETURNING id" - }, "930ad84a4db26fa6d2c8447d447099e944a0ea7b1266b4d02cee620fe3d761a5": { "describe": { "columns": [], @@ -3116,6 +3262,26 @@ }, "query": "DELETE FROM token WHERE token = $1 RETURNING email" }, + "9897705b7c265333559df757a725711a40924ec551a7589a64ee96f8aa7f6a21": { + "describe": { + "columns": [ + { + "name": "set_config", + "ordinal": 0, + "type_info": "Text" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT set_config('session.folders_write', $1, true)" + }, "9db64c9ff790d8c833c1e831a87803c32103ce9de68cc9c08d6f56cc988d7e37": { "describe": { "columns": [ @@ -4254,6 +4420,28 @@ }, "query": "SELECT app.id, app.path, app.summary, app.versions, app.policy,\n app.extra_perms, app_version.value, \n app_version.created_at, app_version.created_by from app, app_version \n WHERE app.path = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]" }, + "d47bff7d6b54cd6da8bb330f7321c37af5dcbd76f9acad73b5ba1b8a4afb5091": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8", + "Jsonb", + "Varchar" + ] + } + }, + "query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2, $3) RETURNING id" + }, "d4bf68b4c2bfd11db84787e41e92c13612fda7352e841f3cdef45ea762588f04": { "describe": { "columns": [], @@ -4299,6 +4487,28 @@ }, "query": "DELETE FROM resource_type WHERE name = $1 AND workspace_id = $2" }, + "d68eaf1533bbe9052f6b1737e2f212c969a18c9d7840fe419c20995404ed0be8": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8", + "Jsonb", + "Varchar" + ] + } + }, + "query": "INSERT INTO app_version\n (app_id, value, created_by)\n VALUES ($1, $2, $3) RETURNING id" + }, "d697b7311430e7bd5375ec5494179f4071e3bfe123d9600249cfa80c1103edd8": { "describe": { "columns": [], @@ -4495,6 +4705,22 @@ }, "query": "SELECT EXISTS (SELECT 1 FROM flow WHERE path = $1 AND workspace_id = $2)" }, + "e33d0bec7e08c0bb4e6f26c93a636707714699e8984b9f537085e773849c8b43": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "VarcharArray", + "Jsonb" + ] + } + }, + "query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)" + }, "e3eeda2e19bfbfd5aadd71c40774f7e93c8479a777fdb2828607b1db36361726": { "describe": { "columns": [], @@ -4935,6 +5161,33 @@ }, "query": "DELETE FROM usr_to_group WHERE usr = $1 AND group_ = $2 AND workspace_id = $3" }, + "f418ad25df9bd0a85c7210c4ea0f44c3b6e8c0810b49e325fd0698259f0bb1a3": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + }, + { + "name": "write", + "ordinal": 1, + "type_info": "Bool" + } + ], + "nullable": [ + false, + null + ], + "parameters": { + "Left": [ + "TextArray", + "Text" + ] + } + }, + "query": "SELECT name, (EXISTS (SELECT 1 FROM (SELECT key, value FROM jsonb_each_text(extra_perms) WHERE key = ANY($1)) t WHERE value::boolean IS true)) as write FROM folder\n WHERE extra_perms ?| $1 AND workspace_id = $2" + }, "f4849960aff7387cb6b130d2bc62dbfce45209fbbccf3b4b9b13019e0cd55ddb": { "describe": { "columns": [ diff --git a/backend/windmill-api/src/folders.rs b/backend/windmill-api/src/folders.rs new file mode 100644 index 0000000000..c81a369928 --- /dev/null +++ b/backend/windmill-api/src/folders.rs @@ -0,0 +1,291 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2022 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +use crate::{ + db::{UserDB, DB}, + users::Authed, +}; +use axum::{ + extract::{Extension, Path, Query}, + routing::{delete, get, post}, + Json, Router, +}; +use windmill_audit::{audit_log, ActionKind}; +use windmill_common::{ + error::{JsonResult, Result}, + users::owner_to_token_owner, + utils::{not_found_if_none, paginate, Pagination}, +}; + +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, Postgres, Transaction}; + +pub fn workspaced_service() -> Router { + Router::new() + .route("/list", get(list_folders)) + .route("/listnames", get(list_foldernames)) + .route("/create", post(create_folder)) + .route("/get/:name", get(get_folder)) + .route("/delete/:name", delete(delete_folder)) + .route("/addowner/:name", post(add_owner)) + .route("/removeowner/:name", post(remove_owner)) +} + +#[derive(FromRow, Serialize, Deserialize)] +pub struct Folder { + pub workspace_id: String, + pub name: String, + pub display_name: String, + pub owners: Vec, + pub extra_perms: serde_json::Value, +} + +#[derive(Deserialize)] +pub struct NewFolder { + pub name: String, + pub display_name: Option, + pub owners: Option>, + pub extra_perms: Option, +} + +#[derive(Deserialize)] +pub struct Owner { + pub owner: String, +} + +async fn list_folders( + authed: Authed, + Extension(user_db): Extension, + Path(w_id): Path, + Query(pagination): Query, +) -> JsonResult> { + let (per_page, offset) = paginate(pagination); + let mut tx = user_db.begin(&authed).await?; + + let rows = sqlx::query_as!( + Folder, + "SELECT * FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3", + w_id, + per_page as i64, + offset as i64 + ) + .fetch_all(&mut tx) + .await?; + tx.commit().await?; + + Ok(Json(rows)) +} +async fn list_foldernames( + authed: Authed, + Extension(user_db): Extension, + Path(w_id): Path, + Query(pagination): Query, +) -> JsonResult> { + let (per_page, offset) = paginate(pagination); + let mut tx = user_db.begin(&authed).await?; + + let rows = sqlx::query_scalar!( + "SELECT name FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3", + w_id, + per_page as i64, + offset as i64 + ) + .fetch_all(&mut tx) + .await?; + + tx.commit().await?; + + Ok(Json(rows)) +} + +async fn create_folder( + authed: Authed, + Extension(user_db): Extension, + Path(w_id): Path, + Json(ng): Json, +) -> Result { + let mut tx = user_db.begin(&authed).await?; + + let owner = owner_to_token_owner(&authed.username, false); + sqlx::query_as!( + Folder, + "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)", + w_id, + ng.name, + ng.display_name.unwrap_or(ng.name.clone()), + &ng.owners.unwrap_or(vec![owner.clone()]), + ng.extra_perms.unwrap_or(serde_json::json!({owner: true})) + ) + .execute(&mut tx) + .await?; + + audit_log( + &mut tx, + &authed.username, + "folder.create", + ActionKind::Create, + &w_id, + Some(&ng.name.to_string()), + None, + ) + .await?; + + tx.commit().await?; + Ok(format!("Created folder {}", ng.name)) +} + +pub async fn get_folderopt<'c>( + db: &mut Transaction<'c, Postgres>, + w_id: &str, + name: &str, +) -> Result> { + let folderopt = sqlx::query_as!( + Folder, + "SELECT * FROM folder WHERE name = $1 AND workspace_id = $2", + name, + w_id + ) + .fetch_optional(db) + .await?; + Ok(folderopt) +} + +async fn get_folder( + authed: Authed, + Extension(user_db): Extension, + Path((w_id, name)): Path<(String, String)>, +) -> JsonResult { + let mut tx = user_db.begin(&authed).await?; + + let folder = not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?; + + tx.commit().await?; + Ok(Json(folder)) +} + +async fn delete_folder( + authed: Authed, + Extension(user_db): Extension, + Path((w_id, name)): Path<(String, String)>, +) -> Result { + let mut tx = user_db.begin(&authed).await?; + + not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?; + + sqlx::query!( + "DELETE FROM folder WHERE name = $1 AND workspace_id = $2", + name, + w_id + ) + .execute(&mut tx) + .await?; + audit_log( + &mut tx, + &authed.username, + "folder.delete", + ActionKind::Delete, + &w_id, + Some(&name.to_string()), + None, + ) + .await?; + tx.commit().await?; + Ok(format!("delete folder at name {}", name)) +} + +async fn add_owner( + authed: Authed, + Extension(user_db): Extension, + Path((w_id, name)): Path<(String, String)>, + Json(Owner { owner }): Json, +) -> Result { + let mut tx = user_db.begin(&authed).await?; + + not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?; + + sqlx::query!( + "UPDATE folder SET owners = array_append(owners, $1) WHERE name = $2 AND workspace_id = $3 AND NOT $1 = ANY(owners) RETURNING name", + owner, + name, + &w_id, + ) + .fetch_optional(&mut tx) + .await?; + + audit_log( + &mut tx, + &authed.username, + "folder.add_owner", + ActionKind::Update, + &w_id, + Some(&name.to_string()), + Some([("owner", owner.as_str())].into()), + ) + .await?; + tx.commit().await?; + Ok(format!("Added {} to folder {}", owner, name)) +} + +pub async fn get_folders_for_user( + w_id: &str, + username: &str, + groups: &[String], + db: &DB, +) -> Result> { + let mut perms = groups + .into_iter() + .map(|x| format!("g/{}", x)) + .collect::>(); + perms.insert(0, format!("u/{}", username)); + let folders = sqlx::query!( + "SELECT name, (EXISTS (SELECT 1 FROM (SELECT key, value FROM jsonb_each_text(extra_perms) WHERE key = ANY($1)) t WHERE value::boolean IS true)) as write FROM folder + WHERE extra_perms ?| $1 AND workspace_id = $2", + &perms[..], + w_id, + ) + .fetch_all(db) + .await? + .into_iter() + .map(|x| (x.name, x.write.unwrap_or(false))) + .collect(); + + Ok(folders) +} + +async fn remove_owner( + authed: Authed, + Extension(user_db): Extension, + Path((w_id, name)): Path<(String, String)>, + Json(Owner { owner }): Json, +) -> Result { + let mut tx = user_db.begin(&authed).await?; + + not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?; + + sqlx::query!( + "UPDATE folder SET owners = array_remove(owners, $1) WHERE name = $2 AND workspace_id = $3 RETURNING name", + owner, + name, + &w_id, + ) + .fetch_optional(&mut tx) + .await?; + + audit_log( + &mut tx, + &authed.username, + "folder.remove_owner", + ActionKind::Update, + &w_id, + Some(&name.to_string()), + Some([("owner", owner.as_str())].into()), + ) + .await?; + tx.commit().await?; + Ok(format!("Removed {} to folder {}", owner, name)) +} diff --git a/frontend/src/lib/components/apps/CreateApp.svelte b/frontend/src/lib/components/apps/CreateApp.svelte index a099d0321c..927bbf95fa 100644 --- a/frontend/src/lib/components/apps/CreateApp.svelte +++ b/frontend/src/lib/components/apps/CreateApp.svelte @@ -23,8 +23,7 @@ async function createApp() { const appJson: App = { grid: [], - title: 'New app', - inlineScripts: {} + title: 'New app' } const policy = {