392 lines
13 KiB
Rust
392 lines
13 KiB
Rust
use super::*;
|
|
|
|
impl PostgresRegistry {
|
|
pub async fn list_workspaces(&self) -> Result<Vec<WorkspaceRecord>, RegistryError> {
|
|
let rows = sqlx::query!(
|
|
"select
|
|
id,
|
|
slug,
|
|
display_name,
|
|
status,
|
|
settings_json,
|
|
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
|
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"
|
|
from workspaces
|
|
order by slug asc",
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
rows.into_iter()
|
|
.map(|row| {
|
|
Ok(WorkspaceRecord {
|
|
workspace: Workspace {
|
|
id: WorkspaceId::new(row.id),
|
|
slug: row.slug,
|
|
display_name: row.display_name,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
settings: row.settings_json,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
},
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub async fn list_workspaces_for_user(
|
|
&self,
|
|
user_id: &UserId,
|
|
) -> Result<Vec<WorkspaceMembershipRecord>, RegistryError> {
|
|
let rows = sqlx::query!(
|
|
"select
|
|
w.id,
|
|
w.slug,
|
|
w.display_name,
|
|
w.status,
|
|
w.settings_json,
|
|
to_char(w.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
|
to_char(w.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",
|
|
m.role
|
|
from memberships m
|
|
join workspaces w on w.id = m.workspace_id
|
|
where m.user_id = $1
|
|
order by w.slug asc",
|
|
user_id.as_str(),
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
rows.into_iter()
|
|
.map(|row| {
|
|
Ok(WorkspaceMembershipRecord {
|
|
workspace: Workspace {
|
|
id: WorkspaceId::new(row.id),
|
|
slug: row.slug,
|
|
display_name: row.display_name,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
settings: row.settings_json,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
},
|
|
role: deserialize_enum_text(&row.role, "role")?,
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub async fn list_memberships(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
) -> Result<Vec<MembershipRecord>, RegistryError> {
|
|
let rows = sqlx::query!(
|
|
"select
|
|
m.workspace_id,
|
|
m.user_id,
|
|
m.role,
|
|
to_char(m.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
|
u.email,
|
|
u.display_name,
|
|
u.status,
|
|
to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"user_created_at!\"
|
|
from memberships m
|
|
join users u on u.id = m.user_id
|
|
where m.workspace_id = $1
|
|
order by u.email asc",
|
|
workspace_id.as_str(),
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
rows.into_iter()
|
|
.map(|row| {
|
|
Ok(MembershipRecord {
|
|
workspace_id: WorkspaceId::new(row.workspace_id),
|
|
user: User {
|
|
id: UserId::new(row.user_id),
|
|
email: row.email,
|
|
display_name: row.display_name,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
created_at: row.user_created_at,
|
|
},
|
|
role: deserialize_enum_text(&row.role, "role")?,
|
|
created_at: row.created_at,
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub async fn update_membership_role(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
user_id: &UserId,
|
|
role: MembershipRole,
|
|
) -> Result<(), RegistryError> {
|
|
let result = sqlx::query(
|
|
"update memberships
|
|
set role = $3
|
|
where workspace_id = $1 and user_id = $2",
|
|
)
|
|
.bind(workspace_id.as_str())
|
|
.bind(user_id.as_str())
|
|
.bind(serialize_enum_text(&role, "role")?)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(RegistryError::MembershipNotFound {
|
|
workspace_id: workspace_id.as_str().to_owned(),
|
|
user_id: user_id.as_str().to_owned(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_membership(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
user_id: &UserId,
|
|
) -> Result<(), RegistryError> {
|
|
let result = sqlx::query(
|
|
"delete from memberships
|
|
where workspace_id = $1 and user_id = $2",
|
|
)
|
|
.bind(workspace_id.as_str())
|
|
.bind(user_id.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(RegistryError::MembershipNotFound {
|
|
workspace_id: workspace_id.as_str().to_owned(),
|
|
user_id: user_id.as_str().to_owned(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn list_invitations(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
) -> Result<Vec<InvitationRecord>, RegistryError> {
|
|
let rows = sqlx::query!(
|
|
"select
|
|
id,
|
|
workspace_id,
|
|
email,
|
|
role,
|
|
status,
|
|
token_hash,
|
|
to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\",
|
|
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"
|
|
from invitation_tokens
|
|
where workspace_id = $1
|
|
order by created_at desc",
|
|
workspace_id.as_str(),
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
rows.into_iter()
|
|
.map(|row| {
|
|
Ok(InvitationRecord {
|
|
invitation: InvitationToken {
|
|
id: InvitationId::new(row.id),
|
|
workspace_id: WorkspaceId::new(row.workspace_id),
|
|
email: row.email,
|
|
role: deserialize_enum_text(&row.role, "role")?,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
token_hash: row.token_hash,
|
|
expires_at: row.expires_at,
|
|
created_at: row.created_at,
|
|
},
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub async fn create_invitation(
|
|
&self,
|
|
request: CreateInvitationRequest<'_>,
|
|
) -> Result<(), RegistryError> {
|
|
sqlx::query(
|
|
"insert into invitation_tokens (
|
|
id,
|
|
workspace_id,
|
|
email,
|
|
role,
|
|
status,
|
|
token_hash,
|
|
expires_at,
|
|
created_at
|
|
) values (
|
|
$1, $2, $3, $4, $5, $6, $7::timestamptz, $8::timestamptz
|
|
)",
|
|
)
|
|
.bind(request.invitation.id.as_str())
|
|
.bind(request.invitation.workspace_id.as_str())
|
|
.bind(&request.invitation.email)
|
|
.bind(serialize_enum_text(&request.invitation.role, "role")?)
|
|
.bind(serialize_enum_text(&request.invitation.status, "status")?)
|
|
.bind(&request.invitation.token_hash)
|
|
.bind(&request.invitation.expires_at)
|
|
.bind(&request.invitation.created_at)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_invitation(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
invitation_id: &InvitationId,
|
|
) -> Result<(), RegistryError> {
|
|
let result =
|
|
sqlx::query("delete from invitation_tokens where workspace_id = $1 and id = $2")
|
|
.bind(workspace_id.as_str())
|
|
.bind(invitation_id.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(RegistryError::InvitationNotFound {
|
|
invitation_id: invitation_id.as_str().to_owned(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_workspace(&self, workspace_id: &WorkspaceId) -> Result<(), RegistryError> {
|
|
let result = sqlx::query("delete from workspaces where id = $1")
|
|
.bind(workspace_id.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(RegistryError::WorkspaceNotFound {
|
|
workspace_id: workspace_id.as_str().to_owned(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_workspace(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
) -> Result<Option<WorkspaceRecord>, RegistryError> {
|
|
let row = sqlx::query!(
|
|
"select
|
|
id,
|
|
slug,
|
|
display_name,
|
|
status,
|
|
settings_json,
|
|
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
|
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"
|
|
from workspaces
|
|
where id = $1",
|
|
workspace_id.as_str(),
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
row.map(|row| {
|
|
Ok(WorkspaceRecord {
|
|
workspace: Workspace {
|
|
id: WorkspaceId::new(row.id),
|
|
slug: row.slug,
|
|
display_name: row.display_name,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
settings: row.settings_json,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
},
|
|
})
|
|
})
|
|
.transpose()
|
|
}
|
|
|
|
pub async fn create_workspace(
|
|
&self,
|
|
request: CreateWorkspaceRequest<'_>,
|
|
) -> Result<(), RegistryError> {
|
|
let result = sqlx::query(
|
|
"insert into workspaces (
|
|
id,
|
|
slug,
|
|
display_name,
|
|
status,
|
|
settings_json,
|
|
created_at,
|
|
updated_at
|
|
) values (
|
|
$1, $2, $3, $4, $5, $6::timestamptz, $7::timestamptz
|
|
)",
|
|
)
|
|
.bind(request.workspace.id.as_str())
|
|
.bind(&request.workspace.slug)
|
|
.bind(&request.workspace.display_name)
|
|
.bind(serialize_enum_text(&request.workspace.status, "status")?)
|
|
.bind(Json(request.workspace.settings.clone()))
|
|
.bind(&request.workspace.created_at)
|
|
.bind(&request.workspace.updated_at)
|
|
.execute(&self.pool)
|
|
.await;
|
|
|
|
match result {
|
|
Ok(_) => Ok(()),
|
|
Err(sqlx::Error::Database(error))
|
|
if error.constraint() == Some("workspaces_slug_key") =>
|
|
{
|
|
Err(RegistryError::WorkspaceSlugAlreadyExists {
|
|
slug: request.workspace.slug.clone(),
|
|
})
|
|
}
|
|
Err(error) => Err(RegistryError::Storage(error)),
|
|
}
|
|
}
|
|
|
|
pub async fn update_workspace(
|
|
&self,
|
|
request: UpdateWorkspaceRequest<'_>,
|
|
) -> Result<(), RegistryError> {
|
|
let result = sqlx::query(
|
|
"update workspaces
|
|
set slug = $1,
|
|
display_name = $2,
|
|
status = $3,
|
|
settings_json = $4,
|
|
updated_at = $5::timestamptz
|
|
where id = $6",
|
|
)
|
|
.bind(&request.workspace.slug)
|
|
.bind(&request.workspace.display_name)
|
|
.bind(serialize_enum_text(&request.workspace.status, "status")?)
|
|
.bind(Json(request.workspace.settings.clone()))
|
|
.bind(&request.workspace.updated_at)
|
|
.bind(request.workspace.id.as_str())
|
|
.execute(&self.pool)
|
|
.await;
|
|
|
|
match result {
|
|
Ok(result) if result.rows_affected() == 0 => Err(RegistryError::WorkspaceNotFound {
|
|
workspace_id: request.workspace.id.as_str().to_owned(),
|
|
}),
|
|
Ok(_) => Ok(()),
|
|
Err(sqlx::Error::Database(error))
|
|
if error.constraint() == Some("workspaces_slug_key") =>
|
|
{
|
|
Err(RegistryError::WorkspaceSlugAlreadyExists {
|
|
slug: request.workspace.slug.clone(),
|
|
})
|
|
}
|
|
Err(error) => Err(RegistryError::Storage(error)),
|
|
}
|
|
}
|
|
}
|