1
0

Organizations (#712)

* untested, unformatted, un-refactored

* minor simplification

* simplification fix

* refactoring, changes

* some fixes

* fixes, refactoring

* missed cache

* revs

* revs - more!

* removed donation links; added all org members to route

* renamed slug to title

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Wyatt Verchere
2023-10-02 10:56:57 -07:00
committed by GitHub
parent 58a61051b9
commit a1b59d4545
24 changed files with 3658 additions and 979 deletions

View File

@@ -1,5 +1,5 @@
use super::ids::*;
use crate::models::teams::Permissions;
use super::{ids::*, Organization, Project};
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
use itertools::Itertools;
use redis::cmd;
use rust_decimal::Decimal;
@@ -14,7 +14,8 @@ pub struct TeamBuilder {
pub struct TeamMemberBuilder {
pub user_id: UserId,
pub role: String,
pub permissions: Permissions,
pub permissions: ProjectPermissions,
pub organization_permissions: Option<OrganizationPermissions>,
pub accepted: bool,
pub payouts_split: Decimal,
pub ordering: i64,
@@ -41,30 +42,20 @@ impl TeamBuilder {
for member in self.members {
let team_member_id = generate_team_member_id(&mut *transaction).await?;
let team_member = TeamMember {
id: team_member_id,
team_id,
user_id: member.user_id,
role: member.role,
permissions: member.permissions,
accepted: member.accepted,
payouts_split: member.payouts_split,
ordering: member.ordering,
};
sqlx::query!(
"
INSERT INTO team_members (id, team_id, user_id, role, permissions, accepted, payouts_split, ordering)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
INSERT INTO team_members (id, team_id, user_id, role, permissions, organization_permissions, accepted, payouts_split, ordering)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
",
team_member.id as TeamMemberId,
team_member.team_id as TeamId,
team_member.user_id as UserId,
team_member.role,
team_member.permissions.bits() as i64,
team_member.accepted,
team_member.payouts_split,
team_member.ordering,
team_member_id as TeamMemberId,
team.id as TeamId,
member.user_id as UserId,
member.role,
member.permissions.bits() as i64,
member.organization_permissions.map(|p| p.bits() as i64),
member.accepted,
member.payouts_split,
member.ordering,
)
.execute(&mut *transaction)
.await?;
@@ -80,15 +71,70 @@ pub struct Team {
pub id: TeamId,
}
#[derive(Deserialize, Serialize, Clone, Debug, Copy)]
pub enum TeamAssociationId {
Project(ProjectId),
Organization(OrganizationId),
}
impl Team {
pub async fn get_association<'a, 'b, E>(
id: TeamId,
executor: E,
) -> Result<Option<TeamAssociationId>, super::DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT m.id AS pid, NULL AS oid
FROM mods m
WHERE m.team_id = $1
UNION ALL
SELECT NULL AS pid, o.id AS oid
FROM organizations o
WHERE o.team_id = $1
",
id as TeamId
)
.fetch_optional(executor)
.await?;
if let Some(t) = result {
// Only one of project_id or organization_id will be set
let mut team_association_id = None;
if let Some(pid) = t.pid {
team_association_id = Some(TeamAssociationId::Project(ProjectId(pid)));
}
if let Some(oid) = t.oid {
team_association_id = Some(TeamAssociationId::Organization(OrganizationId(oid)));
}
return Ok(team_association_id);
}
Ok(None)
}
}
/// A member of a team
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct TeamMember {
pub id: TeamMemberId,
pub team_id: TeamId,
/// The ID of the user associated with the member
pub user_id: UserId,
pub role: String,
pub permissions: Permissions,
// The permissions of the user in this project team
// For an organization team, these are the fallback permissions for any project in the organization
pub permissions: ProjectPermissions,
// The permissions of the user in this organization team
// For a project team, this is None
pub organization_permissions: Option<OrganizationPermissions>,
pub accepted: bool,
pub payouts_split: Decimal,
pub ordering: i64,
@@ -154,31 +200,34 @@ impl TeamMember {
if !team_ids_parsed.is_empty() {
let teams: Vec<TeamMember> = sqlx::query!(
"
SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering,
tm.user_id user_id
FROM team_members tm
WHERE tm.team_id = ANY($1)
ORDER BY tm.team_id, tm.ordering
SELECT id, team_id, role AS member_role, permissions, organization_permissions,
accepted, payouts_split,
ordering, user_id
FROM team_members
WHERE team_id = ANY($1)
ORDER BY team_id, ordering;
",
&team_ids_parsed
)
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|m|
TeamMember {
id: TeamMemberId(m.id),
team_id: TeamId(m.team_id),
role: m.member_role,
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
accepted: m.accepted,
user_id: UserId(m.user_id),
payouts_split: m.payouts_split,
ordering: m.ordering,
}
))
})
.try_collect::<Vec<TeamMember>>()
.await?;
.fetch_many(exec)
.try_filter_map(|e| async {
Ok(e.right().map(|m| TeamMember {
id: TeamMemberId(m.id),
team_id: TeamId(m.team_id),
role: m.member_role,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
.organization_permissions
.map(|p| OrganizationPermissions::from_bits(p as u64).unwrap_or_default()),
accepted: m.accepted,
user_id: UserId(m.user_id),
payouts_split: m.payouts_split,
ordering: m.ordering,
}))
})
.try_collect::<Vec<TeamMember>>()
.await?;
for (id, members) in &teams.into_iter().group_by(|x| x.team_id) {
let mut members = members.collect::<Vec<_>>();
@@ -240,7 +289,9 @@ impl TeamMember {
let team_members = sqlx::query!(
"
SELECT id, team_id, user_id, role, permissions, accepted, payouts_split, ordering
SELECT id, team_id, role AS member_role, permissions, organization_permissions,
accepted, payouts_split, role,
ordering, user_id
FROM team_members
WHERE (team_id = ANY($1) AND user_id = $2 AND accepted = TRUE)
ORDER BY ordering
@@ -256,7 +307,11 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
.organization_permissions
.map(|p| OrganizationPermissions::from_bits(p as u64).unwrap_or_default()),
accepted: m.accepted,
payouts_split: m.payouts_split,
ordering: m.ordering,
@@ -286,9 +341,13 @@ impl TeamMember {
{
let result = sqlx::query!(
"
SELECT id, user_id, role, permissions, accepted, payouts_split, ordering
SELECT id, team_id, role AS member_role, permissions, organization_permissions,
accepted, payouts_split, role,
ordering, user_id
FROM team_members
WHERE (team_id = $1 AND user_id = $2)
ORDER BY ordering
",
id as TeamId,
user_id as UserId
@@ -302,7 +361,11 @@ impl TeamMember {
team_id: id,
user_id,
role: m.role,
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
.organization_permissions
.map(|p| OrganizationPermissions::from_bits(p as u64).unwrap_or_default()),
accepted: m.accepted,
payouts_split: m.payouts_split,
ordering: m.ordering,
@@ -319,10 +382,10 @@ impl TeamMember {
sqlx::query!(
"
INSERT INTO team_members (
id, team_id, user_id, role, permissions, accepted
id, team_id, user_id, role, permissions, organization_permissions, accepted
)
VALUES (
$1, $2, $3, $4, $5, $6
$1, $2, $3, $4, $5, $6, $7
)
",
self.id as TeamMemberId,
@@ -330,6 +393,7 @@ impl TeamMember {
self.user_id as UserId,
self.role,
self.permissions.bits() as i64,
self.organization_permissions.map(|p| p.bits() as i64),
self.accepted,
)
.execute(&mut *transaction)
@@ -362,7 +426,8 @@ impl TeamMember {
pub async fn edit_team_member(
id: TeamId,
user_id: UserId,
new_permissions: Option<Permissions>,
new_permissions: Option<ProjectPermissions>,
new_organization_permissions: Option<OrganizationPermissions>,
new_role: Option<String>,
new_accepted: Option<bool>,
new_payouts_split: Option<Decimal>,
@@ -384,6 +449,21 @@ impl TeamMember {
.await?;
}
if let Some(organization_permissions) = new_organization_permissions {
sqlx::query!(
"
UPDATE team_members
SET organization_permissions = $1
WHERE (team_id = $2 AND user_id = $3)
",
organization_permissions.bits() as i64,
id as TeamId,
user_id as UserId,
)
.execute(&mut *transaction)
.await?;
}
if let Some(role) = new_role {
sqlx::query!(
"
@@ -458,7 +538,8 @@ impl TeamMember {
{
let result = sqlx::query!(
"
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted, tm.payouts_split, tm.ordering FROM mods m
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.organization_permissions, tm.accepted, tm.payouts_split, tm.ordering
FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 AND accepted = TRUE
WHERE m.id = $1
",
@@ -474,7 +555,52 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
.organization_permissions
.map(|p| OrganizationPermissions::from_bits(p as u64).unwrap_or_default()),
accepted: m.accepted,
payouts_split: m.payouts_split,
ordering: m.ordering,
}))
} else {
Ok(None)
}
}
pub async fn get_from_user_id_organization<'a, 'b, E>(
id: OrganizationId,
user_id: UserId,
executor: E,
) -> Result<Option<Self>, super::DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.organization_permissions, tm.accepted, tm.payouts_split, tm.ordering
FROM organizations o
INNER JOIN team_members tm ON tm.team_id = o.team_id AND user_id = $2 AND accepted = TRUE
WHERE o.id = $1
",
id as OrganizationId,
user_id as UserId
)
.fetch_optional(executor)
.await?;
if let Some(m) = result {
Ok(Some(TeamMember {
id: TeamMemberId(m.id),
team_id: TeamId(m.team_id),
user_id,
role: m.role,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
.organization_permissions
.map(|p| OrganizationPermissions::from_bits(p as u64).unwrap_or_default()),
accepted: m.accepted,
payouts_split: m.payouts_split,
ordering: m.ordering,
@@ -494,7 +620,8 @@ impl TeamMember {
{
let result = sqlx::query!(
"
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted, tm.payouts_split, tm.ordering FROM versions v
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.organization_permissions, tm.accepted, tm.payouts_split, tm.ordering, v.mod_id
FROM versions v
INNER JOIN mods m ON m.id = v.mod_id
INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.user_id = $2 AND tm.accepted = TRUE
WHERE v.id = $1
@@ -511,7 +638,11 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
.organization_permissions
.map(|p| OrganizationPermissions::from_bits(p as u64).unwrap_or_default()),
accepted: m.accepted,
payouts_split: m.payouts_split,
ordering: m.ordering,
@@ -520,4 +651,30 @@ impl TeamMember {
Ok(None)
}
}
// Gets both required members for checking permissions of an action on a project
// - project team member (a user's membership to a given project)
// - organization team member (a user's membership to a given organization that owns a given project)
pub async fn get_for_project_permissions<'a, 'b, E>(
project: &Project,
user_id: UserId,
executor: E,
) -> Result<(Option<Self>, Option<Self>), super::DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let project_team_member =
Self::get_from_user_id(project.team_id, user_id, executor).await?;
let organization =
Organization::get_associated_organization_project_id(project.id, executor).await?;
let organization_team_member = if let Some(organization) = &organization {
Self::get_from_user_id(organization.team_id, user_id, executor).await?
} else {
None
};
Ok((project_team_member, organization_team_member))
}
}