mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
feat(backend): add scoped get-draft and list-drafts endpoints
Never-deployed items now live only in the draft table, so the editors and home
list need to read drafts directly:
- GET /w/{workspace}/drafts/get/{kind}/{path} returns the requesting user's
draft for that item (email-scoped), 404 if none.
- GET /w/{workspace}/drafts/list returns the user's drafts (path, typ, value,
created_at), email-scoped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path, value as \"value: _\", typ as \"typ: DraftType\"\n FROM draft\n WHERE path = $1 AND typ = $2 AND workspace_id = $3 AND email = $4",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "value: _",
|
||||
"type_info": "Json"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "typ: DraftType",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "draft_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"flow",
|
||||
"app"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "draft_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"flow",
|
||||
"app"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0877f40f086fb5324af54701b29c1edc9d3afb796a06ae4c673aa4c54ad87ddb"
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path, value as \"value: _\", typ as \"typ: DraftType\", created_at\n FROM draft\n WHERE workspace_id = $1 AND email = $2\n ORDER BY created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "value: _",
|
||||
"type_info": "Json"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "typ: DraftType",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "draft_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"flow",
|
||||
"app"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "67f91733c44e9db3124a713dd950e2bad22709254f20a2c385377b9a00e7d79f"
|
||||
}
|
||||
@@ -7854,6 +7854,74 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/drafts/get/{kind}/{path}:
|
||||
get:
|
||||
summary: get draft
|
||||
operationId: getDraft
|
||||
tags:
|
||||
- draft
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: kind
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- script
|
||||
- flow
|
||||
- app
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: draft
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
typ:
|
||||
type: string
|
||||
enum: ["flow", "script", "app"]
|
||||
value: {}
|
||||
required:
|
||||
- path
|
||||
- typ
|
||||
|
||||
/w/{workspace}/drafts/list:
|
||||
get:
|
||||
summary: list drafts of the current user
|
||||
operationId: listDrafts
|
||||
tags:
|
||||
- draft
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
responses:
|
||||
"200":
|
||||
description: list of drafts
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
typ:
|
||||
type: string
|
||||
enum: ["flow", "script", "app"]
|
||||
value: {}
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- path
|
||||
- typ
|
||||
- created_at
|
||||
|
||||
/w/{workspace}/scripts/create:
|
||||
post:
|
||||
summary: create script
|
||||
|
||||
@@ -13,17 +13,23 @@ use crate::{
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path},
|
||||
routing::{delete, post},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use windmill_common::{db::UserDB, error::Result, utils::StripPath};
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{JsonResult, Result},
|
||||
utils::{not_found_if_none, StripPath},
|
||||
};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(create_draft))
|
||||
.route("/delete/{kind}/{*path}", delete(delete_draft))
|
||||
.route("/get/{kind}/{*path}", get(get_draft))
|
||||
.route("/list", get(list_drafts))
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
@@ -117,24 +123,63 @@ async fn delete_draft(
|
||||
Ok(format!("deleted draft"))
|
||||
}
|
||||
|
||||
// async fn get_draft(
|
||||
// authed: ApiAuthed,
|
||||
// Extension(user_db): Extension<UserDB>,
|
||||
// Path((w_id, path)): Path<(String, StripPath)>,
|
||||
// ) -> JsonResult<Draft> {
|
||||
// let path = path.to_path();
|
||||
// let mut tx = user_db.begin(&authed).await?;
|
||||
// Fetch a single draft (scoped to the requesting user). Used by the editors to
|
||||
// (re)open a never-deployed item that lives only in the `draft` table.
|
||||
async fn get_draft(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, kind, path)): Path<(String, DraftType, StripPath)>,
|
||||
) -> JsonResult<Draft> {
|
||||
let path = path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
// let script_o = sqlx::query_as!(
|
||||
// Draft,
|
||||
// r#"SELECT path, value, typ as "typ: DraftType" FROM draft WHERE path = $1 AND workspace_id = $2"#,
|
||||
// path,
|
||||
// w_id
|
||||
// )
|
||||
// .fetch_optional(&mut *tx)
|
||||
// .await?;
|
||||
// tx.commit().await?;
|
||||
let draft_o = sqlx::query_as!(
|
||||
Draft,
|
||||
r#"SELECT path, value as "value: _", typ as "typ: DraftType"
|
||||
FROM draft
|
||||
WHERE path = $1 AND typ = $2 AND workspace_id = $3 AND email = $4"#,
|
||||
path,
|
||||
kind as DraftType,
|
||||
w_id,
|
||||
&authed.email,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
// let draft = not_found_if_none(script_o, "draft", path)?;
|
||||
// Ok(Json(draft))
|
||||
// }
|
||||
let draft = not_found_if_none(draft_o, "draft", path)?;
|
||||
Ok(Json(draft))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct DraftWithMetadata {
|
||||
pub path: String,
|
||||
pub value: sqlx::types::Json<Box<serde_json::value::Value>>,
|
||||
pub typ: DraftType,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
// List the requesting user's drafts. The editors/home use this to surface
|
||||
// never-deployed items (which no longer have a row in script/flow/app).
|
||||
async fn list_drafts(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
) -> JsonResult<Vec<DraftWithMetadata>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let drafts = sqlx::query_as!(
|
||||
DraftWithMetadata,
|
||||
r#"SELECT path, value as "value: _", typ as "typ: DraftType", created_at
|
||||
FROM draft
|
||||
WHERE workspace_id = $1 AND email = $2
|
||||
ORDER BY created_at DESC"#,
|
||||
w_id,
|
||||
&authed.email,
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Json(drafts))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user