Adds /teams route (#373)

* basic list (no grouping yet)

* now groups and checks auth, moved Team::get_many to TeamMember::get_from_team_full_many

* Ran 'cargo sqlx prepare'

* batch TeamMember::get_from_user_id

* Batches before for loop

* Ran 'cargo sqlx prepare'

Co-authored-by: Emma Cypress <emmaffle@modrinth.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Ricky12Awesome 2022-07-27 00:40:20 -05:00 committed by GitHub
parent b864791fa6
commit 13335cadc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 326 additions and 0 deletions

View File

@ -2189,6 +2189,57 @@
]
}
},
"64570e9cadd7391ad45a1029a0e5212e17720c4f65682384d7493fc350114228": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "team_id",
"ordinal": 1,
"type_info": "Int8"
},
{
"name": "user_id",
"ordinal": 2,
"type_info": "Int8"
},
{
"name": "role",
"ordinal": 3,
"type_info": "Varchar"
},
{
"name": "permissions",
"ordinal": 4,
"type_info": "Int8"
},
{
"name": "accepted",
"ordinal": 5,
"type_info": "Bool"
}
],
"nullable": [
false,
false,
false,
false,
false,
false
],
"parameters": {
"Left": [
"Int8Array",
"Int8"
]
}
},
"query": "\n SELECT id, team_id, user_id, role, permissions, accepted\n FROM team_members\n WHERE (team_id = ANY($1) AND user_id = $2 AND accepted = TRUE)\n "
},
"67d021f0776276081d3c50ca97afa6b78b98860bf929009e845e9c00a192e3b5": {
"query": "\n SELECT id FROM report_types\n WHERE name = $1\n ",
"describe": {
@ -5383,6 +5434,104 @@
"nullable": []
}
},
"d553bb44ac600047656962bd1c44378fb32e74f0e7f07d1c26336dc80dffe78a": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "team_id",
"ordinal": 1,
"type_info": "Int8"
},
{
"name": "member_role",
"ordinal": 2,
"type_info": "Varchar"
},
{
"name": "permissions",
"ordinal": 3,
"type_info": "Int8"
},
{
"name": "accepted",
"ordinal": 4,
"type_info": "Bool"
},
{
"name": "user_id",
"ordinal": 5,
"type_info": "Int8"
},
{
"name": "github_id",
"ordinal": 6,
"type_info": "Int8"
},
{
"name": "user_name",
"ordinal": 7,
"type_info": "Varchar"
},
{
"name": "email",
"ordinal": 8,
"type_info": "Varchar"
},
{
"name": "avatar_url",
"ordinal": 9,
"type_info": "Varchar"
},
{
"name": "username",
"ordinal": 10,
"type_info": "Varchar"
},
{
"name": "bio",
"ordinal": 11,
"type_info": "Varchar"
},
{
"name": "created",
"ordinal": 12,
"type_info": "Timestamptz"
},
{
"name": "user_role",
"ordinal": 13,
"type_info": "Varchar"
}
],
"nullable": [
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
false,
true,
false,
false
],
"parameters": {
"Left": [
"Int8Array"
]
}
},
"query": "\n SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted,\n u.id user_id, u.github_id github_id, u.name user_name, u.email email,\n u.avatar_url avatar_url, u.username username, u.bio bio,\n u.created created, u.role user_role\n FROM team_members tm\n INNER JOIN users u ON u.id = tm.user_id\n WHERE tm.team_id = ANY($1)\n ORDER BY tm.team_id\n "
},
"d5a496a0e17c5784f98ca2067bff996b23bb0a798609c4d4928df8080e4e1758": {
"query": "\n SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,\n v.changelog, v.changelog_url, v.date_published, v.downloads,\n v.version_type, v.featured\n FROM versions v\n WHERE v.id = ANY($1)\n ORDER BY v.date_published ASC\n ",
"describe": {

View File

@ -202,6 +202,71 @@ impl TeamMember {
Ok(team_members)
}
pub async fn get_from_team_full_many<'a, E>(
team_ids: Vec<TeamId>,
exec: E,
) -> Result<Vec<QueryTeamMember>, super::DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;
let team_ids_parsed: Vec<i64> =
team_ids.into_iter().map(|x| x.0).collect();
let teams = sqlx::query!(
"
SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted,
u.id user_id, u.github_id github_id, u.name user_name, u.email email,
u.avatar_url avatar_url, u.username username, u.bio bio,
u.created created, u.role user_role
FROM team_members tm
INNER JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = ANY($1)
ORDER BY tm.team_id
",
&team_ids_parsed
)
.fetch_many(exec)
.try_filter_map(|e| async {
if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64);
if let Some(perms) = permissions {
Ok(Some(Ok(QueryTeamMember {
id: TeamMemberId(m.id),
team_id: TeamId(m.team_id),
role: m.member_role,
permissions: perms,
accepted: m.accepted,
user: User {
id: UserId(m.user_id),
github_id: m.github_id,
name: m.user_name,
email: m.email,
avatar_url: m.avatar_url,
username: m.username,
bio: m.bio,
created: m.created,
role: m.user_role,
},
})))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else {
Ok(None)
}
})
.try_collect::<Vec<Result<QueryTeamMember, super::DatabaseError>>>()
.await?;
let team_members = teams
.into_iter()
.collect::<Result<Vec<QueryTeamMember>, super::DatabaseError>>()?;
Ok(team_members)
}
/// Lists the team members for a user. Does not list pending requests.
pub async fn get_from_user_public<'a, 'b, E>(
id: UserId,
@ -334,6 +399,59 @@ impl TeamMember {
}
}
/// Gets team members from user ids and team ids. Does not return pending members.
pub async fn get_from_user_id_many<'a, 'b, E>(
team_ids: Vec<TeamId>,
user_id: UserId,
executor: E,
) -> Result<Vec<Self>, super::DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
use futures::stream::TryStreamExt;
let team_ids_parsed: Vec<i64> =
team_ids.into_iter().map(|x| x.0).collect();
let team_members = sqlx::query!(
"
SELECT id, team_id, user_id, role, permissions, accepted
FROM team_members
WHERE (team_id = ANY($1) AND user_id = $2 AND accepted = TRUE)
",
&team_ids_parsed,
user_id as UserId
)
.fetch_many(executor)
.try_filter_map(|e| async {
if let Some(m) = e.right() {
let permissions = Permissions::from_bits(m.permissions as u64);
if let Some(perms) = permissions {
Ok(Some(Ok(TeamMember {
id: TeamMemberId(m.id),
team_id: TeamId(m.team_id),
user_id,
role: m.role,
permissions: perms,
accepted: m.accepted,
})))
} else {
Ok(Some(Err(super::DatabaseError::Bitflag)))
}
} else {
Ok(None)
}
})
.try_collect::<Vec<Result<TeamMember, super::DatabaseError>>>()
.await?;
let team_members = team_members
.into_iter()
.collect::<Result<Vec<TeamMember>, super::DatabaseError>>()?;
Ok(team_members)
}
/// Gets a team member from a user id and team id, including pending members.
pub async fn get_from_user_id_pending<'a, 'b, E>(
id: TeamId,

View File

@ -126,6 +126,8 @@ pub fn users_config(cfg: &mut web::ServiceConfig) {
}
pub fn teams_config(cfg: &mut web::ServiceConfig) {
cfg.service(teams::teams_get);
cfg.service(
web::scope("team")
.service(teams::team_members_get)

View File

@ -101,6 +101,63 @@ pub async fn team_members_get(
Ok(HttpResponse::Ok().json(team_members))
}
#[derive(Serialize, Deserialize)]
pub struct TeamIds {
pub ids: String,
}
#[get("teams")]
pub async fn teams_get(
req: HttpRequest,
web::Query(ids): web::Query<TeamIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
use itertools::Itertools;
let team_ids = serde_json::from_str::<Vec<TeamId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect::<Vec<crate::database::models::ids::TeamId>>();
let teams_data =
TeamMember::get_from_team_full_many(team_ids.clone(), &**pool).await?;
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
let accepted = if let Some(user) = current_user {
TeamMember::get_from_user_id_many(team_ids, user.id.into(), &**pool)
.await?
.into_iter()
.map(|m| m.team_id.0)
.collect()
} else {
std::collections::HashSet::new()
};
let teams_groups = teams_data.into_iter().group_by(|data| data.team_id.0);
let mut teams: Vec<Vec<crate::models::teams::TeamMember>> = vec![];
for (id, member_data) in &teams_groups {
if accepted.contains(&id) {
let team_members = member_data.map(|data| {
crate::models::teams::TeamMember::from(data, false)
});
teams.push(team_members.collect());
continue;
}
let team_members = member_data
.filter(|x| x.accepted)
.map(|data| crate::models::teams::TeamMember::from(data, true));
teams.push(team_members.collect());
}
Ok(HttpResponse::Ok().json(teams))
}
#[post("{id}/join")]
pub async fn join_team(
req: HttpRequest,