wizards: ensure-chain preflight in AddToTeam + AddToCompany (W1)
TeamWizard already ran ensure-chain before /api/teams; the two "AddTo"
modals didn't, so teams/companies created from them landed as
structural orphans (no company/org parent). Same treatment now applied
to both modals + their backend endpoints.
Backend — two symmetric `attach_to_*_id` fields (mirrors what
create_team already exposes):
- ComposeTeamRequest gains `attach_to_company_id`. After team insert,
create_team_from_claws binds it via companies::add_team with a fresh
`n{count}` node id.
- CreateCompanyRequest gains `attach_to_org_id`. After company insert,
create_company binds it via orgs::add_company the same way.
Both bindings are optional — plain POSTs from tools/tests still work.
Ownership is re-checked via `<parent>::get(pool, id, workspace_id)` so
the endpoints can't be tricked into parenting into another workspace.
Frontend — both modals now:
1. POST /api/structure/ensure-chain (empty body → server picks
"My Workspace" / "General" fallbacks when nothing exists yet).
2. Include the returned parent id in the create request.
AddToOrgModal untouched — orgs are top-level, no parent needed.
MasterPlannerModal untouched — it posts to /webhooks, doesn't create
structural rows.
Follow-up already queued in the original list: same treatment for the
Company/Org "wizard"-flavored surfaces (as opposed to the compose
modals). Currently those don't exist as distinct wizards.
This commit is contained in:
@@ -28,6 +28,12 @@ pub struct CreateCompanyRequest {
|
||||
/// TopologyKind (snake_case), e.g. "hierarchical", "pipeline".
|
||||
pub kind: String,
|
||||
pub members: Vec<CompanyMemberInput>,
|
||||
/// Optional parent org id. When set, the new company is bound under
|
||||
/// this org via `orgs::add_company` inside the same handler so the
|
||||
/// company never lands orphaned. Wizards fetch this via
|
||||
/// `POST /api/structure/ensure-chain`.
|
||||
#[serde(default)]
|
||||
pub attach_to_org_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -104,6 +110,19 @@ pub async fn create_company(
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-parent under the caller's ensured org, mirroring create_team's
|
||||
// attach_to_company_id pattern. Ownership-checked; skipped when the
|
||||
// caller didn't run ensure-chain.
|
||||
if let Some(org_id) = body.attach_to_org_id {
|
||||
let org = cm_db::repo::orgs::get(&state.pool, org_id, user.workspace_id)
|
||||
.await
|
||||
.map_err(|_| ApiError::NotFound)?;
|
||||
let existing = cm_db::repo::orgs::companies_for_org(&state.pool, org.id).await?;
|
||||
let node_id = format!("n{}", existing.len());
|
||||
cm_db::repo::orgs::add_company(&state.pool, org.id, &node_id, company_id, "company")
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(CompanyCreated {
|
||||
|
||||
Reference in New Issue
Block a user