Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
owner; test changes
Browse files Browse the repository at this point in the history
  • Loading branch information
thesuzerain committed Nov 26, 2023
1 parent 83ab67d commit 7a144f3
Show file tree
Hide file tree
Showing 25 changed files with 491 additions and 291 deletions.
6 changes: 5 additions & 1 deletion migrations/20231124070100_renaming_consistency.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ ALTER TABLE notifications_actions RENAME COLUMN title TO name;
-- rename project 'description' to 'summary'
-- rename project 'body' to 'description'
ALTER TABLE mods RENAME COLUMN description TO summary;
ALTER TABLE mods RENAME COLUMN body TO description;
ALTER TABLE mods RENAME COLUMN body TO description;

-- Adds 'is_owner' boolean to team members table- only one can be true.
ALTER TABLE team_members ADD COLUMN is_owner boolean NOT NULL DEFAULT false;
UPDATE team_members SET is_owner = true WHERE role = 'Owner';
47 changes: 37 additions & 10 deletions src/database/models/team_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct TeamBuilder {
pub struct TeamMemberBuilder {
pub user_id: UserId,
pub role: String,
pub is_owner: bool,
pub permissions: ProjectPermissions,
pub organization_permissions: Option<OrganizationPermissions>,
pub accepted: bool,
Expand Down Expand Up @@ -50,6 +51,7 @@ impl TeamBuilder {
team_ids,
user_ids,
roles,
is_owners,
permissions,
organization_permissions,
accepteds,
Expand All @@ -64,13 +66,15 @@ impl TeamBuilder {
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
) = members
.into_iter()
.map(|m| {
(
team.id.0,
m.user_id.0,
m.role,
m.is_owner,
m.permissions.bits() as i64,
m.organization_permissions.map(|p| p.bits() as i64),
m.accepted,
Expand All @@ -81,13 +85,14 @@ impl TeamBuilder {
.multiunzip();
sqlx::query!(
"
INSERT INTO team_members (id, team_id, user_id, role, permissions, organization_permissions, accepted, payouts_split, ordering)
SELECT * FROM UNNEST ($1::int8[], $2::int8[], $3::int8[], $4::varchar[], $5::int8[], $6::int8[], $7::bool[], $8::numeric[], $9::int8[])
INSERT INTO team_members (id, team_id, user_id, role, is_owner, permissions, organization_permissions, accepted, payouts_split, ordering)
SELECT * FROM UNNEST ($1::int8[], $2::int8[], $3::int8[], $4::varchar[], $5::bool[], $6::int8[], $7::int8[], $8::bool[], $9::numeric[], $10::int8[])
",
&team_member_ids[..],
&team_ids[..],
&user_ids[..],
&roles[..],
&is_owners[..],
&permissions[..],
&organization_permissions[..] as &[Option<i64>],
&accepteds[..],
Expand Down Expand Up @@ -162,6 +167,7 @@ pub struct TeamMember {
/// The ID of the user associated with the member
pub user_id: UserId,
pub role: String,
pub is_owner: bool,

// The permissions of the user in this project team
// For an organization team, these are the fallback permissions for any project in the organization
Expand Down Expand Up @@ -233,7 +239,7 @@ impl TeamMember {
if !team_ids_parsed.is_empty() {
let teams: Vec<TeamMember> = sqlx::query!(
"
SELECT id, team_id, role AS member_role, permissions, organization_permissions,
SELECT id, team_id, role AS member_role, is_owner, permissions, organization_permissions,
accepted, payouts_split,
ordering, user_id
FROM team_members
Expand All @@ -248,6 +254,7 @@ impl TeamMember {
id: TeamMemberId(m.id),
team_id: TeamId(m.team_id),
role: m.member_role,
is_owner: m.is_owner,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
Expand Down Expand Up @@ -310,7 +317,7 @@ impl TeamMember {

let team_members = sqlx::query!(
"
SELECT id, team_id, role AS member_role, permissions, organization_permissions,
SELECT id, team_id, role AS member_role, is_owner, permissions, organization_permissions,
accepted, payouts_split, role,
ordering, user_id
FROM team_members
Expand All @@ -328,6 +335,7 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
is_owner: m.is_owner,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
Expand Down Expand Up @@ -362,7 +370,7 @@ impl TeamMember {
{
let result = sqlx::query!(
"
SELECT id, team_id, role AS member_role, permissions, organization_permissions,
SELECT id, team_id, role AS member_role, is_owner, permissions, organization_permissions,
accepted, payouts_split, role,
ordering, user_id
Expand All @@ -382,6 +390,7 @@ impl TeamMember {
team_id: id,
user_id,
role: m.role,
is_owner: m.is_owner,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
Expand Down Expand Up @@ -431,11 +440,10 @@ impl TeamMember {
sqlx::query!(
"
DELETE FROM team_members
WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)
WHERE (team_id = $1 AND user_id = $2 AND NOT is_owner = TRUE)
",
id as TeamId,
user_id as UserId,
crate::models::teams::OWNER_ROLE,
)
.execute(&mut **transaction)
.await?;
Expand All @@ -453,6 +461,7 @@ impl TeamMember {
new_accepted: Option<bool>,
new_payouts_split: Option<Decimal>,
new_ordering: Option<i64>,
new_is_owner: Option<bool>,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), super::DatabaseError> {
if let Some(permissions) = new_permissions {
Expand Down Expand Up @@ -546,6 +555,21 @@ impl TeamMember {
.await?;
}

if let Some(is_owner) = new_is_owner {
sqlx::query!(
"
UPDATE team_members
SET is_owner = $1
WHERE (team_id = $2 AND user_id = $3)
",
is_owner,
id as TeamId,
user_id as UserId,
)
.execute(&mut **transaction)
.await?;
}

Ok(())
}

Expand All @@ -559,7 +583,7 @@ impl TeamMember {
{
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
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.is_owner, 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
Expand All @@ -576,6 +600,7 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
is_owner: m.is_owner,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
Expand All @@ -600,7 +625,7 @@ impl TeamMember {
{
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
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.is_owner, 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
Expand All @@ -617,6 +642,7 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
is_owner: m.is_owner,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
Expand All @@ -641,7 +667,7 @@ impl TeamMember {
{
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, v.mod_id
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.is_owner, 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
Expand All @@ -659,6 +685,7 @@ impl TeamMember {
team_id: TeamId(m.team_id),
user_id,
role: m.role,
is_owner: m.is_owner,
permissions: ProjectPermissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
organization_permissions: m
Expand Down
6 changes: 2 additions & 4 deletions src/database/models/user_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,9 @@ impl User {
"
SELECT m.id FROM mods m
INNER JOIN team_members tm ON tm.team_id = m.team_id
WHERE tm.user_id = $1 AND tm.role = $2
WHERE tm.user_id = $1 AND tm.is_owner = TRUE
",
id as UserId,
crate::models::teams::OWNER_ROLE
)
.fetch_many(&mut **transaction)
.try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.id))) })
Expand All @@ -462,11 +461,10 @@ impl User {
"
UPDATE team_members
SET user_id = $1
WHERE (user_id = $2 AND role = $3)
WHERE (user_id = $2 AND is_owner = TRUE)
",
deleted_user as UserId,
id as UserId,
crate::models::teams::OWNER_ROLE
)
.execute(&mut **transaction)
.await?;
Expand Down
1 change: 1 addition & 0 deletions src/models/v2/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Legacy models from V2, where its useful to keep the struct for rerouting/conversion
pub mod projects;
pub mod notifications;
pub mod teams;
40 changes: 40 additions & 0 deletions src/models/v2/teams.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};

use crate::models::{ids::TeamId, users::User, teams::{ProjectPermissions, OrganizationPermissions, TeamMember}};

/// A member of a team
#[derive(Serialize, Deserialize, Clone)]
pub struct LegacyTeamMember {
pub role: String,
// is_owner removed, and role hardcoded to Owner if true,

pub team_id: TeamId,
pub user: User,
pub permissions: Option<ProjectPermissions>,
pub organization_permissions: Option<OrganizationPermissions>, // TODO: technically not a v2 field, should it be kept?
pub accepted: bool,

#[serde(with = "rust_decimal::serde::float_option")]
pub payouts_split: Option<Decimal>,
pub ordering: i64,
}

impl LegacyTeamMember {
pub fn from(team_member : TeamMember) -> Self {
LegacyTeamMember {
role: match (team_member.is_owner, team_member.role.as_str()) {
(true, _) => "Owner".to_string(),
(false, "Owner") => "Member".to_string(), // The odd case of a non-owner with the owner role should show as 'Member'
(false, role) => role.to_string(),
},
team_id: team_member.team_id,
user: team_member.user,
permissions: team_member.permissions,
organization_permissions: team_member.organization_permissions,
accepted: team_member.accepted,
payouts_split: team_member.payouts_split,
ordering: team_member.ordering,
}
}
}
3 changes: 3 additions & 0 deletions src/models/v3/teams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ pub struct TeamMember {
pub user: User,
/// The role of the user in the team
pub role: String,
/// Is the user the owner of the team?
pub is_owner: bool,
/// A bitset containing the user's permissions in this team.
/// In an organization-controlled project, these are the unique overriding permissions for the user's role for any project in the organization, if they exist.
/// In an organization, these are the default project permissions for any project in the organization.
Expand Down Expand Up @@ -178,6 +180,7 @@ impl TeamMember {
team_id: data.team_id.into(),
user,
role: data.role,
is_owner: data.is_owner,
permissions: if override_permissions {
None
} else {
Expand Down
45 changes: 39 additions & 6 deletions src/routes/v2/teams.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::database::redis::RedisPool;
use crate::models::teams::{OrganizationPermissions, ProjectPermissions, TeamId};
use crate::models::teams::{OrganizationPermissions, ProjectPermissions, TeamId, TeamMember};
use crate::models::users::UserId;
use crate::models::v2::teams::LegacyTeamMember;
use crate::queue::session::AuthQueue;
use crate::routes::{v3, ApiError, v2_reroute};
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
Expand Down Expand Up @@ -34,7 +35,15 @@ pub async fn team_members_get_project(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::team_members_get_project(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)
let response = v3::teams::team_members_get_project(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
let members = members.into_iter().map(LegacyTeamMember::from).collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}

#[get("{id}/members")]
Expand All @@ -45,7 +54,15 @@ pub async fn team_members_get_organization(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::team_members_get_organization(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)
let response = v3::teams::team_members_get_organization(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
let members = members.into_iter().map(LegacyTeamMember::from).collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}

// Returns all members of a team, but not necessarily those of a project-team's organization (unlike team_members_get_project)
Expand All @@ -57,7 +74,15 @@ pub async fn team_members_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::team_members_get(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)
let response = v3::teams::team_members_get(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
let members = members.into_iter().map(LegacyTeamMember::from).collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}

#[derive(Serialize, Deserialize)]
Expand All @@ -73,14 +98,22 @@ pub async fn teams_get(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
v3::teams::teams_get(
let response = v3::teams::teams_get(
req,
web::Query(v3::teams::TeamIds { ids: ids.ids }),
pool,
redis,
session_queue,
)
.await.or_else(v2_reroute::flatten_404_error)
.await.or_else(v2_reroute::flatten_404_error);
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Vec<TeamMember>>>(response?).await {
Ok(members) => {
let members = members.into_iter().map(|members| members.into_iter().map(LegacyTeamMember::from).collect::<Vec<_>>()).collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(members))
}
Err(response) => Ok(response),
}
}

#[post("{id}/join")]
Expand Down
2 changes: 0 additions & 2 deletions src/routes/v2/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,7 @@ pub async fn user_notifications(
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
println!("Gott notifications");
let response = v3::users::user_notifications(req, info, pool, redis, session_queue).await.or_else(v2_reroute::flatten_404_error)?;

// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Notification>>(response).await {
Ok(notifications) => {
Expand Down
Loading

0 comments on commit 7a144f3

Please sign in to comment.