mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 08:01:35 +00:00
[ee] feat: create a personal workspace on cloud signup instead of the demo invite
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9
This commit is contained in:
co-authored by
Claude Opus 5
parent
7751d3e43e
commit
9f12745b13
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS(\n SELECT 1 FROM workspace_invite WHERE email = $1\n UNION ALL\n SELECT 1 FROM usr WHERE email = $1\n )",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "exists",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9d5fb7a829a1631328cf9a34be41895619c0c69848fa8ea60f46e5edb676fec1"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
d6aef91c0f7ba556befbf4addeb7674d4a9dd819
|
||||
395720dc81b2d5e3aff5be26a2600a85ca07f4a7
|
||||
@@ -5275,6 +5275,117 @@ async fn _check_nb_of_archived_workspaces(db: &DB) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Insert a workspace and everything it needs to be usable: settings, encryption key,
|
||||
/// the owner as its admin, and the `all` / `wm_deployers` groups. Returns the owner's
|
||||
/// username in the new workspace.
|
||||
///
|
||||
/// The caller owns the transaction and the audit log: the signup path creates a
|
||||
/// workspace before the user has an `ApiAuthed` to audit under.
|
||||
pub async fn insert_workspace<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
id: &str,
|
||||
name: &str,
|
||||
owner_email: &str,
|
||||
color: Option<&str>,
|
||||
error_handler_fallback_to_instance_alerts: bool,
|
||||
requested_username: Option<String>,
|
||||
) -> Result<String> {
|
||||
validate_workspace_name(name)?;
|
||||
check_w_id_conflict(tx, id).await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace
|
||||
(id, name, owner)
|
||||
VALUES ($1, $2, $3)",
|
||||
id,
|
||||
name,
|
||||
owner_email,
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
if error_handler_fallback_to_instance_alerts {
|
||||
ensure_instance_alert_fallback_allowed(tx, id).await?;
|
||||
}
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_settings
|
||||
(workspace_id, color, error_handler_fallback_to_instance_alerts)
|
||||
VALUES ($1, $2, $3)",
|
||||
id,
|
||||
color,
|
||||
error_handler_fallback_to_instance_alerts,
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
let key = rd_string(64);
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_key
|
||||
(workspace_id, kind, key)
|
||||
VALUES ($1, 'cloud', $2)",
|
||||
id,
|
||||
&key
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
let automate_username_creation = sqlx::query_scalar!(
|
||||
"SELECT value FROM global_settings WHERE name = $1",
|
||||
AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?
|
||||
.map(|v| v.as_bool())
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
|
||||
let username = if automate_username_creation {
|
||||
if requested_username.is_some_and(|u| u.len() > 0) {
|
||||
return Err(Error::BadRequest(
|
||||
"username is not allowed when username creation is automated".to_string(),
|
||||
));
|
||||
}
|
||||
get_instance_username_or_create_pending(tx, owner_email).await?
|
||||
} else {
|
||||
requested_username.ok_or(Error::BadRequest("username is required".to_string()))?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr
|
||||
(workspace_id, email, username, is_admin)
|
||||
VALUES ($1, $2, $3, true)",
|
||||
id,
|
||||
owner_email,
|
||||
username,
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO group_
|
||||
VALUES ($1, 'all', 'The group that always contains all users of this workspace')",
|
||||
id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO group_
|
||||
VALUES ($1, 'wm_deployers', 'Members can preserve the original author when deploying to this workspace')",
|
||||
id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr_to_group
|
||||
VALUES ($1, 'all', $2)",
|
||||
id,
|
||||
username
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
async fn create_workspace(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -5302,115 +5413,17 @@ async fn create_workspace(
|
||||
}
|
||||
}
|
||||
|
||||
validate_workspace_name(&nw.name)?;
|
||||
|
||||
let mut tx: Transaction<'_, Postgres> = db.begin().await?;
|
||||
|
||||
check_w_id_conflict(&mut tx, &nw.id).await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace
|
||||
(id, name, owner)
|
||||
VALUES ($1, $2, $3)",
|
||||
nw.id,
|
||||
nw.name,
|
||||
authed.email,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
if nw.error_handler_fallback_to_instance_alerts {
|
||||
ensure_instance_alert_fallback_allowed(&mut tx, &nw.id).await?;
|
||||
}
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_settings
|
||||
(workspace_id, color, error_handler_fallback_to_instance_alerts)
|
||||
VALUES ($1, $2, $3)",
|
||||
nw.id,
|
||||
nw.color,
|
||||
insert_workspace(
|
||||
&mut tx,
|
||||
&nw.id,
|
||||
&nw.name,
|
||||
&authed.email,
|
||||
nw.color.as_deref(),
|
||||
nw.error_handler_fallback_to_instance_alerts,
|
||||
nw.username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
let key = rd_string(64);
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_key
|
||||
(workspace_id, kind, key)
|
||||
VALUES ($1, 'cloud', $2)",
|
||||
nw.id,
|
||||
&key
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
// let mc = magic_crypt::new_magic_crypt!(key, 256);
|
||||
// sqlx::query!(
|
||||
// "INSERT INTO variable
|
||||
// (workspace_id, path, value, is_secret, description)
|
||||
// VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
|
||||
// ($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
|
||||
// nw.id,
|
||||
// crate::variables::encrypt(&mc, "pretty secret value"),
|
||||
// nw.id,
|
||||
// "finland does not actually exist",
|
||||
// )
|
||||
// .execute(&mut *tx)
|
||||
// .await?;
|
||||
|
||||
let automate_username_creation = sqlx::query_scalar!(
|
||||
"SELECT value FROM global_settings WHERE name = $1",
|
||||
AUTOMATE_USERNAME_CREATION_SETTING,
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.map(|v| v.as_bool())
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
|
||||
let username = if automate_username_creation {
|
||||
if nw.username.is_some() && nw.username.unwrap().len() > 0 {
|
||||
return Err(Error::BadRequest(
|
||||
"username is not allowed when username creation is automated".to_string(),
|
||||
));
|
||||
}
|
||||
get_instance_username_or_create_pending(&mut tx, &authed.email).await?
|
||||
} else {
|
||||
nw.username
|
||||
.ok_or(Error::BadRequest("username is required".to_string()))?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr
|
||||
(workspace_id, email, username, is_admin)
|
||||
VALUES ($1, $2, $3, true)",
|
||||
nw.id,
|
||||
authed.email,
|
||||
username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO group_
|
||||
VALUES ($1, 'all', 'The group that always contains all users of this workspace')",
|
||||
nw.id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO group_
|
||||
VALUES ($1, 'wm_deployers', 'Members can preserve the original author when deploying to this workspace')",
|
||||
nw.id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr_to_group
|
||||
VALUES ($1, 'all', $2)",
|
||||
nw.id,
|
||||
username
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { ArrowLeft } from 'lucide-svelte'
|
||||
import { UserService } from '$lib/gen/services.gen'
|
||||
import { UserService, WorkspaceService } from '$lib/gen/services.gen'
|
||||
import { goto } from '$lib/navigation'
|
||||
import { usersWorkspaceStore } from '$lib/stores'
|
||||
import { switchWorkspace } from '$lib/storeUtils'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
||||
@@ -16,7 +18,7 @@
|
||||
Building2,
|
||||
Twitter,
|
||||
Youtube,
|
||||
Bot,
|
||||
Bot,
|
||||
MessageCircleCode
|
||||
} from 'lucide-svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
@@ -78,6 +80,25 @@
|
||||
currentStep = STEP_SOURCE
|
||||
}
|
||||
|
||||
// Cloud signup creates a workspace for the user, so the picker would show a single
|
||||
// entry and one more click. Enter it directly; fall back to the picker for anyone
|
||||
// with none or several.
|
||||
async function leaveOnboarding() {
|
||||
try {
|
||||
const workspaces = await WorkspaceService.listUserWorkspaces()
|
||||
usersWorkspaceStore.set(workspaces)
|
||||
const owned = workspaces.workspaces.filter((w) => w.id !== 'admins')
|
||||
if (owned.length === 1) {
|
||||
switchWorkspace(owned[0].id)
|
||||
await goto('/')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Could not list workspaces after onboarding:', error)
|
||||
}
|
||||
await goto('/user/workspaces')
|
||||
}
|
||||
|
||||
async function continueToWorkspaces() {
|
||||
if (!selectedSource || isSubmitting) return
|
||||
|
||||
@@ -96,7 +117,7 @@
|
||||
sendUserToast('Failed to save information: ' + (error?.body || error?.message || error), true)
|
||||
} finally {
|
||||
// do not block users from accessing windmill even if there is an error
|
||||
goto('/user/workspaces')
|
||||
leaveOnboarding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +131,7 @@
|
||||
console.error('Error skipping onboarding:', error)
|
||||
} finally {
|
||||
// do not block users from accessing windmill even if there is an error
|
||||
goto('/user/workspaces')
|
||||
leaveOnboarding()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user